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,43 +6,174 @@
6
6
  #==========================
7
7
  #
8
8
  # Aa class that represents a basic tree element, either node or leaf.
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>
15
10
 
16
- class Element
11
+ require "markup_parser"
12
+ require 'utils'
13
+
14
+ module RSyntaxTree
15
+ class Element
17
16
 
18
- attr_accessor :id, :parent, :type, :content, :level, :width, :indent, :triangle
19
- def initialize(id = 0, parent = 0, content = "", level = 0, type = ETYPE_LEAF)
17
+ attr_accessor :id,
18
+ :parent, :type, :level,
19
+ :width, :height,
20
+ :content, :content_width, :content_height,
21
+ :horizontal_indent, :vertical_indent,
22
+ :triangle, :enclosure, :children, :parent,
23
+ :font, :fontsize, :contains_phrase,
24
+ :path
25
+
26
+ def initialize(id, parent, content, level, fontset, fontsize)
27
+ @type = ETYPE_LEAF
20
28
  @id = id # Unique element id
21
29
  @parent = parent # Parent element id
30
+ @children = [] # Child element ids
22
31
  @type = type # Element type
23
32
  @level = level # Element level in the tree (0=top etc...)
24
- @width = 0 # Width of the element in pixels
25
- @indent = 0 # Drawing offset
33
+ @width = 0 # Width of the part of the tree including itself and it governs
34
+ @content_width = 0 # Width of the content
35
+ @horizontal_indent = 0 # Drawing offset
36
+ @vertical_indent = 0 # Drawing offset
26
37
  content = content.strip
27
- if /\A.+\^\z/m =~ content
28
- @content = content.gsub("^"){""} # The actual element content
29
- @triangle = true # draw triangle instead of stright bar when in auto mode
38
+
39
+ if /.+?\^?((?:\+\>?\d+)+)\^?\z/m =~ content
40
+ @path = $1.sub(/\A\+/, "").split("+")
30
41
  else
31
- @content = content.gsub("^"){""}.strip # The actual element content
32
- @triangle = false # draw triangle instead of stright bar when in auto mode
42
+ @path = []
33
43
  end
34
- # workaround to save "[A [B [C] [D] ] [E [F] [G [H] [J] ] ] ]"
44
+
45
+ @fontset = fontset
46
+ @fontsize = fontsize
47
+
48
+ parsed = Markup.parse(content)
49
+
50
+ if parsed[:status] == :success
51
+ results = parsed[:results]
52
+ else
53
+ error_text = "Error: input text contains an invalid string"
54
+ error_text += "\n > " + content
55
+ raise RSTError, error_text
56
+ end
57
+ @content = results[:contents]
58
+ @paths = results[:paths]
59
+ @enclosure = results[:enclosure]
60
+ @triangle = results[:triangle]
61
+
62
+ @contains_phrase = false
63
+ setup
35
64
  end
36
65
 
