html2doc 0.8.6 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1e07932ce2329645bb69ae3c9c5f401d229d3080be417428e5aa36b900d9f6c
4
- data.tar.gz: 9e916f0a465fa1e11d693fcfc04197f7fcfae0b423da255bb3c4cdc3a02a61f7
3
+ metadata.gz: 8b4a4257cfea9d3621715b98d906f8eb0ffdc7ce49f38b732dc2aada2e7bf122
4
+ data.tar.gz: a767085867c7dc1eb105e90eeb221ba4c89383dad79a61615df7e03615f890fb
5
5
  SHA512:
6
- metadata.gz: 3bad513cc61d42db8d5bd24b7e0b0eaf66c631d40d39128817549a84e8c8d3f9fbd917b5a1fff949d57604981833f64579e9e630ead7366feb1694510bb42591
7
- data.tar.gz: 6733ac123a379ca70712acdc39f32048ee6220f4b58e894af93df87c84defb382ffec978fc6cc084f6fb1b040da3fe5dffdc111c237d4d6afce4cf397a42144e
6
+ metadata.gz: 0d47018c5d6c3d9432f860485994462cfc6634d487de4a72375df7974b9af51d0409c5ac0bdc1ffcac585c51fc329b5d39cb4c1e6810821ba6489dfbd4b4e557
7
+ data.tar.gz: 5067f79bb2dca4836021d960d807a9ec81a97b5ec3545588fe60120e98d2d46f26665f7a5ac0c6ffd5bed50e7f858017f1fd6677edd8e10958240afae11c23bc
@@ -3,13 +3,8 @@
3
3
  # All project-specific additions and overrides should be specified in this file.
4
4
 
5
5
  inherit_from:
6
- # Thoughtbot's style guide from: https://github.com/thoughtbot/guides
7
- - ".rubocop.tb.yml"
8
- # Overrides from Ribose
9
- - ".rubocop.ribose.yml"
6
+ - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
10
7
  AllCops:
11
- DisplayCopNames: false
12
- StyleGuideCopsOnly: false
13
- TargetRubyVersion: 2.4
8
+ TargetRubyVersion: 2.3
14
9
  Rails:
15
10
  Enabled: true
data/Gemfile CHANGED
@@ -3,8 +3,7 @@ source "https://rubygems.org"
3
3
  group :development, :test do
4
4
  gem "rspec"
5
5
  end
6
-
7
-
6
+
8
7
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
9
8
 
10
9
  # Specify your gem's dependencies in html2doc.gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- html2doc (0.8.6)
4
+ html2doc (0.8.7)
5
5
  asciimath
6
6
  htmlentities (~> 4.3.4)
7
7
  image_size
@@ -14,7 +14,7 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- asciimath (1.0.6)
17
+ asciimath (1.0.7)
18
18
  ast (2.4.0)
19
19
  byebug (9.1.0)
20
20
  coderay (1.1.2)
@@ -22,9 +22,9 @@ GEM
22
22
  docile (1.3.1)
23
23
  equivalent-xml (0.6.0)
24
24
  nokogiri (>= 1.4.3)
25
- ffi (1.9.25)
25
+ ffi (1.10.0)
26
26
  formatador (0.2.5)
27
- guard (2.14.2)
27
+ guard (2.15.0)
28
28
  formatador (>= 0.2.4)
29
29
  listen (>= 2.7, < 4.0)
30
30
  lumberjack (>= 1.0.12, < 2.0)
@@ -40,21 +40,20 @@ GEM
40
40
  rspec (>= 2.99.0, < 4.0)
41
41
  htmlentities (4.3.4)
42
42
  image_size (2.0.0)
43
- jaro_winkler (1.5.1)
44
43
  json (2.1.0)
45
44
  listen (3.1.5)
46
45
  rb-fsevent (~> 0.9, >= 0.9.4)
47
46
  rb-inotify (~> 0.9, >= 0.9.7)
48
47
  ruby_dep (~> 1.2)
49
48
  lumberjack (1.0.13)
50
- method_source (0.9.0)
49
+ method_source (0.9.2)
51
50
  mime-types (3.2.2)
52
51
  mime-types-data (~> 3.2015)
53
52
  mime-types-data (3.2018.0812)
54
- mini_portile2 (2.3.0)
53
+ mini_portile2 (2.4.0)
55
54
  nenv (0.3.0)
56
- nokogiri (1.8.5)
57
- mini_portile2 (~> 2.3.0)
55
+ nokogiri (1.10.0)
56
+ mini_portile2 (~> 2.4.0)
58
57
  notiffany (0.1.1)
59
58
  nenv (~> 0.1)
60
59
  shellany (~> 0.0)
@@ -62,14 +61,14 @@ GEM
62
61
  parser (2.5.3.0)
63
62
  ast (~> 2.4.0)
64
63
  powerpack (0.1.2)
65
- pry (0.11.3)
64
+ pry (0.12.2)
66
65
  coderay (~> 1.1.0)
67
66
  method_source (~> 0.9.0)
68
67
  rainbow (3.0.0)
69
- rake (12.3.1)
68
+ rake (12.3.2)
70
69
  rb-fsevent (0.10.3)
71
- rb-inotify (0.9.10)
72
- ffi (>= 0.5.0, < 2)
70
+ rb-inotify (0.10.0)
71
+ ffi (~> 1.0)
73
72
  rspec (3.8.0)
74
73
  rspec-core (~> 3.8.0)
75
74
  rspec-expectations (~> 3.8.0)
@@ -85,14 +84,13 @@ GEM
85
84
  diff-lcs (>= 1.2.0, < 2.0)
86
85
  rspec-support (~> 3.8.0)
87
86
  rspec-support (3.8.0)
88
- rubocop (0.60.0)
89
- jaro_winkler (~> 1.5.1)
87
+ rubocop (0.54.0)
90
88
  parallel (~> 1.10)
91
- parser (>= 2.5, != 2.5.1.1)
89
+ parser (>= 2.5)
92
90
  powerpack (~> 0.1)
93
91
  rainbow (>= 2.2.2, < 4.0)
94
92
  ruby-progressbar (~> 1.7)
95
- unicode-display_width (~> 1.4.0)
93
+ unicode-display_width (~> 1.0, >= 1.0.1)
96
94
  ruby-progressbar (1.10.0)
97
95
  ruby-xslt (0.9.10)
98
96
  ruby_dep (1.5.0)
@@ -102,17 +100,17 @@ GEM
102
100
  json (>= 1.8, < 3)
103
101
  simplecov-html (~> 0.10.0)
104
102
  simplecov-html (0.10.2)
105
- thor (0.20.0)
103
+ thor (0.20.3)
106
104
  thread_safe (0.3.6)
107
105
  timecop (0.9.1)
108
- unicode-display_width (1.4.0)
106
+ unicode-display_width (1.4.1)
109
107
  uuidtools (2.1.5)
110
108
 
111
109
  PLATFORMS
112
110
  ruby
113
111
 
114
112
  DEPENDENCIES
115
- bundler (~> 1.15)
113
+ bundler (~> 2.0.1)
116
114
  byebug (~> 9.1)
