rsyntaxtree 0.9.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.tags +171 -0
- data/Gemfile +2 -0
- data/README.md +0 -1
- data/Rakefile +7 -0
- data/bin/rsyntaxtree +38 -31
- data/fonts/OpenMoji-Black.ttf +0 -0
- data/fonts/OpenMoji-Color.ttf +0 -0
- data/lib/rsyntaxtree/base_graph.rb +262 -0
- data/lib/rsyntaxtree/element.rb +155 -25
- data/lib/rsyntaxtree/elementlist.rb +16 -13
- data/lib/rsyntaxtree/markup_parser.rb +208 -0
- data/lib/rsyntaxtree/string_parser.rb +187 -204
- data/lib/rsyntaxtree/svg_graph.rb +446 -299
- data/lib/rsyntaxtree/utils.rb +49 -6
- data/lib/rsyntaxtree/version.rb +1 -1
- data/lib/rsyntaxtree.rb +143 -161
- data/rsyntaxtree.gemspec +2 -0
- data/syntree.png +0 -0
- data/syntree.svg +24 -0
- data/test/markup_parser_test.rb +207 -0
- metadata +39 -11
- data/fonts/latinmodern-math.otf +0 -0
- data/fonts/lmroman10-bold.otf +0 -0
- data/fonts/lmroman10-bolditalic.otf +0 -0
- data/fonts/lmroman10-italic.otf +0 -0
- data/fonts/lmroman10-regular.otf +0 -0
- data/lib/rsyntaxtree/error_message.rb +0 -68
- data/lib/rsyntaxtree/graph.rb +0 -312
- data/lib/rsyntaxtree/tree_graph.rb +0 -328
data/lib/rsyntaxtree/utils.rb
CHANGED
@@ -6,15 +6,58 @@
|
|
6
6
|
#==========================
|
7
7
|
#
|
8
8
|
# Image utility functions to inspect text font metrics
|
9
|
-
#
|
10
|
-
# This file is part of RSyntaxTree, which is a ruby port of Andre Eisenbach's
|
11
|
-
# excellent program phpSyntaxTree.
|
12
|
-
#
|
13
9
|
# Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
|
14
|
-
|
10
|
+
|
11
|
+
require 'rmagick'
|
12
|
+
include Magick
|
15
13
|
|
16
14
|
class String
|
17
15
|
def contains_cjk?
|
18
|
-
!!(self =~ /\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}/)
|
16
|
+
!!(self.gsub(WHITESPACE_BLOCK, "") =~ /\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}|[^\x01-\x7E]/)
|
17
|
+
end
|
18
|
+
|
19
|
+
def contains_emoji?
|
20
|
+
!!(self.gsub(WHITESPACE_BLOCK, "").gsub(/\d/, "") =~ /\p{Emoji}/)
|
21
|
+
end
|
22
|
+
|
23
|
+
def all_emoji?
|
24
|
+
!!(self.gsub(WHITESPACE_BLOCK, "").gsub(/\d/, "") =~ /\A\p{Emoji}[\p{Emoji}\s]*\z/)
|
19
25
|
end
|
26
|
+
|
27
|
+
def split_by_emoji
|
28
|
+
results = []
|
29
|
+
self.split(//).each do |ch|
|
30
|
+
case ch
|
31
|
+
when /\d/, WHITESPACE_BLOCK
|
32
|
+
results << {:type => :normal, :char => ch}
|
33
|
+
when /\p{Emoji}/
|
34
|
+
results << {:type => :emoji, :char => ch}
|
35
|
+
else
|
36
|
+
results << {:type => :normal, :char => ch}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
results.reject{|string| string == ""}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module FontMetrics
|
44
|
+
def get_metrics(text, font, fontsize, font_style, font_weight)
|
45
|
+
background = Image.new(1, 1)
|
46
|
+
gc = Draw.new
|
47
|
+
gc.annotate(background, 0, 0, 0, 0, text) do |gc|
|
48
|
+
gc.font = font
|
49
|
+
gc.font_style = font_style == :italic ? ItalicStyle : NormalStyle
|
50
|
+
gc.font_weight = font_weight == :bold ? BoldWeight : NormalWeight
|
51
|
+
gc.pointsize = fontsize
|
52
|
+
gc.gravity = CenterGravity
|
53
|
+
gc.stroke = 'none'
|
54
|
+
gc.kerning = 0
|
55
|
+
gc.interline_spacing = 0
|
56
|
+
gc.interword_spacing = 0
|
57
|
+
end
|
58
|
+
metrics = gc.get_multiline_type_metrics(background, text)
|
59
|
+
return metrics
|
60
|
+
end
|
61
|
+
module_function :get_metrics
|
20
62
|
end
|
63
|
+
|
data/lib/rsyntaxtree/version.rb
CHANGED
data/lib/rsyntaxtree.rb
CHANGED
@@ -7,186 +7,168 @@
|
|
7
7
|
#
|
8
8
|
# Facade of rsyntaxtree library. When loaded by a driver script, it does all
|
9
9
|
# the necessary 'require' to use the library.
|
10
|
-
#
|
11
|
-
# This file is part of RSyntaxTree, which is originally a ruby port of Andre Eisenbach's
|
12
|
-
# excellent program phpSyntaxTree.
|
13
10
|
# Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
|
14
|
-
# Copyright (c) 2003-2004 Andre Eisenbach <andre@ironcreek.net>
|
15
11
|
|
16
12
|
$LOAD_PATH << File.join( File.dirname(__FILE__), 'rsyntaxtree')
|
17
13
|
|
14
|
+
FONT_DIR = File.expand_path(File.dirname(__FILE__) + "/../fonts")
|
15
|
+
ETYPE_NODE = 1
|
16
|
+
ETYPE_LEAF = 2
|
17
|
+
SUBSCRIPT_CONST = 0.7
|
18
|
+
FONT_SCALING = 2
|
19
|
+
WHITESPACE_BLOCK = "■"
|
20
|
+
|
21
|
+
class RSTError < StandardError
|
22
|
+
def initialize(msg="Error: something unexpected occurred")
|
23
|
+
msg.gsub!(WHITESPACE_BLOCK, "<>")
|
24
|
+
super msg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
18
28
|
require 'cgi'
|
29
|
+
require 'rsvg2'
|
19
30
|
require 'utils'
|
20
31
|
require 'element'
|
21
32
|
require 'elementlist'
|
22
33
|
require 'string_parser'
|
23
|
-
require 'tree_graph'
|
24
34
|
require 'svg_graph'
|
25
|
-
require 'error_message'
|
26
35
|
require 'version'
|
36
|
+
require 'rmagick'
|
37
|
+
|
38
|
+
module RSyntaxTree
|
39
|
+
|
40
|
+
class RSGenerator
|
41
|
+
|
42
|
+
def initialize(params = {})
|
43
|
+
new_params = {}
|
44
|
+
fontset = {}
|
45
|
+
params.each do |keystr, value|
|
46
|
+
key = keystr.to_sym
|
47
|
+
case key
|
48
|
+
when :data
|
49
|
+
data = value
|
50
|
+
data = data.gsub('-AMP-', '&')
|
51
|
+
.gsub('-PERCENT-', "%")
|
52
|
+
.gsub('-PRIME-', "'")
|
53
|
+
.gsub('-SCOLON-', ';')
|
54
|
+
.gsub('-OABRACKET-', '<')
|
55
|
+
.gsub('-CABRACKET-', '>')
|
56
|
+
new_params[key] = data
|
57
|
+
|
58
|
+
when :symmetrize, :color, :transparent
|
59
|
+
new_params[key] = value && (value != "off" && value != "false") ? true : false
|
60
|
+
when :fontsize
|
61
|
+
new_params[key] = value.to_i * FONT_SCALING
|
62
|
+
when :margin
|
63
|
+
new_params[key] = value.to_i * FONT_SCALING * 5
|
64
|
+
when :vheight
|
65
|
+
new_params[key] = value.to_f
|
66
|
+
when :fontstyle
|
67
|
+
if value == "noto-sans" || value == "sans"
|
68
|
+
fontset[:normal] = FONT_DIR + "/NotoSans-Regular.ttf"
|
69
|
+
fontset[:italic] = FONT_DIR + "/NotoSans-Italic.ttf"
|
70
|
+
fontset[:bold] = FONT_DIR + "/NotoSans-Bold.ttf"
|
71
|
+
fontset[:bolditalic] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
|
72
|
+
fontset[:math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
|
73
|
+
fontset[:cjk] = FONT_DIR + "/NotoSansJP-Regular.otf"
|
74
|
+
fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf"
|
75
|
+
new_params[:fontstyle] = "sans"
|
76
|
+
elsif value == "noto-serif" || value == "serif"
|
77
|
+
fontset[:normal] = FONT_DIR + "/NotoSerif-Regular.ttf"
|
78
|
+
fontset[:italic] = FONT_DIR + "/NotoSerif-Italic.ttf"
|
79
|
+
fontset[:bold] = FONT_DIR + "/NotoSerif-Bold.ttf"
|
80
|
+
fontset[:bolditalic] = FONT_DIR + "/NotoSerif-BoldItalic.ttf"
|
81
|
+
fontset[:math] = FONT_DIR + "/latinmodern-math.otf"
|
82
|
+
fontset[:cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf"
|
83
|
+
fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf"
|
84
|
+
new_params[:fontstyle] = "serif"
|
85
|
+
elsif value == "cjk zenhei" || value == "cjk"
|
86
|
+
fontset[:normal] = FONT_DIR + "/wqy-zenhei.ttf"
|
87
|
+
fontset[:italic] = FONT_DIR + "/NotoSans-Italic.ttf"
|
88
|
+
fontset[:bold] = FONT_DIR + "/NotoSans-Bold.ttf"
|
89
|
+
fontset[:bolditalic] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
|
90
|
+
fontset[:math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
|
91
|
+
fontset[:cjk] = FONT_DIR + "/wqy-zenhei.ttf"
|
92
|
+
fontset[:emoji] = FONT_DIR + "/OpenMoji-Black.ttf"
|
93
|
+
new_params[:fontstyle] = "cjk"
|
94
|
+
end
|
95
|
+
else
|
96
|
+
new_params[key] = value
|
97
|
+
end
|
98
|
+
end
|
27
99
|
|
28
|
-
|
100
|
+
# defaults to the following
|
101
|
+
@params = {
|
102
|
+
:symmetrize => true,
|
103
|
+
:color => true,
|
104
|
+
:transparent => false,
|
105
|
+
:fontsize => 16,
|
106
|
+
:format => "png",
|
107
|
+
:leafstyle => "auto",
|
108
|
+
:filename => "syntree",
|
109
|
+
:data => "",
|
110
|
+
:margin => 0,
|
111
|
+
:vheight => 1.0,
|
112
|
+
}
|
113
|
+
|
114
|
+
@params.merge! new_params
|
115
|
+
@params[:fontsize] = @params[:fontsize] * FONT_SCALING
|
116
|
+
@params[:margin] = @params[:margin] * FONT_SCALING
|
117
|
+
@params[:fontset] = fontset
|
118
|
+
|
119
|
+
$single_X_metrics = FontMetrics.get_metrics("X", fontset[:normal], @params[:fontsize], :normal, :normal)
|
120
|
+
$height_connector_to_text = $single_X_metrics.height / 2.0
|
121
|
+
$single_line_height = $single_X_metrics.height * 2.0
|
122
|
+
$width_half_X = $single_X_metrics.width / 2.0
|
123
|
+
$height_connector = $single_X_metrics.height * 1.0 * @params[:vheight]
|
124
|
+
$h_gap_between_nodes = $single_X_metrics.width * 0.8
|
125
|
+
$box_vertical_margin = $single_X_metrics.height * 0.8
|
126
|
+
end
|
29
127
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
128
|
+
def self.check_data(text)
|
129
|
+
if text.to_s == ""
|
130
|
+
raise RSTError, "Error: input text is empty"
|
131
|
+
end
|
132
|
+
StringParser.valid?(text)
|
133
|
+
end
|
35
134
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
.gsub('-PRIME-', "'")
|
48
|
-
.gsub('-SCOLON-', ';')
|
49
|
-
.gsub('-OABRACKET-', '<')
|
50
|
-
.gsub('-CABRACKET-', '>')
|
51
|
-
new_params[key] = data
|
52
|
-
new_params[:multibyte] = data.contains_cjk?
|
53
|
-
when :symmetrize, :color, :autosub, :transparent
|
54
|
-
new_params[key] = value && value != "off" ? true : false
|
55
|
-
when :fontsize
|
56
|
-
new_params[key] = value.to_i * FONT_SCALING
|
57
|
-
when :margin
|
58
|
-
new_params[key] = value.to_i * FONT_SCALING * 4
|
59
|
-
when :vheight
|
60
|
-
new_params[key] = value.to_f
|
61
|
-
when :fontstyle
|
62
|
-
if value == "noto-sans" || value == "sans"
|
63
|
-
new_params[:font] = FONT_DIR + "/NotoSans-Regular.ttf"
|
64
|
-
new_params[:font_it] = FONT_DIR + "/NotoSans-Italic.ttf"
|
65
|
-
new_params[:font_bd] = FONT_DIR + "/NotoSans-Bold.ttf"
|
66
|
-
new_params[:font_bdit] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
|
67
|
-
new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
|
68
|
-
new_params[:font_cjk] = FONT_DIR + "/NotoSansJP-Regular.otf"
|
69
|
-
new_params[:fontstyle] = "sans"
|
70
|
-
elsif value == "noto-serif" || value == "serif"
|
71
|
-
new_params[:font] = FONT_DIR + "/NotoSerif-Regular.ttf"
|
72
|
-
new_params[:font_it] = FONT_DIR + "/NotoSerif-Italic.ttf"
|
73
|
-
new_params[:font_bd] = FONT_DIR + "/NotoSerif-Bold.ttf"
|
74
|
-
new_params[:font_bdit] = FONT_DIR + "/NotoSerif-BoldItalic.ttf"
|
75
|
-
new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
|
76
|
-
new_params[:font_cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf"
|
77
|
-
new_params[:fontstyle] = "serif"
|
78
|
-
elsif value == "cjk zenhei" || value == "cjk"
|
79
|
-
new_params[:font] = FONT_DIR + "/wqy-zenhei.ttf"
|
80
|
-
new_params[:font_it] = FONT_DIR + "/NotoSans-Italic.ttf"
|
81
|
-
new_params[:font_bd] = FONT_DIR + "/NotoSans-Bold.ttf"
|
82
|
-
new_params[:font_bdit] = FONT_DIR + "/NotoSans-BoldItalic.ttf"
|
83
|
-
new_params[:font_math] = FONT_DIR + "/NotoSansMath-Regular.ttf"
|
84
|
-
new_params[:font_cjk] = FONT_DIR + "/wqy-zenhei.ttf"
|
85
|
-
new_params[:fontstyle] = "sans"
|
86
|
-
elsif value == "modern-math" || value == "math"
|
87
|
-
new_params[:font] = FONT_DIR + "/latinmodern-math.otf"
|
88
|
-
new_params[:font_it] = FONT_DIR + "/lmroman10-italic.otf"
|
89
|
-
new_params[:font_bd] = FONT_DIR + "/lmroman10-bold.otf"
|
90
|
-
new_params[:font_bdit] = FONT_DIR + "/lmroman10-bolditalic.otf"
|
91
|
-
new_params[:font_math] = FONT_DIR + "/latinmodern-math.otf"
|
92
|
-
new_params[:font_cjk] = FONT_DIR + "/NotoSerifJP-Regular.otf"
|
93
|
-
new_params[:fontstyle] = "math"
|
94
|
-
end
|
135
|
+
def draw_png(binary = false)
|
136
|
+
svg = draw_svg
|
137
|
+
rsvg = RSVG::Handle.new_from_data(svg)
|
138
|
+
dim = rsvg.dimensions
|
139
|
+
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, dim.width, dim.height)
|
140
|
+
context = Cairo::Context.new(surface)
|
141
|
+
context.render_rsvg_handle(rsvg)
|
142
|
+
b = StringIO.new
|
143
|
+
surface.write_to_png(b)
|
144
|
+
if binary
|
145
|
+
return b
|
95
146
|
else
|
96
|
-
|
147
|
+
return b.string
|
97
148
|
end
|
98
149
|
end
|
99
150
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
:fontsize => 18,
|
107
|
-
:format => "png",
|
108
|
-
:leafstyle => "auto",
|
109
|
-
:filename => "syntree",
|
110
|
-
:data => "",
|
111
|
-
:margin => 0,
|
112
|
-
:vheight => 1.0,
|
113
|
-
:fontstyle => "sans",
|
114
|
-
:font => "/NotoSansJP-Regular.otf",
|
115
|
-
:font_it => "/NotoSans-Italic.ttf",
|
116
|
-
:font_bd => "/NotoSans-Bold.ttf",
|
117
|
-
:font_bdit => "/NotoSans-BoldItalic.ttf",
|
118
|
-
:font_math => "/NotoSansMath-Regular.ttf"
|
119
|
-
}
|
120
|
-
@metrics = {
|
121
|
-
:e_width => 0,
|
122
|
-
:e_padd => 20,
|
123
|
-
:v_space => 0,
|
124
|
-
:h_space => 0,
|
125
|
-
:b_side => 0,
|
126
|
-
:b_topbot => 0
|
127
|
-
# :e_width => 120,
|
128
|
-
# :e_padd => 14,
|
129
|
-
# :v_space => 20,
|
130
|
-
# :h_space => 20,
|
131
|
-
# :b_side => 10,
|
132
|
-
# :b_topbot => 10
|
133
|
-
}
|
134
|
-
|
135
|
-
@params.merge! new_params
|
136
|
-
@params[:fontsize] = @params[:fontsize] * FONT_SCALING
|
137
|
-
@params[:margin] = @params[:margin] * FONT_SCALING
|
138
|
-
@metrics[:v_space] = @metrics[:v_space] * @params[:vheight]
|
139
|
-
end
|
140
|
-
|
141
|
-
def self.check_data(text)
|
142
|
-
sp = StringParser.new(text)
|
143
|
-
sp.valid?
|
144
|
-
end
|
145
|
-
|
146
|
-
def draw_png
|
147
|
-
@params[:format] = "png"
|
148
|
-
draw_tree
|
149
|
-
end
|
150
|
-
|
151
|
-
def draw_jpg
|
152
|
-
@params[:format] = "jpg"
|
153
|
-
draw_tree
|
154
|
-
end
|
155
|
-
|
156
|
-
def draw_gif
|
157
|
-
@params[:format] = "gif"
|
158
|
-
draw_tree
|
159
|
-
end
|
160
|
-
|
161
|
-
def draw_pdf
|
162
|
-
@params[:format] = "pdf"
|
163
|
-
draw_tree
|
164
|
-
end
|
165
|
-
|
166
|
-
def draw_svg
|
167
|
-
@params[:format] = "svg"
|
168
|
-
sp = StringParser.new(@params[:data].gsub('&', '&').gsub('%', '%'))
|
169
|
-
sp.parse
|
170
|
-
sp.auto_subscript if @params[:autosub]
|
171
|
-
elist = sp.get_elementlist
|
172
|
-
graph = SVGGraph.new(elist, @metrics,
|
173
|
-
@params[:symmetrize], @params[:color], @params[:leafstyle], @params[:multibyte],
|
174
|
-
@params[:fontstyle], @params[:font], @params[:font_cjk], @params[:fontsize], @params[:margin], @params[:transparent]
|
175
|
-
)
|
176
|
-
graph.svg_data
|
177
|
-
end
|
151
|
+
def draw_svg
|
152
|
+
sp = StringParser.new(@params[:data].gsub('&', '&').gsub('%', '%'), @params[:fontset], @params[:fontsize])
|
153
|
+
sp.parse
|
154
|
+
graph = SVGGraph.new(sp.get_elementlist, @params)
|
155
|
+
graph.svg_data
|
156
|
+
end
|
178
157
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
158
|
+
def draw_tree
|
159
|
+
svg = draw_svg
|
160
|
+
image, data = Magick::Image.from_blob(svg) do |im|
|
161
|
+
im.format = 'svg'
|
162
|
+
end
|
163
|
+
if @params[:format] == "png"
|
164
|
+
image = draw_png(false)
|
165
|
+
else
|
166
|
+
image.to_blob do |im|
|
167
|
+
im.format = @params[:format].upcase
|
168
|
+
end
|
169
|
+
end
|
170
|
+
image
|
171
|
+
end
|
190
172
|
end
|
191
173
|
end
|
192
174
|
|
data/rsyntaxtree.gemspec
CHANGED
@@ -19,4 +19,6 @@ Gem::Specification.new do |s|
|
|
19
19
|
# specify any dependencies here; for example:
|
20
20
|
s.add_runtime_dependency 'rmagick', '~> 4.2', '>= 4.2.3'
|
21
21
|
s.add_runtime_dependency 'optimist', '~> 3.0', '>= 3.0.1'
|
22
|
+
s.add_runtime_dependency 'parslet'
|
23
|
+
s.add_runtime_dependency 'rsvg2'
|
22
24
|
end
|
data/syntree.png
ADDED
Binary file
|
data/syntree.svg
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg width="161.4" height="471.2" viewBox="-20, -20, 181.4, 491.2" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
4
|
+
<defs>
|
5
|
+
<marker id="arrow" markerUnits="strokeWidth" markerWidth="12.160000000000002" markerHeight="12.160000000000002" viewBox="0 0 12.160000000000002 12.160000000000002" refX="6.080000000000001" refY="0">
|
6
|
+
<polyline fill="none" stroke="purple" stroke-width="1" points="0,12.160000000000002,6.080000000000001,0,12.160000000000002,12.160000000000002" />
|
7
|
+
</marker>
|
8
|
+
<pattern id="hatchBlack" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
9
|
+
<line x1="0" y="0" x2="0" y2="10" stroke="black" stroke-width="4"></line>
|
10
|
+
</pattern>
|
11
|
+
<pattern id="hatchForNode" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
12
|
+
<line x1="0" y="0" x2="0" y2="10" stroke="blue" stroke-width="4"></line>
|
13
|
+
</pattern>
|
14
|
+
<pattern id="hatchForLeaf" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
15
|
+
<line x1="0" y="0" x2="0" y2="10" stroke="green" stroke-width="4"></line>
|
16
|
+
</pattern>
|
17
|
+
</defs>
|
18
|
+
<rect x="-20" y="-20" width="181.4" height="491.2" stroke="none" fill="white" />"
|
19
|
+
<text white-space='pre' alignment-baseline='text-top' style='fill: blue; font-size: 64px;' x='30.400000000000006' y='132.0'><tspan x='30.400000000000006' y='132.0' style="" text-decoration="" font-family="'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif">X%</tspan>
|
20
|
+
</text>
|
21
|
+
<text white-space='pre' alignment-baseline='text-top' style='fill: green; font-size: 64px;' x='33.400000000000006' y='431.2'><tspan x='33.400000000000006' y='431.2' style="" text-decoration="" font-family="'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif">X&</tspan>
|
22
|
+
</text>
|
23
|
+
<line style='stroke:black; stroke-width:2;' x1='75.9' y1='321.2' x2='75.9' y2='187.0' />
|
24
|
+
</svg>
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "minitest/pride"
|
3
|
+
require "./lib/rsyntaxtree/markup_parser.rb"
|
4
|
+
|
5
|
+
class MarkupParserTest < Minitest::Test
|
6
|
+
def setup
|
7
|
+
@parser = MarkupParser.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_rule_cr
|
11
|
+
@parser.cr.parse('\\n')
|
12
|
+
# "\\n"@0
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_rule_brackets
|
16
|
+
@parser.brackets.parse('#')
|
17
|
+
# "#"@0
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_rule_triangle
|
21
|
+
@parser.triangle.parse('^')
|
22
|
+
# "^"@0
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_rule_path
|
26
|
+
@parser.path.parse('+12')
|
27
|
+
# {:path=>"+12"@0}
|
28
|
+
@parser.path.parse('+>34')
|
29
|
+
# {:path=>"+>34"@0}d
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_rule_escaped
|
33
|
+
"<>^+*_=~|-".split(//).each do |chr|
|
34
|
+
@parser.escaped.parse('\\' + chr)
|
35
|
+
# {:chr=>"<"@1}
|
36
|
+
# {:chr=>">"@1}
|
37
|
+
# {:chr=>"^"@1}
|
38
|
+
# {:chr=>"+"@1}
|
39
|
+
# {:chr=>"*"@1}
|
40
|
+
# {:chr=>"_"@1}
|
41
|
+
# {:chr=>"="@1}
|
42
|
+
# {:chr=>"~"@1}
|
43
|
+
# {:chr=>"|"@1}
|
44
|
+
# {:chr=>"-"@1}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_rule_non_escaped
|
49
|
+
"abcde12345".split(//).each do |chr|
|
50
|
+
@parser.non_escaped.parse(chr)
|
51
|
+
# {:chr=>"a"@0}
|
52
|
+
# {:chr=>"b"@0}
|
53
|
+
# {:chr=>"c"@0}
|
54
|
+
# {:chr=>"d"@0}
|
55
|
+
# {:chr=>"e"@0}
|
56
|
+
# {:chr=>"1"@0}
|
57
|
+
# {:chr=>"2"@0}
|
58
|
+
# {:chr=>"3"@0}
|
59
|
+
# {:chr=>"4"@0}
|
60
|
+
# {:chr=>"5"@0}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_rule_text
|
65
|
+
text = "abcde\\<\\>\\^\\+\\*\\_\\=\\~\\|\\-12345"
|
66
|
+
@parser.text.parse(text)
|
67
|
+
# {:text=>
|
68
|
+
# [{:chr=>"a"@0},
|
69
|
+
# {:chr=>"b"@1},
|
70
|
+
# {:chr=>"c"@2},
|
71
|
+
# {:chr=>"d"@3},
|
72
|
+
# {:chr=>"e"@4},
|
73
|
+
# {:chr=>"<"@6},
|
74
|
+
# {:chr=>">"@8},
|
75
|
+
# {:chr=>"^"@10},
|
76
|
+
# {:chr=>"+"@12},
|
77
|
+
# {:chr=>"*"@14},
|
78
|
+
# {:chr=>"_"@16},
|
79
|
+
# {:chr=>"="@18},
|
80
|
+
# {:chr=>"~"@20},
|
81
|
+
# {:chr=>"|"@22},
|
82
|
+
# {:chr=>"-"@24},
|
83
|
+
# {:chr=>"1"@25},
|
84
|
+
# {:chr=>"2"@26},
|
85
|
+
# {:chr=>"3"@27},
|
86
|
+
# {:chr=>"4"@28},
|
87
|
+
# {:chr=>"5"@29}]}
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_rule_bolditalic
|
91
|
+
text = "***~=X\\+Y\\*Z=~***"
|
92
|
+
@parser.bolditalic.parse(text)
|
93
|
+
# {:bolditalic=>{:linethrough=>{:overline=>{:text=>[{:chr=>"X"@5}, {:chr=>"+"@7}, {:chr=>"Y"@8}, {:chr=>"*"@10}, {:chr=>"Z"@11}]}}}}
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_rule_bold
|
97
|
+
text = "**~=X\\+Y\\*Z=~**"
|
98
|
+
@parser.bold.parse(text)
|
99
|
+
# {:bold=>{:linethrough=>{:overline=>{:text=>[{:chr=>"X"@4}, {:chr=>"+"@6}, {:chr=>"Y"@7}, {:chr=>"*"@9}, {:chr=>"Z"@10}]}}}}
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_rule_italic
|
103
|
+
text = "*~=X\\+Y\\*Z=~*"
|
104
|
+
@parser.italic.parse(text)
|
105
|
+
# {:italic=>{:linethrough=>{:overline=>{:text=>[{:chr=>"X"@3}, {:chr=>"+"@5}, {:chr=>"Y"@6}, {:chr=>"*"@8}, {:chr=>"Z"@9}]}}}}
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_rule_overline
|
109
|
+
text = "=*~X\\+Y\\*Z~*="
|
110
|
+
@parser.overline.parse(text)
|
111
|
+
# {:overline=> ...}
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_rule_underline
|
115
|
+
text = "-*~X\\+Y\\*Z~*-"
|
116
|
+
@parser.underline.parse(text)
|
117
|
+
# {:underline=> ...}
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_rule_linethrough
|
121
|
+
text = "~*=X\\+Y\\*Z=*~"
|
122
|
+
@parser.linethrough.parse(text)
|
123
|
+
# {:linethrough=> ...}
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_rule_superscript
|
127
|
+
text = "__~*=X\\+Y\\*Z=*~__"
|
128
|
+
@parser.superscript.parse(text)
|
129
|
+
# {:superscript=> ...}
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_rule_subscript
|
133
|
+
text = "_~*=X\\+Y\\*Z=*~_"
|
134
|
+
@parser.subscript.parse(text)
|
135
|
+
# {:subscript=> ...}
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_rule_box
|
139
|
+
text = "|~*=X\\+Y\\*Z=*~|"
|
140
|
+
@parser.box.parse(text)
|
141
|
+
# {:box=> ...}
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_rule_markup
|
145
|
+
text = "|~*=X\\+Y\\*Z=*~|"
|
146
|
+
@parser.markup.parse(text)
|
147
|
+
# {:box=>{:linethrough=>{:italic=>{:overline=>{:text=>[
|
148
|
+
# {:chr=>"X"@4}, {:chr=>"+"@6}, {:chr=>"Y"@7}, {:chr=>"*"@9}, {:chr=>"Z"@10}]}}}}
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_rule_border
|
152
|
+
text = "----"
|
153
|
+
@parser.border.parse(text)
|
154
|
+
# {:border=>"----"@0}
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_rule_line
|
158
|
+
text1 = "----"
|
159
|
+
@parser.line.parse(text1)
|
160
|
+
# {:border=>"----"@0}
|
161
|
+
text2 = "-u-|b|n"
|
162
|
+
@parser.line.parse(text2)
|
163
|
+
# {:line=>[{:underline=>{:text=>[{:chr=>"u"@1}]}}, {:box=>{:text=>[{:chr=>"b"@4}]}}, {:text=>[{:chr=>"n"@6}]}]}
|
164
|
+
text3 = "\\n"
|
165
|
+
@parser.line.parse(text3)
|
166
|
+
# "\\n"@0
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_rule_lines
|
170
|
+
text1 = "^#X_Y_Z"
|
171
|
+
@parser.lines.parse(text1)
|
172
|
+
# [{:triangle=>"^"@0, :brackets=>"#"@1}, {:line=>[{:text=>[{:chr=>"X"@2}]},
|
173
|
+
# {:subscript=>{:text=>[{:chr=>"Y"@4}]}}, {:text=>[{:chr=>"Z"@6}]}]}, {:paths=>[]}]
|
174
|
+
text2 = "^X_Y_Z+1+>2"
|
175
|
+
@parser.lines.parse(text2)
|
176
|
+
# [{:triangle=>"^"@0, :enclosure=>:none},
|
177
|
+
# {:line=>[{:text=>[{:chr=>"X"@2}]}, {:subscript=>{:text=>[{:chr=>"Y"@4}]}}, {:text=>[{:chr=>"Z"@6}]}]},
|
178
|
+
# {:paths=>[{:path=>"+1"@7}, {:path=>"+>2"@9}]}]
|
179
|
+
text3 = "^----\\n\\nX_Y_Z+1+>2"
|
180
|
+
@parser.lines.parse(text3)
|
181
|
+
# [{:triangle=>"^"@0, :enclosure=>:none},
|
182
|
+
# {:border=>"----"@1},
|
183
|
+
# {:blankline=>"\\n\\n"@5},
|
184
|
+
# {:line=>[{:text=>[{:chr=>"X"@9}]}, {:subscript=>{:text=>[{:chr=>"Y"@11}]}},
|
185
|
+
# {:text=>[{:chr=>"Z"@13}]}]},
|
186
|
+
# {:paths=>[{:path=>"+1"@14}, {:path=>"+>2"@16}]}]
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_evaluator
|
190
|
+
text1 = "^#----\\n\\nX_Y_Z+1+>2"
|
191
|
+
Markup.parse text1
|
192
|
+
# {:status=>:success,
|
193
|
+
# :results=>
|
194
|
+
# {:enclosure=>:brackets,
|
195
|
+
# :triangle=>true,
|
196
|
+
# :paths=>[{:path=>"+1"@15}, {:path=>"+>2"@17}],
|
197
|
+
# :contents=>
|
198
|
+
# [{:type=>:border},
|
199
|
+
# {:type=>:blankline},
|
200
|
+
# {:type=>:text, :elements=>[{:text=>"X", :decoration=>[]},
|
201
|
+
# {:text=>"Y", :decoration=>[:subscript]}, {:text=>"Z", :decoration=>[]}]}]}}
|
202
|
+
text2 = "!^#----\\n\\nX_Y_Z+1+>2"
|
203
|
+
Markup.parse text2
|
204
|
+
# {:status=>:error, :text=>"!^#----\\n\\nX_Y_Z+1+>2"}
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|