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.
@@ -6,43 +6,173 @@
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
+ metrics = FontMetrics.get_metrics(text, font, fontsize, style, weight)
136
+ width = metrics.width
137
+ end
138
+
139
+ if e[:decoration].include?(:box) || e[:decoration].include?(:circle) || e[:decoration].include?(:bar)
140
+ if e[:text].size == 1
141
+ e[:content_width] = width
142
+ width += (height - width)
143
+ else
144
+ e[:content_width] = width
145
+ width += $width_half_X
146
+ end
147
+ end
148
+
149
+ if e[:decoration].include?(:whitespace)
150
+ width = $width_half_X / 2 * e[:text].size / 4
151
+ e[:text] = ""
152
+ end
153
+
154
+ e[:height] = height
155
+ elements_height << height + $box_vertical_margin / 2
156
+
157
+ e[:width] = width
158
+ row_width += width
159
+ end
160
+
161
+ if @enclosure != :none
162
+ total_height += (elements_height.max + $height_connector_to_text)
163
+ else
164
+ total_height += elements_height.max
165
+ end
166
+ content_width += row_width
167
+ end
168
+ total_width = content_width if total_width < content_width
169
+ end
170
+ @content_width = total_width
171
+ @content_height = total_height
46
172
  end
47
173
 
174
+ def add_child(child_id)
175
+ @children << child_id
176
+ end
177
+ end
48
178
  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')