rsyntaxtree 0.9.2 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Copyright (c) 2003-2004 Andre Eisenbach <andre@ironcreek.net>
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
+
@@ -1,4 +1,4 @@
1
1
  module RSyntaxTree
2
- VERSION = "0.9.2"
2
+ VERSION = "1.0.5"
3
3
  end
4
4
 
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
- FONT_DIR = File.expand_path(File.dirname(__FILE__) + "/../fonts")
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
- ETYPE_UNDEFINED = 0
31
- ETYPE_NODE = 1
32
- ETYPE_LEAF = 2
33
- SUBSCRIPT_CONST = 0.7
34
- FONT_SCALING = 2
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
- class RSGenerator
37
- def initialize(params = {})
38
- new_params = {}
39
- params.each do |keystr, value|
40
- key = keystr.to_sym
41
- case key
42
- when :data
43
- data = value
44
- # data = CGI.unescape(value)
45
- data = data.gsub('-AMP-', '&')
46
- .gsub('-PERCENT-', "%")
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
- new_params[key] = value
147
+ return b.string
97
148
  end
98
149
  end
99
150
 
100
- # defaults to the following
101
- @params = {
102
- :symmetrize => true,
103
- :color => true,
104
- :autosub => false,
105
- :transparent => false,
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('&', '&amp;').gsub('%', '&#37;'))
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('&', '&amp;'), @params[:fontset], @params[:fontsize])
153
+ sp.parse
154
+ graph = SVGGraph.new(sp.get_elementlist, @params)
155
+ graph.svg_data
156
+ end
178
157
 
179
- def draw_tree
180
- sp = StringParser.new(@params[:data])
181
- sp.parse
182
- sp.auto_subscript if @params[:autosub]
183
- elist = sp.get_elementlist
184
- graph = TreeGraph.new(elist, @metrics,
185
- @params[:symmetrize], @params[:color], @params[:leafstyle], @params[:multibyte],
186
- @params[:fontstyle], @params[:font], @params[:font_it], @params[:font_bd], @params[:font_bdit], @params[:font_math],
187
- @params[:font_cjk], @params[:fontsize], @params[:margin], @params[:transparent]
188
- )
189
- graph.to_blob(@params[:format])
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="722.4" height="471.2" viewBox="-20, -20, 742.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="742.4" height="491.2" stroke="none" fill="white" />"
19
+ <text white-space='pre' alignment-baseline='text-top' style='fill: blue; font-size: 64px;' x='319.4' y='132.0'><tspan x='319.4' 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='30.399999999999977' y='431.2'><tspan x='30.399999999999977' y='431.2' style="" text-decoration="" font-family="'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif">%Yxxxxxxxxxxxxxxxxx</tspan>
22
+ </text>
23
+ <line style='stroke:black; stroke-width:2;' x1='356.4' y1='321.2' x2='356.4' 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