37
- # Debug helper function
38
- def dump
39
- printf( "ID : %d\n", @id );
40
- printf( "Parent : %d\n", @parent );
41
- printf( "Level : %d\n", @level );
42
- printf( "Type : %d\n", @type );
43
- printf( "Width : %d\n", @width );
44
- printf( "Indent : %d\n", @indent );
45
- printf( "Content : %s\n\n", @content );
66
+ def setup
67
+ total_width = 0
68
+ total_height = 0
69
+ @content.each_with_index do |content, idx|
70
+ content_width = 0
71
+ case content[:type]
72
+ when :border, :bborder
73
+ height = $single_line_height / 2
74
+ content[:height] = height
75
+ total_height += height
76
+ when :text
77
+ row_width = 0
78
+ elements_height = []
79
+ content[:elements].each do |e|
80
+ text = e[:text]
81
+ e[:text] = text.gsub(" ", WHITESPACE_BLOCK).gsub(">", '&#62;').gsub("<", '&#60;')
82
+
83
+
84
+ @contains_phrase = true if text.include?(" ")
85
+ decoration = e[:decoration]
86
+ fontsize = decoration.include?(:small) || decoration.include?(:small) ? @fontsize * SUBSCRIPT_CONST : @fontsize
87
+ fontsize = decoration.include?(:subscript) || decoration.include?(:superscript) ? fontsize * SUBSCRIPT_CONST : fontsize
88
+ style = decoration.include?(:italic) || decoration.include?(:bolditalic) ? :italic : :normal
89
+ weight = decoration.include?(:bold) || decoration.include?(:bolditalic) ? :bold : :normal
90
+
91
+ # e[:cjk] = false
92
+ # if e[:decoration].include?(:math)
93
+ # font = @fontset[:math]
94
+ # elsif text.contains_cjk?
95
+ # font = @fontset[:cjk]
96
+ # e[:cjk] = true
97
+ # elsif decoration.include? :bolditalic
98
+ if decoration.include? :bolditalic
99
+ font = @fontset[:bolditalic]
100
+ elsif decoration.include? :bold
101
+ font = @fontset[:bold]
102
+ elsif decoration.include? :italic
103
+ font = @fontset[:italic]
104
+ else
105
+ font = @fontset[:normal]
106
+ end
107
+
108
+ standard_metrics = FontMetrics.get_metrics('X', @fontset[:normal], fontsize, :normal, :normal)
109
+ height = standard_metrics.height
110
+ if /\A[\<\>]+\z/ =~ text
111
+ width = standard_metrics.width * text.size / 2
112
+ elsif text.contains_emoji?
113
+ segments = text.split_by_emoji
114
+ width = 0
115
+ segments.each do |seg|
116
+ if /\s/ =~ seg[:char]
117
+ ch = 't'
118
+ else
119
+ ch = seg[:char]
120
+ end
121
+ if seg[:type] == :emoji
122
+ this_font = @fontset[:emoji]
123
+ metrics = FontMetrics.get_metrics(ch, this_font, fontsize, style, weight)
124
+ width += metrics.width
125
+ else
126
+ this_font = font
127
+ metrics = FontMetrics.get_metrics(ch, this_font, fontsize, style, weight)
128
+ width += metrics.width
129
+ end
130
+ end
131
+ else
132
+ text.gsub!("\\\\", 'i')
133
+ text.gsub!("\\", "")
134
+ text.gsub!(" ", "x")
135
+ text.gsub!("%", "X")
136
+ metrics = FontMetrics.get_metrics(text, font, fontsize, style, weight)
137
+ width = metrics.width
138
+ end
139
+
140
+ if e[:decoration].include?(:box) || e[:decoration].include?(:circle) || e[:decoration].include?(:bar)
141
+ if e[:text].size == 1
142
+ e[:content_width] = width
143
+ width += (height - width)
144
+ else
145
+ e[:content_width] = width
146
+ width += $width_half_X
147
+ end
148
+ end
149
+
150
+ if e[:decoration].include?(:whitespace)
151
+ width = $width_half_X / 2 * e[:text].size / 4
152
+ e[:text] = ""
153
+ end
154
+
155
+ e[:height] = height
156
+ elements_height << height + $box_vertical_margin / 2
157
+
158
+ e[:width] = width
159
+ row_width += width
160
+ end
161
+
162
+ if @enclosure != :none
163
+ total_height += (elements_height.max + $height_connector_to_text)
164
+ else
165
+ total_height += elements_height.max
166
+ end
167
+ content_width += row_width
168
+ end
169
+ total_width = content_width if total_width < content_width
170
+ end
171
+ @content_width = total_width
172
+ @content_height = total_height
46
173
  end
47
174
 
175
+ def add_child(child_id)
176
+ @children << child_id
177
+ end
178
+ end
48
179
  end
@@ -6,23 +6,25 @@
6
6
  #==========================