117
115
  equivalent-xml (~> 0.6)
118
116
  guard (~> 2.14)
@@ -121,9 +119,9 @@ DEPENDENCIES
121
119
  rake (~> 12.0)
122
120
  rspec
123
121
  rspec-match_fuzzy (~> 0.1.3)
124
- rubocop (~> 0.50)
122
+ rubocop (= 0.54.0)
125
123
  simplecov (~> 0.15)
126
124
  timecop (~> 0.9)
127
125
 
128
126
  BUNDLED WITH
129
- 1.17.1
127
+ 2.0.1
@@ -34,14 +34,14 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "ruby-xslt"
35
35
  spec.add_dependency "asciimath"
36
36
 
37
- spec.add_development_dependency "bundler", "~> 1.15"
37
+ spec.add_development_dependency "bundler", "~> 2.0.1"
38
38
  spec.add_development_dependency "byebug", "~> 9.1"
39
39
  spec.add_development_dependency "equivalent-xml", "~> 0.6"
40
40
  spec.add_development_dependency "guard", "~> 2.14"
41
41
  spec.add_development_dependency "guard-rspec", "~> 4.7"
42
42
  spec.add_development_dependency "rake", "~> 12.0"
43
43
  spec.add_development_dependency "rspec", "~> 3.6"
44
- spec.add_development_dependency "rubocop", "~> 0.50"
44
+ spec.add_development_dependency "rubocop", "= 0.54.0"
45
45
  spec.add_development_dependency "simplecov", "~> 0.15"
46
46
  spec.add_development_dependency "timecop", "~> 0.9"
47
47
  spec.add_development_dependency "rspec-match_fuzzy", "~> 0.1.3"