7
7
  #
8
8
  # Contains a list of unordered tree elements with a defined parent
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>
15
10
 
16
11
  require 'element'
17
12
 
18
- class ElementList
13
+ module RSyntaxTree
14
+ class ElementList
19
15
 
20
- attr_accessor :elements, :iterator
16
+ attr_accessor :elements, :iterator
21
17
  def initialize
22
18
  @elements = Array.new # The element array
23
19
  @iterator = -1 # Iterator index (used for get_first / get_next)
24
20
  end
25
21
 
22
+ def set_hierarchy
23
+ @elements.each do |e|
24
+ get_id(e.parent).add_child(e.id) unless e.parent == 0
25
+ end
26
+ end
27
+
26
28
  def add(element)
27
29
  @elements << element
28
30
  if(element.parent != 0)
@@ -41,12 +43,12 @@ class ElementList
41
43
  end
42
44
 
43
45
  def get_next
44
- @iterator += 1
45
- if @elements[@iterator]
46
- return @elements[@iterator]
47
- else
48
- return nil
49
- end
46
+ @iterator += 1
47
+ if @elements[@iterator]
48
+ return @elements[@iterator]
49
+ else
50
+ return nil
51
+ end
50
52
  end
51
53
 
52
54
  def get_id(id)
@@ -118,4 +120,5 @@ class ElementList
118
120
  end
119
121
  return maxlevel + 1;
120
122
  end
123
+ end
121
124
  end
@@ -0,0 +1,208 @@
1
+ require 'parslet'
2
+
3
+ class MarkupParser < Parslet::Parser
4
+ rule(:cr) { str('\\n')}
5
+ rule(:eof) { any.absent? }
6
+ rule(:border) { match('[^\-]').absent? >> str('-').repeat(3).as(:border) >> (eof | cr) }
7
+ rule(:bborder) { match('[^=]').absent? >> str('=').repeat(3).as(:bborder) >> (eof | cr) }
8
+
9
+ rule(:brectangle) { str('###') }
10
+ rule(:rectangle) { str('##') }
11
+ rule(:brackets) { str('#') }
12
+ rule(:triangle) { str('^') }
13
+
14
+ rule(:path) { (str('+') >> str('>').maybe >> match('\d').repeat(1)).as(:path) }
15
+ rule(:escaped) { str('\\') >> (match('[#<>{}\\^+*_=~\|\n\-]')).as(:chr) }
16
+ rule(:non_escaped) { ((match('[#<>{}\\^+*_=~\|\-]') | str('\\n')).absent? >> any).as(:chr) }
17
+ rule(:text) { (escaped | non_escaped).repeat(1).as(:text) }
18
+
19
+ rule(:horizontal_bar) { str('--').as(:horizontal_bar) }
20
+ rule(:arrow_both) { str('<->').as(:arrow_both) }
21
+ rule(:arrow_to_r) { str('->').as(:arrow_to_r) }
22
+ rule(:arrow_to_l) { str('<-').as(:arrow_to_l) }
23
+ rule(:empty_circle) { str('{}').as(:empty_circle) }
24
+ rule(:empty_box) { str('||').as(:empty_box) }
25
+ rule(:hatched_circle) { str('{/}').as(:hatched_circle) }
26
+ rule(:hatched_box) { str('|/|').as(:hatched_box) }
27
+ rule(:circle) { str('{') >> (text|decoration).as(:circle) >> str('}')}
28
+ rule(:box) { str('|') >> (text|decoration).as(:box) >> str('|')}
29
+
30
+ rule(:bolditalic) { str('***') >> (text|decoration).as(:bolditalic) >> str('***')}
31
+ rule(:bold) { str('**') >> (text|decoration).as(:bold) >> str('**')}
32
+ rule(:italic) { str('*') >> (text|decoration).as(:italic) >> str('*')}
33
+
34
+ rule(:bstroke) { str('*') >> shape.as(:bstroke) >> str('*')}
35
+
36
+ rule(:overline) { str('=') >> (text|decoration).as(:overline) >> str('=')}
37
+ rule(:underline) { str('-') >> (text|decoration).as(:underline) >> str('-')}
38
+ rule(:linethrough) { str('~') >> (text|decoration).as(:linethrough) >> str('~')}
39
+
40
+ rule(:small) { str('___') >> (text|decoration|shape).as(:small) >> str('___')}
41
+ rule(:superscript) { str('__') >> (text|decoration|shape).as(:superscript) >> str('__')}
42
+ rule(:subscript) { str('_') >> (text|decoration|shape).as(:subscript) >> str('_')}
43
+
44
+ rule(:decoration) {(bolditalic | bold | italic | small | superscript | subscript |
45
+ overline | underline | linethrough) }
46
+
47
+ rule(:shape) {(hatched_circle | hatched_box | empty_circle | empty_box |
48
+ horizontal_bar | arrow_both | arrow_to_l | arrow_to_r |
49
+ circle | box ) }
50
+
51
+ rule(:markup) {(text | decoration | shape | bstroke)}
52
+
53
+ rule(:line) { ( cr.as(:extracr) | border | bborder | markup.repeat(1).as(:line) >> (cr | eof | str('+').present?))}
54
+ rule(:lines) { triangle.maybe.as(:triangle) >>
55
+ (brectangle | rectangle | brackets).maybe.as(:enclosure) >>
56
+ line.repeat(1) >>
57
+ path.repeat(0).as(:paths) >> (cr | eof) }
58
+ root :lines
59
+ end
60
+
61
+ module Markup
62
+ @parser = MarkupParser.new
63
+
64
+ @evaluator = Parslet::Transform.new do
65
+ rule(:chr => simple(:chr)) { chr.to_s }
66
+ rule(:text => sequence(:text)) {{:text => text.join(""), :decoration => []} }
67
+
68
+ rule(:horizontal_bar => subtree(:empty)) {
69
+ {:text => " ", :decoration => [:bar]}
70
+ }
71
+ rule(:arrow_both => subtree(:empty)) {
72
+ {:text => " ", :decoration => [:bar, :arrow_to_l, :arrow_to_r]}
73
+ }
74
+ rule(:arrow_to_l => subtree(:empty)) {
75
+ {:text => " ", :decoration => [:bar, :arrow_to_l]}
76
+ }
77
+ rule(:arrow_to_r => subtree(:empty)) {
78
+ {:text => " ", :decoration => [:bar, :arrow_to_r]}
79
+ }
80
+
81
+ rule(:empty_circle => subtree(:empty)) {
82
+ {:text => " ", :decoration => [:circle]}
83
+ }
84
+ rule(:empty_box => subtree(:empty)) {
85
+ {:text => " ", :decoration => [:box]}
86
+ }
87
+ rule(:hatched_circle => subtree(:empty)) {
88
+ {:text => " ", :decoration => [:hatched, :circle]}
89
+ }
90
+ rule(:hatched_box => subtree(:empty)) {
91
+ {:text => " ", :decoration => [:hatched, :box]}
92
+ }
93
+
94
+ rule(:bolditalic => subtree(:text)) {
95
+ text[:decoration] << :bolditalic; text
96
+ }
97
+ rule(:bold => subtree(:text)) {
98
+ text[:decoration] << :bold; text
99
+ }
100
+ rule(:italic => subtree(:text)) {
101
+ text[:decoration] << :italic; text
102
+ }
103
+
104
+ rule(:bstroke => subtree(:box)) {
105
+ box[:decoration] << :bstroke; box
106
+ }
107
+ rule(:bstroke => subtree(:circle)) {
108
+ circle[:decoration] << :bstroke; circle
109
+ }
110
+ rule(:bstroke => subtree(:horizontal_bar)) {
111
+ horizontal_bar[:decoration] << :bstroke; horizontal_bar
112
+ }
113
+ rule(:bstroke => subtree(:empty_circle)) {
114
+ empty_circle[:decoration] << :bstroke; empty_circle
115
+ }
116
+ rule(:bstroke => subtree(:empty_box)) {
117
+ empty_box[:decoration] << :bstroke; empty_box
118
+ }
119
+ rule(:bstroke => subtree(:hatched_circle)) {
120
+ hatched_circle[:decoration] << :bstroke; hatched_circle
121
+ }
122
+ rule(:bstroke => subtree(:hatched_box)) {
123
+ hatched_box[:decoration] << :bstroke; hatched_box
124
+ }
125
+
126
+ rule(:overline => subtree(:text)) {
127
+ text[:decoration] << :overline; text
128
+ }
129
+ rule(:underline => subtree(:text)) {
130
+ text[:decoration] << :underline; text
131
+ }
132
+ rule(:linethrough => subtree(:text)) {
133
+ text[:decoration] << :linethrough; text
134
+ }
135
+ rule(:subscript => subtree(:text)) {
136
+ text[:decoration] << :subscript; text
137
+ }
138
+ rule(:superscript => subtree(:text)) {
139
+ text[:decoration] << :superscript; text
140
+ }
141
+ rule(:small => subtree(:text)) {
142
+ text[:decoration] << :small; text
143
+ }
144
+ rule(:box => subtree(:text)) {
145
+ text[:decoration] << :box; text
146
+ }
147
+ rule(:circle => subtree(:text)) {
148
+ text[:decoration] << :circle; text
149
+ }
150
+ rule(:math => subtree(:text)) {
151
+ text[:decoration] << :math; text
152
+ }
153
+ rule(:border => simple(:border)) {
154
+ {:type => :border}
155
+ }
156
+ rule(:bborder => simple(:bborder)) {
157
+ {:type => :bborder}
158
+ }
159
+ rule(:line => subtree(:line)) {
160
+ {:type => :text, :elements => line }
161
+ }
162
+ rule(:extracr => subtree(:extracr)) {
163
+ {:type => :text, :elements=>[{:text=>" ", :decoration=>[]}]}
164
+ }
165
+
166
+ end
167
+
168
+ def parse(txt)
169
+ begin
170
+ parsed = @parser.parse(txt)
171
+ rescue Parslet::ParseFailed => e
172
+ # puts e.parse_failure_cause.ascii_tree
173
+ return {:status => :error, :text => txt}
174
+ end
175
+
176
+ applied = @evaluator.apply(parsed)
177
+
178
+ results = {:enclosure => :none, :triangle => false, :paths => [], :contents => []}
179
+ applied.each do |h|
180
+ if h[:enclosure]
181
+ if h[:enclosure].to_s == '###'
182
+ results[:enclosure] = :brectangle
183
+ elsif h[:enclosure].to_s == '##'
184
+ results[:enclosure] = :rectangle
185
+ elsif h[:enclosure].to_s == '#'
186
+ results[:enclosure] = :brackets
187
+ else
188
+ results[:enclosure] = :none
189
+ end
190
+ end
191
+ if h[:triangle]
192
+ results[:triangle] = h[:triangle].to_s == '^' ? true : false
193
+ end
194
+ if h[:paths]
195
+ results[:paths] = h[:paths]
196
+ end
197
+ if h[:type] == :text || h[:type] == :border || h[:type] == :bborder
198
+ results[:contents] << h
199
+ end
200
+ end
201
+ result = {:status => :success, :results => results}
202
+ result
203
+ end
204
+
205
+ module_function :parse
206
+ end
207
+
208
+ # pp results = Markup.parse('^#\_\#\+あり\-がとう**_X_**\\n----\\n933\\n__|Y|__+>3+2+1343+>5464')