@@ -0,0 +1,18 @@
1
+ require_relative 'parser'
2
+ require_relative 'mathml'
3
+ require_relative 'html'
4
+
5
+ module AsciiMath
6
+ module CLI
7
+ def self.run(args)
8
+ asciimath = args.last
9
+ output = ''
10
+ if args.length == 1 || args.first == "mathml"
11
+ output = AsciiMath.parse(asciimath).to_mathml
12
+ elsif args.first == "html"
13
+ output = AsciiMath.parse(asciimath).to_html
14
+ end
15
+ puts output
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,222 @@
1
+ module AsciiMath
2
+ class HTMLBuilder
3
+ def initialize(prefix)
4
+ @prefix = prefix
5
+ @html = ''
6
+ end
7
+
8
+ def to_s
9
+ @html
10
+ end
11
+
12
+ def append_expression(expression, inline, attrs = {})
13
+ if inline
14
+ inline('', attrs) do
15
+ append(expression, :single_child => true)
16
+ end
17
+ else
18
+ block('', attrs) do
19
+ append(expression, :single_child => true)
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ ZWJ = "\u8205"
27
+
28
+ def append(expression, opts = {})
29
+ case expression
30
+ when Array
31
+ row do
32
+ expression.each { |e| append(e) }
33
+ end
34
+ when Hash
35
+ case expression[:type]
36
+ when :operator
37
+ operator(expression[:c])
38
+ when :identifier
39
+ identifier(expression[:c])
40
+ when :number
41
+ number(expression[:c])
42
+ when :text
43
+ text(expression[:c])
44
+ when :paren
45
+ paren = !opts[:strip_paren]
46
+ if paren
47
+ if opts[:single_child]
48
+ brace(expression[:lparen]) if expression[:lparen]
49
+ append(expression[:e], :single_child => true)
50
+ brace(expression[:rparen]) if expression[:rparen]
51
+ else
52
+ row do
53
+ brace(expression[:lparen]) if expression[:lparen]
54
+ append(expression[:e], :single_child => true)
55
+ brace(expression[:rparen]) if expression[:rparen]
56
+ end
57
+ end
58
+ else
59
+ append(expression[:e])
60
+ end
61
+ when :font
62
+ #TODO - currently ignored
63
+ when :unary
64
+ operator = expression[:operator]
65
+ tag(operator) do
66
+ append(expression[:s], :single_child => true, :strip_paren => true)
67
+ end
68
+ when :binary
69
+ operator = expression[:operator]
70
+ if operator == :frac
71
+ append_fraction(expression[:s1],expression[:s2])
72
+ elsif operator == :sub
73
+ append_subsup(expression[:s1],expression[:s2],nil)
74
+ elsif operator == :sup
75
+ append_subsup(expression[:s1],nil,expression[:s2])
76
+ elsif operator == :under
77
+ append_underover(expression[:s1],expression[:s2],nil)
78
+ elsif operator == :over
79
+ append_underover(expression[:s1],nil,expression[:s2])
80
+ else
81
+ tag(operator) do
82
+ append(expression[:s1], :strip_paren => true)
83
+ append(expression[:s2], :strip_paren => true)
84
+ end
85
+ end
86
+ when :ternary
87
+ operator = expression[:operator]
88
+ if operator == :subsup
89
+ append_subsup(expression[:s1],expression[:s2],expression[:s3])
90
+ elsif operator == :underover
91
+ # TODO: Handle over/under braces in some way? SVG maybe?
92
+ append_underover(expression[:s1],expression[:s2],expression[:s3])
93
+ end
94
+ when :matrix
95
+ row do
96
+ # Figures out a font size for the braces, based on the height of the matrix.
97
+ # NOTE: This does not currently consider the size of each element within the matrix.
98
+ brace_height = "font-size: " + expression[:rows].length.to_s + "00%;"
99
+
100
+ if expression[:lparen]
101
+ brace(expression[:lparen], {:style => brace_height})
102
+ else
103
+ blank(ZWJ)
104
+ end
105
+ matrix_width = "grid-template-columns:repeat(" + expression[:rows][0].length.to_s + ",1fr);"
106
+ matrix_height = "grid-template-rows:repeat(" + expression[:rows].length.to_s + ",1fr);"
107
+
108
+ matrix({:style => (matrix_width + matrix_height)}) do
109
+ expression[:rows].each do |row|
110
+ row.each do |col|
111
+ row do
112
+ append(col)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ if expression[:rparen]
118
+ brace(expression[:rparen], {:style => brace_height})
119
+ else
120
+ blank(ZWJ)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def append_subsup(base, sub, sup)
128
+ append(base)
129
+ subsup do
130
+ if sup
131
+ smaller do
132
+ append(sup, :strip_paren => true)
133
+ end
134
+ else
135
+ smaller(ZWJ)
136
+ end
137
+ if sub
138
+ smaller do
139
+ append(sub, :strip_paren => true)
140
+ end
141
+ else
142
+ smaller(ZWJ)
143
+ end
144
+ end
145
+ end
146
+
147
+ def append_underover(base, under, over)
148
+ blank(ZWJ)
149
+ underover do
150
+ smaller do
151
+ if over
152
+ append(over, :strip_paren => true)
153
+ else
154
+ blank(ZWJ)
155
+ end
156
+ end
157
+ append(base)
158
+ smaller do
159
+ if under
160
+ append(under, :strip_paren => true)
161
+ else
162
+ blank(ZWJ)
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ def append_fraction(numerator, denominator)
169
+ blank(ZWJ)
170
+ fraction do
171
+ fraction_row do
172
+ fraction_cell do
173
+ smaller do
174
+ row do
175
+ append(numerator, :strip_paren => true)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ fraction_row do
181
+ fraction_cell do
182
+ smaller do
183
+ row do
184
+ append(denominator, :strip_paren => true)
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def method_missing(meth, *args, &block)
193
+ tag(meth, *args, &block)
194
+ end
195
+
196
+ def tag(tag, *args)
197
+ attrs = args.last.is_a?(Hash) ? args.pop : {}
198
+ text = args.last.is_a?(String) ? args.pop : ''
199
+
200
+ @html << '<span class="math-' << @prefix << tag.to_s << '"'
201
+
202
+ attrs.each_pair do |key, value|
203
+ @html << ' ' << key.to_s << '="' << value.to_s << '"'
204
+ end
205
+
206
+ if block_given? || text
207
+ @html << '>'
208
+ @html << text.encode(Encoding::US_ASCII, :xml => :text) if text
209
+ yield if block_given?
210
+ @html << '</span>'
211
+ else
212
+ @html << '/>'
213
+ end
214
+ end
215
+ end
216
+
217
+ class Expression
218
+ def to_html(prefix = "", inline = true, attrs = {})
219
+ HTMLBuilder.new(prefix).append_expression(@parsed_expression, inline, attrs).to_s
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,131 @@
1
+ module AsciiMath
2
+ class MathMLBuilder
3
+ def initialize(prefix)
4
+ @prefix = prefix
5
+ @mathml = ''
6
+ end
7
+
8
+ def to_s
9
+ @mathml
10
+ end
11
+
12
+ def append_expression(expression, attrs = {})
13
+ math('', attrs) do
14
+ append(expression, :single_child => true)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def append(expression, opts = {})
21
+ case expression
22
+ when Array
23
+ if expression.length <= 1 || opts[:single_child]
24
+ expression.each { |e| append(e) }
25
+ else
26
+ mrow do
27
+ expression.each { |e| append(e) }
28
+ end
29
+ end
30
+ when Hash
31
+ case expression[:type]
32
+ when :operator
33
+ mo(expression[:c])
34
+ when :identifier
35
+ mi(expression[:c])
36
+ when :number
37
+ mn(expression[:c])
38
+ when :text
39
+ mtext(expression[:c])
40
+ when :paren
41
+ paren = !opts[:strip_paren]
42
+ if paren
43
+ if opts[:single_child]
44
+ mo(expression[:lparen]) if expression[:lparen]
45
+ append(expression[:e], :single_child => true)
46
+ mo(expression[:rparen]) if expression[:rparen]
47
+ else
48
+ mrow do
49
+ mo(expression[:lparen]) if expression[:lparen]
50
+ append(expression[:e], :single_child => true)
51
+ mo(expression[:rparen]) if expression[:rparen]
52
+ end
53
+ end
54
+ else
55
+ append(expression[:e])
56
+ end
57
+ when :font
58
+ style = expression[:operator]
59
+ tag("mstyle", :mathvariant => style.to_s.gsub('_', '-')) do
60
+ append(expression[:s], :single_child => true, :strip_paren => true)
61
+ end
62
+ when :unary
63
+ operator = expression[:operator]
64
+ tag("m#{operator}") do
65
+ append(expression[:s], :single_child => true, :strip_paren => true)
66
+ end
67
+ when :binary
68
+ operator = expression[:operator]
69
+ tag("m#{operator}") do
70
+ append(expression[:s1], :strip_paren => (operator != :sub && operator != :sup))
71
+ append(expression[:s2], :strip_paren => true)
72
+ end
73
+ when :ternary
74
+ operator = expression[:operator]
75
+ tag("m#{operator}") do
76
+ append(expression[:s1])
77
+ append(expression[:s2], :strip_paren => true)
78
+ append(expression[:s3], :strip_paren => true)
79
+ end
80
+ when :matrix
81
+ mrow do
82
+ mo(expression[:lparen]) if expression[:lparen]
83
+ mtable do
84
+ expression[:rows].each do |row|
85
+ mtr do
86
+ row.each do |col|
87
+ mtd do
88
+ append(col)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ mo(expression[:rparen]) if expression[:rparen]
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def method_missing(meth, *args, &block)
101
+ tag(meth, *args, &block)
102
+ end
103
+
104
+ def tag(tag, *args)
105
+ attrs = args.last.is_a?(Hash) ? args.pop : {}
106
+ text = args.last.is_a?(String) ? args.pop : ''
107
+
108
+ @mathml << '<' << @prefix << tag.to_s
109
+
110
+ attrs.each_pair do |key, value|
111
+ @mathml << ' ' << key.to_s << '="' << value.to_s << '"'
112
+ end
113
+
114
+
115
+ if block_given? || text
116
+ @mathml << '>'
117
+ @mathml << text.encode(Encoding::US_ASCII, :xml => :text) if text
118
+ yield self if block_given?
119
+ @mathml << '</' << @prefix << tag.to_s << '>'
120
+ else
121
+ @mathml << '/>'
122
+ end
123
+ end
124
+ end
125
+
126
+ class Expression
127
+ def to_mathml(prefix = "", attrs = {})
128
+ MathMLBuilder.new(prefix).append_expression(@parsed_expression, attrs).to_s
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,591 @@
1
+ require 'strscan'
2
+
3
+ # Parser for ASCIIMath expressions.
4
+ #
5
+ # The syntax for ASCIIMath in EBNF style notation is
6
+ #
7
+ # expr = ( simp ( fraction | sub | super ) )+
8
+ # simp = constant | paren_expr | unary_expr | binary_expr | text
9
+ # fraction = '/' simp
10
+ # super = '^' simp
11
+ # sub = '_' simp super?
12
+ # paren_expr = lparen expr rparen
13
+ # lparen = '(' | '[' | '{' | '(:' | '{:'
14
+ # rparen = ')' | ']' | '}' | ':)' | ':}'
15
+ # unary_expr = unary_op simp
16
+ # unary_op = 'sqrt' | 'text'
17
+ # binary_expr = binary_op simp simp
18
+ # binary_op = 'frac' | 'root' | 'stackrel'
19
+ # text = '"' [^"]* '"'
20
+ # constant = number | symbol | identifier
21
+ # number = '-'? [0-9]+ ( '.' [0-9]+ )?
22
+ # symbol = /* any string in the symbol table */
23
+ # identifier = [A-z]
24
+ #
25
+ # ASCIIMath is parsed left to right without any form of operator precedence.
26
+ # When parsing the 'constant' the parser will try to find the longest matching string in the symbol
27
+ # table starting at the current position of the parser. If no matching string can be found the
28
+ # character at the current position of the parser is interpreted as an identifier instead.
29
+ module AsciiMath
30
+ # Internal: Splits an ASCIIMath expression into a sequence of tokens.
31
+ # Each token is represented as a Hash containing the keys :value and :type.
32
+ # The :value key is used to store the text associated with each token.
33
+ # The :type key indicates the semantics of the token. The value for :type will be one
34
+ # of the following symbols:
35
+ #
36
+ # - :identifier a symbolic name or a bit of text without any further semantics
37
+ # - :text a bit of arbitrary text
38
+ # - :number a number
39
+ # - :operator a mathematical operator symbol
40
+ # - :unary a unary operator (e.g., sqrt, text, ...)
41
+ # - :font a unary font command (e.g., bb, cc, ...)
42
+ # - :infix an infix operator (e.g, /, _, ^, ...)
43
+ # - :binary a binary operator (e.g., frac, root, ...)
44
+ # - :accent an accent character
45
+ # - :eof indicates no more tokens are available
46
+ #
47
+ # Each token type may also have an :underover modifier. When present and set to true
48
+ # sub- and superscript expressions associated with the token will be rendered as
49
+ # under- and overscriptabove and below rather than as sub- or superscript.
50
+ #
51
+ # :accent tokens additionally have a :postion value which is set to either :over or :under.
52
+ # This determines if the accent should be rendered over or under the expression to which
53
+ # it applies.
54
+ #
55
+ class Tokenizer
56
+ WHITESPACE = /^\s+/
57
+ NUMBER = /-?[0-9]+(?:\.[0-9]+)?/
58
+ QUOTED_TEXT = /"[^"]*"/
59
+ TEX_TEXT = /text\([^)]*\)/
60
+
61
+ # Public: Initializes an ASCIIMath tokenizer.
62
+ #
63
+ # string - The ASCIIMath expression to tokenize
64
+ # symbols - The symbol table to use while tokenizing
65
+ def initialize(string, symbols)
66
+ @string = StringScanner.new(string)
67
+ @symbols = symbols
68
+ lookahead = @symbols.keys.map { |k| k.length }.max
69
+ @symbol_regexp = /([^\s0-9]{1,#{lookahead}})/
70
+ @push_back = nil
71
+ end
72
+
73
+ # Public: Read the next token from the ASCIIMath expression and move the tokenizer
74
+ # ahead by one token.
75
+ #
76
+ # Returns the next token as a Hash
77
+ def next_token
78
+ if @push_back
79
+ t = @push_back
80
+ @push_back = nil
81
+ return t
82
+ end
83
+
84
+ @string.scan(WHITESPACE)
85
+
86
+ return {:value => nil, :type => :eof} if @string.eos?
87
+
88
+ case @string.peek(1)
89
+ when '"'
90
+ read_quoted_text
91
+ when 't'
92
+ case @string.peek(5)
93
+ when 'text('
94
+ read_tex_text
95
+ else
96
+ read_symbol
97
+ end
98
+ when '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
99
+ read_number || read_symbol
100
+ else
101
+ read_symbol
102
+ end
103
+ end
104
+
105
+ # Public: Pushes the given token back to the tokenizer. A subsequent call to next_token
106
+ # will return the given token rather than generating a new one. At most one
107
+ # token can be pushed back.
108
+ #
109
+ # token - The token to push back
110
+ def push_back(token)
111
+ @push_back = token unless token[:type] == :eof
112
+ end
113
+
114
+ private
115
+
116
+ # Private: Reads a text token from the input string
117
+ #
118
+ # Returns the text token or nil if a text token could not be matched at
119
+ # the current position
120
+ def read_quoted_text
121
+ read_value(QUOTED_TEXT) do |text|
122
+ {:value => text[1..-2], :type => :text}
123
+ end
124
+ end
125
+
126
+ # Private: Reads a text token from the input string
127
+ #
128
+ # Returns the text token or nil if a text token could not be matched at
129
+ # the current position
130
+ def read_tex_text
131
+ read_value(TEX_TEXT) do |text|
132
+ {:value => text[5..-2], :type => :text}
133
+ end
134
+ end
135
+
136
+ # Private: Reads a number token from the input string
137
+ #
138
+ # Returns the number token or nil if a number token could not be matched at
139
+ # the current position
140
+ def read_number
141
+ read_value(NUMBER) do |number|
142
+ {:value => number, :type => :number}
143
+ end
144
+ end
145
+
146
+ if String.method_defined?(:bytesize)
147
+ def bytesize(s)
148
+ s.bytesize
149
+ end
150
+ else
151
+ def bytesize(s)
152
+ s.length
153
+ end
154
+ end
155
+
156
+
157
+ # Private: Reads a symbol token from the input string. This method first creates
158
+ # a String from the input String starting from the current position with a length
159
+ # that matches that of the longest key in the symbol table. It then looks up that
160
+ # substring in the symbol table. If the substring is present in the symbol table, the
161
+ # associated value is returned and the position is moved ahead by the length of the
162
+ # substring. Otherwise this method chops one character off the end of the substring
163
+ # and repeats the symbol lookup. This continues until a single character is left.
164
+ # If that character can still not be found in the symbol table, then an identifier
165
+ # token is returned whose value is the remaining single character string.
166
+ #
167
+ # Returns the token that was read or nil if a token could not be matched at
168
+ # the current position
169
+ def read_symbol
170
+ position = @string.pos
171
+ read_value(@symbol_regexp) do |s|
172
+ until s.length == 1 || @symbols.include?(s)
173
+ s.chop!
174
+ end
175
+ @string.pos = position + bytesize(s)
176
+ @symbols[s] || {:value => s, :type => :identifier}
177
+ end
178
+ end
179
+
180
+ # Private: Reads a String from the input String that matches the given RegExp
181
+ #
182
+ # regexp - a RegExp that will be used to match the token
183
+ # block - if a block is provided the matched token will be passed to the block
184
+ #
185
+ # Returns the matched String or the value returned by the block if one was given
186
+ def read_value(regexp)
187
+ s = @string.scan(regexp)
188
+ if s
189
+ yield s
190
+ else
191
+ s
192
+ end
193
+ end
194
+
195
+ if String.respond_to?(:byte_size)
196
+ def byte_size(s)
197
+ s.byte_size
198
+ end
199
+ end
200
+ end
201
+
202
+ class Parser
203
+ SYMBOLS = {
204
+ # Operation symbols
205
+ '+' => {:value => '+', :type => :operator},
206
+ '-' => {:value => "\u2212", :type => :operator},
207
+ '*' => {:value => "\u22C5", :type => :operator},
208
+ '**' => {:value => "\u002A", :type => :operator},
209
+ '***' => {:value => "\u22C6", :type => :operator},
210
+ '//' => {:value => '/', :type => :operator},
211
+ '\\\\' => {:value => '\\', :type => :operator},
212
+ 'xx' => {:value => "\u00D7", :type => :operator},
213
+ '-:' => {:value => "\u00F7", :type => :operator},
214
+ '|><' => {:value => "\u22C9", :type => :operator},
215
+ '><|' => {:value => "\u22CA", :type => :operator},
216
+ '|><|' => {:value => "\u22C8", :type => :operator},
217
+ '@' => {:value => "\u26AC", :type => :operator},
218
+ 'o+' => {:value => "\u2295", :type => :operator},
219
+ 'ox' => {:value => "\u2297", :type => :operator},
220
+ 'o.' => {:value => "\u2299", :type => :operator},
221
+ 'sum' => {:value => "\u2211", :type => :operator, :underover => true},
222
+ 'prod' => {:value => "\u220F", :type => :operator, :underover => true},
223
+ '^^' => {:value => "\u2227", :type => :operator},
224
+ '^^^' => {:value => "\u22C0", :type => :operator, :underover => true},
225
+ 'vv' => {:value => "\u2228", :type => :operator},
226
+ 'vvv' => {:value => "\u22C1", :type => :operator, :underover => true},
227
+ 'nn' => {:value => "\u2229", :type => :operator},
228
+ 'nnn' => {:value => "\u22C2", :type => :operator, :underover => true},
229
+ 'uu' => {:value => "\u222A", :type => :operator},
230
+ 'uuu' => {:value => "\u22C3", :type => :operator, :underover => true},
231
+
232
+ # Relation symbols
233
+ '=' => {:value => '=', :type => :operator},
234
+ '!=' => {:value => "\u2260", :type => :operator},
235
+ ':=' => {:value => ':=', :type => :operator},
236
+ '<' => {:value => "\u003C", :type => :operator},
237
+ 'lt' => {:value => "\u003C", :type => :operator},
238
+ '>' => {:value => "\u003E", :type => :operator},
239
+ 'gt' => {:value => "\u003E", :type => :operator},
240
+ '<=' => {:value => "\u2264", :type => :operator},
241
+ 'le' => {:value => "\u2264", :type => :operator},
242
+ '>=' => {:value => "\u2265", :type => :operator},
243
+ 'ge' => {:value => "\u2265", :type => :operator},
244
+ '-<' => {:value => "\u227A", :type => :operator},
245
+ '>-' => {:value => "\u227B", :type => :operator},
246
+ '-<=' => {:value => "\u2AAF", :type => :operator},
247
+ '>-=' => {:value => "\u2AB0", :type => :operator},
248
+ 'in' => {:value => "\u2208", :type => :operator},
249
+ '!in' => {:value => "\u2209", :type => :operator},
250
+ 'sub' => {:value => "\u2282", :type => :operator},
251
+ 'sup' => {:value => "\u2283", :type => :operator},
252
+ 'sube' => {:value => "\u2286", :type => :operator},
253
+ 'supe' => {:value => "\u2287", :type => :operator},
254
+ '-=' => {:value => "\u2261", :type => :operator},
255
+ '~=' => {:value => "\u2245", :type => :operator},
256
+ '~~' => {:value => "\u2248", :type => :operator},
257
+ 'prop' => {:value => "\u221D", :type => :operator},
258
+
259
+ # Logical symbols
260
+ 'and' => {:value => 'and', :type => :text},
261
+ 'or' => {:value => 'or', :type => :text},
262
+ 'not' => {:value => "\u00AC", :type => :operator},
263
+ '=>' => {:value => "\u21D2", :type => :operator},
264
+ 'if' => {:value => 'if', :type => :operator},
265
+ '<=>' => {:value => "\u21D4", :type => :operator},
266
+ 'AA' => {:value => "\u2200", :type => :operator},
267
+ 'EE' => {:value => "\u2203", :type => :operator},
268
+ '_|_' => {:value => "\u22A5", :type => :operator},
269
+ 'TT' => {:value => "\u22A4", :type => :operator},
270
+ '|--' => {:value => "\u22A2", :type => :operator},
271
+ '|==' => {:value => "\u22A8", :type => :operator},
272
+
273
+ # Grouping brackets
274
+ '(' => {:value => '(', :type => :lparen},
275
+ ')' => {:value => ')', :type => :rparen},
276
+ '[' => {:value => '[', :type => :lparen},
277
+ ']' => {:value => ']', :type => :rparen},
278
+ '{' => {:value => '{', :type => :lparen},
279
+ '}' => {:value => '}', :type => :rparen},
280
+ '(:' => {:value => "\u2329", :type => :lparen},
281
+ ':)' => {:value => "\u232A", :type => :rparen},
282
+ '<<' => {:value => "\u2329", :type => :lparen},
283
+ '>>' => {:value => "\u232A", :type => :rparen},
284
+ '|' => {:value => '|', :type => :lrparen},
285
+ '||' => {:value => '||', :type => :lrparen},
286
+ '{:' => {:value => nil, :type => :lparen},
287
+ ':}' => {:value => nil, :type => :rparen},
288
+
289
+ # Miscellaneous symbols
290
+ 'int' => {:value => "\u222B", :type => :operator},
291
+ 'dx' => {:value => 'dx', :type => :identifier},
292
+ 'dy' => {:value => 'dy', :type => :identifier},
293
+ 'dz' => {:value => 'dz', :type => :identifier},
294
+ 'dt' => {:value => 'dt', :type => :identifier},
295
+ 'oint' => {:value => "\u222E", :type => :operator},
296
+ 'del' => {:value => "\u2202", :type => :operator},
297
+ 'grad' => {:value => "\u2207", :type => :operator},
298
+ '+-' => {:value => "\u00B1", :type => :operator},
299
+ 'O/' => {:value => "\u2205", :type => :operator},
300
+ 'oo' => {:value => "\u221E", :type => :operator},
301
+ 'aleph' => {:value => "\u2135", :type => :operator},
302
+ '...' => {:value => '...', :type => :operator},
303
+ ':.' => {:value => "\u2234", :type => :operator},
304
+ '/_' => {:value => "\u2220", :type => :operator},
305
+ '\\ ' => {:value => "\u00A0", :type => :operator},
306
+ 'quad' => {:value => '\u00A0\u00A0', :type => :operator},
307
+ 'qquad' => {:value => '\u00A0\u00A0\u00A0\u00A0', :type => :operator},
308
+ 'cdots' => {:value => "\u22EF", :type => :operator},
309
+ 'vdots' => {:value => "\u22EE", :type => :operator},
310
+ 'ddots' => {:value => "\u22F1", :type => :operator},
311
+ 'diamond' => {:value => "\u22C4", :type => :operator},
312
+ 'square' => {:value => "\u25A1", :type => :operator},
313
+ '|__' => {:value => "\u230A", :type => :operator},
314
+ '__|' => {:value => "\u230B", :type => :operator},
315
+ '|~' => {:value => "\u2308", :type => :operator},
316
+ '~|' => {:value => "\u2309", :type => :operator},
317
+ 'CC' => {:value => "\u2102", :type => :operator},
318
+ 'NN' => {:value => "\u2115", :type => :operator},
319
+ 'QQ' => {:value => "\u211A", :type => :operator},
320
+ 'RR' => {:value => "\u211D", :type => :operator},
321
+ 'ZZ' => {:value => "\u2124", :type => :operator},
322
+
323
+ 'lim' => {:value => 'lim', :type => :operator, :underover => true},
324
+ 'Lim' => {:value => 'Lim', :type => :operator, :underover => true},
325
+
326
+ # Standard functions
327
+ 'sin' => {:value => 'sin', :type => :identifier},
328
+ 'cos' => {:value => 'cos', :type => :identifier},
329
+ 'tan' => {:value => 'tan', :type => :identifier},
330
+ 'sec' => {:value => 'sec', :type => :identifier},
331
+ 'csc' => {:value => 'csc', :type => :identifier},
332
+ 'cot' => {:value => 'cot', :type => :identifier},
333
+ 'arcsin' => {:value => 'arcsin', :type => :identifier},
334
+ 'arccos' => {:value => 'arccos', :type => :identifier},
335
+ 'arctan' => {:value => 'arctan', :type => :identifier},
336
+ 'sinh' => {:value => 'sinh', :type => :identifier},
337
+ 'cosh' => {:value => 'cosh', :type => :identifier},
338
+ 'tanh' => {:value => 'tanh', :type => :identifier},
339
+ 'sech' => {:value => 'sech', :type => :identifier},
340
+ 'csch' => {:value => 'csch', :type => :identifier},
341
+ 'coth' => {:value => 'coth', :type => :identifier},
342
+ 'exp' => {:value => 'exp', :type => :identifier},
343
+ 'log' => {:value => 'log', :type => :identifier},
344
+ 'ln' => {:value => 'ln', :type => :identifier},
345
+ 'det' => {:value => 'det', :type => :identifier},
346
+ 'dim' => {:value => 'dim', :type => :identifier},
347
+ 'mod' => {:value => 'mod', :type => :identifier},
348
+ 'gcd' => {:value => 'gcd', :type => :identifier},
349
+ 'lcm' => {:value => 'lcm', :type => :identifier},
350
+ 'lub' => {:value => 'lub', :type => :identifier},
351
+ 'glb' => {:value => 'glb', :type => :identifier},
352
+ 'min' => {:value => 'min', :type => :identifier, :underover => true},
353
+ 'max' => {:value => 'max', :type => :identifier, :underover => true},
354
+ 'f' => {:value => 'f', :type => :identifier},
355
+ 'g' => {:value => 'g', :type => :identifier},
356
+
357
+ # Accents
358
+ 'hat' => {:value => "\u005E", :type => :accent, :position => :over},
359
+ 'bar' => {:value => "\u00AF", :type => :accent, :position => :over},
360
+ 'ul' => {:value => '_', :type => :accent, :position => :under},
361
+ 'vec' => {:value => "\u2192", :type => :accent, :position => :over},
362
+ 'dot' => {:value => '.', :type => :accent, :position => :over},
363
+ 'ddot' => {:value => '..', :type => :accent, :position => :over},
364
+ 'obrace' => {:value => "\u23DE", :type => :accent, :position => :over},
365
+ 'ubrace' => {:value => "\u23DF", :type => :accent, :position => :under},
366
+
367
+ # Arrows
368
+ 'uarr' => {:value => "\u2191", :type => :operator},
369
+ 'darr' => {:value => "\u2193", :type => :operator},
370
+ 'rarr' => {:value => "\u2192", :type => :operator},
371
+ '->' => {:value => "\u2192", :type => :operator},
372
+ '>->' => {:value => "\u21A3", :type => :operator},
373
+ '->>' => {:value => "\u21A0", :type => :operator},
374
+ '>->>' => {:value => "\u2916", :type => :operator},
375
+ '|->' => {:value => "\u21A6", :type => :operator},
376
+ 'larr' => {:value => "\u2190", :type => :operator},
377
+ 'harr' => {:value => "\u2194", :type => :operator},
378
+ 'rArr' => {:value => "\u21D2", :type => :operator},
379
+ 'lArr' => {:value => "\u21D0", :type => :operator},
380
+ 'hArr' => {:value => "\u21D4", :type => :operator},
381
+
382
+ # Other
383
+ 'sqrt' => {:value => :sqrt, :type => :unary},
384
+ 'text' => {:value => :text, :type => :unary},
385
+ 'bb' => {:value => :bold, :type => :font},
386
+ 'bbb' => {:value => :double_struck, :type => :font},
387
+ 'ii' => {:value => :italic, :type => :font},
388
+ 'bii' => {:value => :bold_italic, :type => :font},
389
+ 'cc' => {:value => :script, :type => :font},
390
+ 'bcc' => {:value => :bold_script, :type => :font},
391
+ 'tt' => {:value => :monospace, :type => :font},
392
+ 'fr' => {:value => :fraktur, :type => :font},
393
+ 'bfr' => {:value => :bold_fraktur, :type => :font},
394
+ 'sf' => {:value => :sans_serif, :type => :font},
395
+ 'bsf' => {:value => :bold_sans_serif, :type => :font},
396
+ 'sfi' => {:value => :sans_serif_italic, :type => :font},
397
+ 'sfbi' => {:value => :sans_serif_bold_italic, :type => :font},
398
+ 'frac' => {:value => :frac, :type => :binary},
399
+ 'root' => {:value => :root, :type => :binary},
400
+ 'stackrel' => {:value => :over, :type => :binary},
401
+ '/' => {:value => :frac, :type => :infix},
402
+ '_' => {:value => :sub, :type => :infix},
403
+ '^' => {:value => :sup, :type => :infix},
404
+
405
+ # Greek letters
406
+ 'alpha' => {:value => "\u03b1", :type => :identifier},
407
+ 'Alpha' => {:value => "\u0391", :type => :identifier},
408
+ 'beta' => {:value => "\u03b2", :type => :identifier},
409
+ 'Beta' => {:value => "\u0392", :type => :identifier},
410
+ 'gamma' => {:value => "\u03b3", :type => :identifier},
411
+ 'Gamma' => {:value => "\u0393", :type => :operator},
412
+ 'delta' => {:value => "\u03b4", :type => :identifier},
413
+ 'Delta' => {:value => "\u0394", :type => :operator},
414
+ 'epsilon' => {:value => "\u03b5", :type => :identifier},
415
+ 'Epsilon' => {:value => "\u0395", :type => :identifier},
416
+ 'varepsilon' => {:value => "\u025b", :type => :identifier},
417
+ 'zeta' => {:value => "\u03b6", :type => :identifier},
418
+ 'Zeta' => {:value => "\u0396", :type => :identifier},
419
+ 'eta' => {:value => "\u03b7", :type => :identifier},
420
+ 'Eta' => {:value => "\u0397", :type => :identifier},
421
+ 'theta' => {:value => "\u03b8", :type => :identifier},
422
+ 'Theta' => {:value => "\u0398", :type => :operator},
423
+ 'vartheta' => {:value => "\u03d1", :type => :identifier},
424
+ 'iota' => {:value => "\u03b9", :type => :identifier},
425
+ 'Iota' => {:value => "\u0399", :type => :identifier},
426
+ 'kappa' => {:value => "\u03ba", :type => :identifier},
427
+ 'Kappa' => {:value => "\u039a", :type => :identifier},
428
+ 'lambda' => {:value => "\u03bb", :type => :identifier},
429
+ 'Lambda' => {:value => "\u039b", :type => :operator},
430
+ 'mu' => {:value => "\u03bc", :type => :identifier},
431
+ 'Mu' => {:value => "\u039c", :type => :identifier},
432
+ 'nu' => {:value => "\u03bd", :type => :identifier},
433
+ 'Nu' => {:value => "\u039d", :type => :identifier},
434
+ 'xi' => {:value => "\u03be", :type => :identifier},
435
+ 'Xi' => {:value => "\u039e", :type => :operator},
436
+ 'omicron' => {:value => "\u03bf", :type => :identifier},
437
+ 'Omicron' => {:value => "\u039f", :type => :identifier},
438
+ 'pi' => {:value => "\u03c0", :type => :identifier},
439
+ 'Pi' => {:value => "\u03a0", :type => :operator},
440
+ 'rho' => {:value => "\u03c1", :type => :identifier},
441
+ 'Rho' => {:value => "\u03a1", :type => :identifier},
442
+ 'sigma' => {:value => "\u03c3", :type => :identifier},
443
+ 'Sigma' => {:value => "\u03a3", :type => :operator},
444
+ 'tau' => {:value => "\u03c4", :type => :identifier},
445
+ 'Tau' => {:value => "\u03a4", :type => :identifier},
446
+ 'upsilon' => {:value => "\u03c5", :type => :identifier},
447
+ 'Upsilon' => {:value => "\u03a5", :type => :identifier},
448
+ 'phi' => {:value => "\u03c6", :type => :identifier},
449
+ 'Phi' => {:value => "\u03a6", :type => :identifier},
450
+ 'varphi' => {:value => "\u03d5", :type => :identifier},
451
+ 'chi' => {:value => '\u03b3c7', :type => :identifier},
452
+ 'Chi' => {:value => '\u0393a7', :type => :identifier},
453
+ 'psi' => {:value => "\u03c8", :type => :identifier},
454
+ 'Psi' => {:value => "\u03a8", :type => :identifier},
455
+ 'omega' => {:value => "\u03c9", :type => :identifier},
456
+ 'Omega' => {:value => "\u03a9", :type => :operator},
457
+ }
458
+
459
+ def parse(input)
460
+ Expression.new(
461
+ input,
462
+ parse_expression(Tokenizer.new(input, SYMBOLS), 0)
463
+ )
464
+ end
465
+
466
+ private
467
+ def parse_expression(tok, depth)
468
+ e = []
469
+
470
+ while (s1 = parse_simple_expression(tok, depth))
471
+ t1 = tok.next_token
472
+
473
+ if t1[:type] == :infix
474
+ s2 = parse_simple_expression(tok, depth)
475
+ t2 = tok.next_token
476
+ if t1[:value] == :sub && t2[:value] == :sup
477
+ s3 = parse_simple_expression(tok, depth)
478
+ operator = s1[:underover] ? :underover : :subsup
479
+ e << {:type => :ternary, :operator => operator, :s1 => s1, :s2 => s2, :s3 => s3}
480
+ else
481
+ operator = s1[:underover] ? (t1[:value] == :sub ? :under : :over) : t1[:value]
482
+ e << {:type => :binary, :operator => operator, :s1 => s1, :s2 => s2}
483
+ tok.push_back(t2)
484
+ if (t2[:type] == :lrparen || t2[:type] == :rparen) && depth > 0
485
+ break
486
+ end
487
+ end
488
+ elsif t1[:type] == :eof
489
+ e << s1
490
+ break
491
+ else
492
+ e << s1
493
+ tok.push_back(t1)
494
+ if (t1[:type] == :lrparen || t1[:type] == :rparen) && depth > 0
495
+ break
496
+ end
497
+ end
498
+ end
499
+
500
+ e
501
+ end
502
+
503
+ def parse_simple_expression(tok, depth)
504
+ t1 = tok.next_token
505
+
506
+ case t1[:type]
507
+ when :lparen, :lrparen
508
+ t2 = tok.next_token
509
+ case t2[:type]
510
+ when :rparen, :lrparen
511
+ {:type => :paren, :e => nil, :lparen => t1[:value], :rparen => t2[:value]}
512
+ else
513
+ tok.push_back(t2)
514
+
515
+ e = parse_expression(tok, depth + 1)
516
+
517
+ t2 = tok.next_token
518
+ case t2[:type]
519
+ when :rparen, :lrparen
520
+ convert_to_matrix({:type => :paren, :e => e, :lparen => t1[:value], :rparen => t2[:value]})
521
+ else
522
+ tok.push_back(t2)
523
+ {:type => :paren, :e => e, :lparen => t1[:value]}
524
+ end
525
+ end
526
+ when :accent
527
+ s = parse_simple_expression(tok, depth)
528
+ {:type => :binary, :s1 => s, :s2 => {:type => :operator, :c => t1[:value]}, :operator => t1[:position]}
529
+ when :unary, :font
530
+ s = parse_simple_expression(tok, depth)
531
+ {:type => t1[:type], :s => s, :operator => t1[:value]}
532
+ when :binary
533
+ s1 = parse_simple_expression(tok, depth)
534
+ s2 = parse_simple_expression(tok, depth)
535
+ {:type => :binary, :s1 => s1, :s2 => s2, :operator => t1[:value]}
536
+ when :eof
537
+ nil
538
+ else
539
+ {:type => t1[:type], :c => t1[:value], :underover => t1[:underover]}
540
+ end
541
+ end
542
+
543
+ def convert_to_matrix(expression)
544
+ return expression unless matrix? expression
545
+
546
+ rows = expression[:e].select.with_index { |obj, i| i.even? }.map do |row|
547
+ row[:e].select.with_index { |obj, i| i.even? }
548
+ end
549
+
550
+ {:type => :matrix, :rows => rows, :lparen => expression[:lparen], :rparen => expression[:rparen]}
551
+ end
552
+
553
+ def matrix?(expression)
554
+ return false unless expression.is_a?(Hash) && expression[:type] == :paren
555
+
556
+ rows, separators = expression[:e].partition.with_index { |obj, i| i.even? }
557
+
558
+ rows.length > 1 &&
559
+ rows.length > separators.length &&
560
+ separators.all? { |item| item[:type] == :identifier && item[:c] == ',' } &&
561
+ (rows.all? { |item| item[:type] == :paren && item[:lparen] == '(' && item[:rparen] == ')' } ||
562
+ rows.all? { |item| item[:type] == :paren && item[:lparen] == '[' && item[:rparen] == ']' }) &&
563
+ rows.all? { |item| item[:e].length == rows[0][:e].length } &&
564
+ rows.all? { |item| matrix_cols?(item[:e]) }
565
+ end
566
+
567
+ def matrix_cols?(expression)
568
+ return false unless expression.is_a?(Array)
569
+
570
+ cols, separators = expression.partition.with_index { |obj, i| i.even? }
571
+
572
+ cols.all? { |item| item[:type] != :identifier || item[:c] != ',' } &&
573
+ separators.all? { |item| item[:type] == :identifier && item[:c] == ',' }
574
+ end
575
+ end
576
+
577
+ class Expression
578
+ def initialize(asciimath, parsed_expression)
579
+ @asciimath = asciimath
580
+ @parsed_expression = parsed_expression
581
+ end
582
+
583
+ def to_s
584
+ @asciimath
585
+ end
586
+ end
587
+
588
+ def self.parse(asciimath)
589
+ Parser.new.parse(asciimath)
590
+ end
591
+ end
@@ -0,0 +1,3 @@
1
+ module AsciiMath
2
+ VERSION = "1.0.7.pre1"
3
+ end
@@ -4,3 +4,6 @@ require_relative "html2doc/mime"
4
4
  require_relative "html2doc/notes"
5
5
  require_relative "html2doc/math"
6
6
  require_relative "html2doc/lists"
7
+ #require_relative "asciimath/parser"
8
+ #require_relative "asciimath/mathml"
9
+ #require_relative "asciimath/html"
@@ -44,6 +44,7 @@ module Html2Doc
44
44
  end
45
45
 
46
46
  def self.cleanup(docxml, hash)
47
+ namespace(docxml.root)
47
48
  image_cleanup(docxml, hash[:dir1], File.dirname(hash[:filename]))
48
49
  mathml_to_ooml(docxml)
49
50
  lists(docxml, hash[:liststyles])
@@ -91,6 +92,12 @@ module Html2Doc
91
92
  r.gsub!(%r{<meta http-equiv="Content-Type"},
92
93
  "<meta http-equiv=Content-Type")
93
94
  r.gsub!(%r{></m:jc>}, "/>")
95
+ r.gsub!(%r{></v:stroke>}, "/>")
96
+ r.gsub!(%r{></v:f>}, "/>")
97
+ r.gsub!(%r{></v:path>}, "/>")
98
+ r.gsub!(%r{></o:lock>}, "/>")
99
+ r.gsub!(%r{></v:imagedata>}, "/>")
100
+ r.gsub!(%r{></w:wrap>}, "/>")
94
101
  r.gsub!(%r{&tab;|&amp;tab;}, '<span style="mso-tab-count:1">&#xA0; </span>')
95
102
  r
96
103
  end
@@ -142,7 +149,7 @@ module Html2Doc
142
149
  css = stylesheet(hash[:filename], hash[:header_file], hash[:stylesheet])
143
150
  add_stylesheet(head, title, css)
144
151
  define_head1(docxml, hash[:dir1])
145
- namespace(docxml.root)
152
+ rootnamespace(docxml.root)
146
153
  end
147
154
 
148
155
  def self.add_stylesheet(head, title, css)
@@ -162,12 +169,16 @@ module Html2Doc
162
169
  v: "urn:schemas-microsoft-com:vml",
163
170
  m: "http://schemas.microsoft.com/office/2004/12/omml",
164
171
  }.each { |k, v| root.add_namespace_definition(k.to_s, v) }
172
+ end
173
+
174
+ def self.rootnamespace(root)
165
175
  root.add_namespace(nil, "http://www.w3.org/TR/REC-html40")
166
176
  end
167
177
 
168
178
  def self.bookmarks(docxml)
169
179
  docxml.xpath("//*[@id][not(@name)][not(@style = 'mso-element:footnote')]").each do |x|
170
180
  next if x["id"].empty?
181
+ next if %w(shapetype v:shapetype shape v:shape).include? x.name
171
182
  if x.children.empty?
172
183
  x.add_child("<a name='#{x["id"]}'></a>")
173
184
  else
@@ -83,7 +83,9 @@ module Html2Doc
83
83
 
84
84
  # only processes locally stored images
85
85
  def self.image_cleanup(docxml, dir, localdir)
86
- docxml.xpath(IMAGE_PATH).each do |i|
86
+ #docxml.xpath(IMAGE_PATH).each do |i|
87
+ docxml.traverse do |i|
88
+ next unless i.element? && %w(img v:imagedata).include?(i.name)
87
89
  warnsvg(i["src"])
88
90
  next if /^http/.match i["src"]
89
91
  local_filename = File.join(localdir, i["src"])
@@ -1,3 +1,3 @@
1
1
  module Html2Doc
2
- VERSION = "0.8.6".freeze
2
+ VERSION = "0.8.7".freeze
3
3
  end
@@ -383,7 +383,7 @@ RSpec.describe Html2Doc do
383
383
  end
384
384
 
385
385
  it "processes AsciiMath" do
386
- Html2Doc.process(html_input("<div>{{sum_(i=1)^n i^3=((n(n+1))/2)^2)}}</div>"), filename: "test", asciimathdelims: ["{{", "}}"])
386
+ Html2Doc.process(html_input(%[<div>{{sum_(i=1)^n i^3=((n(n+1))/2)^2 text("integer"))}}</div>]), filename: "test", asciimathdelims: ["{{", "}}"])
387
387
  expect(guid_clean(File.read("test.doc", encoding: "utf-8"))).
388
388
  to match_fuzzy(<<~OUTPUT)
389
389
  #{WORD_HDR} #{DEFAULT_STYLESHEET} #{WORD_HDR_END}
@@ -401,6 +401,7 @@ RSpec.describe Html2Doc do
401
401
  </m:num><m:den><m:r><m:t>2</m:t></m:r></m:den></m:f>
402
402
  <m:r><m:t>)</m:t></m:r>
403
403
  </m:e><m:sup><m:r><m:t>2</m:t></m:r></m:sup></m:sSup>
404
+ <m:r><m:rPr><m:nor></m:nor></m:rPr><m:t>"integer"</m:t></m:r>
404
405
  </m:oMath>
405
406
  </div>', '<div style="mso-element:footnote-list"/>')}
406
407
  #{WORD_FTR1}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html2doc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-31 00:00:00.000000000 Z
11
+ date: 2019-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: htmlentities
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '1.15'
131
+ version: 2.0.1
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '1.15'
138
+ version: 2.0.1
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: byebug
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -224,16 +224,16 @@ dependencies:
224
224
  name: rubocop
225
225
  requirement: !ruby/object:Gem::Requirement
226
226
  requirements:
227
- - - "~>"
227
+ - - '='
228
228
  - !ruby/object:Gem::Version
229
- version: '0.50'
229
+ version: 0.54.0
230
230
  type: :development
231
231
  prerelease: false
232
232
  version_requirements: !ruby/object:Gem::Requirement
233
233
  requirements:
234
- - - "~>"
234
+ - - '='
235
235
  - !ruby/object:Gem::Version
236
- version: '0.50'
236
+ version: 0.54.0
237
237
  - !ruby/object:Gem::Dependency
238
238
  name: simplecov
239
239
  requirement: !ruby/object:Gem::Requirement
@@ -306,6 +306,11 @@ files:
306
306
  - bin/rspec
307
307
  - bin/setup
308
308
  - html2doc.gemspec
309
+ - lib/asciimath/cli.rb
310
+ - lib/asciimath/html.rb
311
+ - lib/asciimath/mathml.rb
312
+ - lib/asciimath/parser.rb
313
+ - lib/asciimath/version.rb
309
314
  - lib/html2doc.rb
310
315
  - lib/html2doc/base.rb
311
316
  - lib/html2doc/lists.rb