latexmath 0.1.4 → 1.0.0

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/LICENSE.md +33 -0
  4. data/README.adoc +133 -14
  5. data/artifacts/latex.bindings.python.json +1 -0
  6. data/artifacts/latex.bindings.rs.json +1 -0
  7. data/artifacts/latex.bindings.ruby.json +1 -0
  8. data/artifacts/latex.bindings.ts.json +1 -0
  9. data/artifacts/latex.checksums.txt +19 -0
  10. data/artifacts/latex.json +1 -0
  11. data/data/latex-symbols.json +3301 -0
  12. data/docs/model-contract.adoc +125 -0
  13. data/docs/parser-contract.adoc +349 -0
  14. data/docs/testsuite-pin.adoc +76 -0
  15. data/grammars/latex.wrappers.json +17 -0
  16. data/lib/latexmath/model/nodes.rb +918 -0
  17. data/lib/latexmath/model.rb +44 -0
  18. data/lib/latexmath/parser/artifact.rb +80 -0
  19. data/lib/latexmath/parser/configuration.rb +35 -0
  20. data/lib/latexmath/parser/engine.rb +161 -0
  21. data/lib/latexmath/parser/errors.rb +51 -0
  22. data/lib/latexmath/parser/native_recursion_guard.rb +43 -0
  23. data/lib/latexmath/parser/preprocessor.rb +77 -0
  24. data/lib/latexmath/parser/raw_parser.rb +84 -0
  25. data/lib/latexmath/parser/source_map.rb +121 -0
  26. data/lib/latexmath/parser/unwrap.rb +91 -0
  27. data/lib/latexmath/parser.rb +97 -0
  28. data/lib/latexmath/symbols.rb +74 -0
  29. data/lib/latexmath/version.rb +1 -1
  30. data/lib/latexmath.rb +5 -87
  31. data/model/latex.lml.checksums.txt +36 -0
  32. data/model/latex.lml.json +3350 -0
  33. metadata +60 -71
  34. data/.editorconfig +0 -14
  35. data/.github/workflows/test.yml +0 -80
  36. data/.gitignore +0 -22
  37. data/.rspec +0 -3
  38. data/.rubocop.yml +0 -129
  39. data/CODE_OF_CONDUCT.md +0 -74
  40. data/Gemfile +0 -10
  41. data/Gemfile.lock +0 -79
  42. data/Rakefile +0 -95
  43. data/bin/console +0 -14
  44. data/bin/setup +0 -8
  45. data/exe/latexmath +0 -8
  46. data/latexmath.gemspec +0 -34
  47. data/lib/latexmath/aggregator.rb +0 -351
  48. data/lib/latexmath/converter.rb +0 -424
  49. data/lib/latexmath/equation.rb +0 -13
  50. data/lib/latexmath/ext.rb +0 -9
  51. data/lib/latexmath/symbol.rb +0 -21
  52. data/lib/latexmath/tokenizer.rb +0 -82
  53. data/lib/latexmath/xml/builder.rb +0 -4
  54. data/lib/latexmath/xml/element.rb +0 -25
  55. data/lib/unimathsymbols.js.erb +0 -4
  56. data/lib/unimathsymbols.txt +0 -2864
  57. data/opal/latexmath-opal.rb +0 -40
  58. data/opal/ox.rb +0 -64
  59. data/opal/pseudoenumerator.rb +0 -19
@@ -1,424 +0,0 @@
1
- module Latexmath
2
- class Converter
3
- def initialize(aggregate)
4
- @aggregate = aggregate
5
- end
6
-
7
- def convert(xmlns = 'http://www.w3.org/1998/Math/MathML', display = 'block')
8
- @doc = Ox::Document.new
9
- math = Ox::Element.new('math')
10
- math = Latexmath::XML::Element.new(@doc, 'math', { xmlns: xmlns, display: display })
11
- mrow = Latexmath::XML::Element.new(math, 'mrow')
12
-
13
- classify_subgroup(@aggregate, mrow)
14
-
15
- Ox.dump(@doc, indent: -1)
16
- end
17
-
18
- private
19
-
20
- def apply_style(element, elements, index, iterable, parent)
21
- style = STYLES[element]
22
- index += 1
23
- iterable.next
24
- classify_subgroup(elements[index], parent, false, style)
25
- end
26
-
27
- def classify(element, parent, is_math_mode = false, style = nil)
28
- symbol = Latexmath::Symbol.get("\\mathbf{#{element}}") unless style.nil?
29
- symbol ||= Latexmath::Symbol.get(element)
30
- if element == '\\displaystyle'
31
- elsif element.nil?
32
- Latexmath::XML::Element.new(parent, 'mi')
33
- elsif element.match?(/^\d+(.\d+)?$/)
34
- el = Latexmath::XML::Element.new(parent, 'mn')
35
- el.text = element
36
- elsif element.size > 0 && '<>&'.include?(element)
37
- el = Latexmath::XML::Element.new(parent, 'mo')
38
- el.text = { '<' => '&lt;', '>' => '&gt;', '&' => '&amp;' }[element]
39
- elsif element.size > 0 && '+-*/()='.include?(element)
40
- el = Latexmath::XML::Element.new(parent, 'mo')
41
- el.text = symbol.nil? ? element : "&#x#{symbol};"
42
- el.set_attribute('stretchy', false) if '()'.include?(element)
43
- elsif symbol &&
44
- (
45
- Range.new('2200'.to_i(16), '22FF'.to_i(16)).include?(symbol.to_i(16)) ||
46
- Range.new('2190'.to_i(16), '21FF'.to_i(16)).include?(symbol.to_i(16))
47
- ) ||
48
- symbol == '.'
49
-
50
- mo = Latexmath::XML::Element.new(parent, 'mo')
51
- mo.text = "&#x#{symbol};"
52
- elsif element.start_with?('\\textrm')
53
- el = Latexmath::XML::Element.new(parent, 'mtext')
54
- matches = element.match(/\\textrm{([^}]*)}/)
55
- el.text = matches[1]
56
- elsif element.start_with?('\\vec')
57
- el = Latexmath::XML::Element.new(parent, 'mover', {accent: 'true'})
58
- mi = Latexmath::XML::Element.new(el, 'mi')
59
- matches = element.match(/\\vec{([^}]*)}/)
60
- mi.text = matches[1]
61
- mo = Latexmath::XML::Element.new(el, 'mo', {stretchy: 'false'})
62
- mo.text = "&#x#{Latexmath::Symbol.get('\\vec')};"
63
- elsif element.start_with?('\\hat')
64
- el = Latexmath::XML::Element.new(parent, 'mover', {accent: 'true'})
65
- mi = Latexmath::XML::Element.new(el, 'mi')
66
- matches = element.match(/\\hat{([^}]*)}/)
67
- mi.text = matches[1]
68
- mo = Latexmath::XML::Element.new(el, 'mo', {stretchy: 'false'})
69
- mo.text = "&#x#{Latexmath::Symbol.get('\\hat')};"
70
- elsif element.start_with?('\\mbox')
71
- el = Latexmath::XML::Element.new(parent, 'mtext')
72
- matches = element.match(/\\mbox{([^}]*)}/)
73
- el.text = matches[1]
74
- elsif element.start_with?('\\')
75
- tag_name = is_math_mode ? 'mo' : 'mi'
76
- el = Latexmath::XML::Element.new(parent, tag_name)
77
- el.text = if symbol
78
- "&#x#{symbol};"
79
- elsif ['\\log', '\\ln', '\\tan', '\\sec', '\\cos', '\\sin', '\\cot', '\\csc'].include?(element)
80
- element[1..element.size]
81
- else
82
- element
83
- end
84
- else
85
- tag_name = is_math_mode ? 'mo' : 'mi'
86
- el = Latexmath::XML::Element.new(parent, tag_name)
87
- el.text = symbol ? "&#x#{symbol};" : element
88
- end
89
- end
90
-
91
- def classify_subgroup(elements, row, is_math_mode = false, style = nil)
92
- return if elements.size == 0
93
-
94
- iterable = Range.new(0, (elements.size - 1)).each_compat
95
- while i = iterable.next
96
- element = elements[i]
97
- if element.is_a?(Array)
98
- _row = Latexmath::XML::Element.new(row, 'mrow')
99
- classify_subgroup(element, _row, is_math_mode)
100
- is_math_mode = false
101
- elsif COMMANDS.keys.include?(element)
102
- convert_command(element, elements, i, iterable, row, style)
103
- elsif STYLES.keys.include?(element)
104
- apply_style(element, elements, i, iterable, row)
105
- elsif element.start_with?('\\math')
106
- is_math_mode = true
107
- else
108
- classify(element, row, is_math_mode, style)
109
- end
110
- begin
111
- iterable.peek
112
- rescue StandardError
113
- break
114
- end
115
- end
116
- end
117
-
118
- def convert_and_append_operator(symbol, parent)
119
- converted = Latexmath::Symbol.get(symbol)
120
- mo = Latexmath::XML::Element.new(parent, 'mo')
121
- mo.text = "&#x#{converted};"
122
- end
123
-
124
- def convert_array_content(param, parent, alignment = '')
125
- all_are_list = param.all? { |item| item.is_a?(Array) }
126
-
127
- if all_are_list
128
- param.each do |row|
129
- convert_array_array(row, parent, alignment)
130
- end
131
- else
132
- convert_array_row(param, parent, alignment)
133
- end
134
- end
135
-
136
- def convert_array_array(row, parent, alignment)
137
- if alignment&.include?('|')
138
- _alignment = []
139
- column_lines = []
140
- alignment.chars.each do |j|
141
- if j == '|'
142
- column_lines << 'solid'
143
- else
144
- _alignment << j
145
- end
146
- column_lines << 'none' if _alignment.size - column_lines.size == 2
147
- end
148
- parent.set_attribute('columnlines', column_lines.join(' '))
149
- else
150
- _alignment = alignment.chars
151
- end
152
-
153
- mtr = Latexmath::XML::Element.new(parent, 'mtr')
154
- iterable = Range.new(0, row.size - 1).each_compat
155
-
156
- has_row_line = false
157
- index = 0
158
- row_lines = []
159
-
160
- while i = iterable.next
161
- element = row[i]
162
- if element == '\\hline'
163
- row_lines << 'solid'
164
- has_row_line = true
165
- row_lines.insert(0, 'none')
166
- next
167
- end
168
- align = _alignment[index]
169
-
170
- mtd = if %w[r l c].include?(align)
171
- column_align = { 'r' => 'right', 'l' => 'left', 'c' => 'center' }.fetch(align, nil)
172
- Latexmath::XML::Element.new(mtr, 'mtd', { columnalign: column_align })
173
- else
174
- Latexmath::XML::Element.new(mtr, 'mtd')
175
- end
176
-
177
- if element.is_a?(Array)
178
- classify_subgroup(element, mtd)
179
- elsif COMMANDS.include?(element)
180
- convert_command(element, row, i, iterable, mtd)
181
- else
182
- classify(element, mtd)
183
- end
184
- index += 1
185
- begin
186
- iterable.peek
187
- rescue StandardError
188
- break
189
- end
190
- end
191
-
192
- parent.set_attribute('rowlines', row_lines.join(' ')) if row_lines.include?('solid')
193
- end
194
-
195
- def convert_array_row(param, parent, alignment = '')
196
- if alignment&.include?('|')
197
- _alignment = []
198
- column_lines = []
199
- alignment.chars.each do |j|
200
- if j == '|'
201
- column_lines << 'solid'
202
- else
203
- _alignment << j
204
- end
205
- column_lines << 'none' if _alignment.size - column_lines.size == 2
206
- end
207
- parent.set_attribute('columnlines', column_lines.join(' '))
208
- else
209
- _alignment = alignment.chars
210
- end
211
-
212
- row_lines = []
213
- row_count = 0
214
-
215
- if param.all?{|d| !d.is_a?(Array)}
216
- mtr = Latexmath::XML::Element.new(parent, 'mtr')
217
- param.each do |element |
218
- mtd = Latexmath::XML::Element.new(mtr, 'mtd')
219
- classify(element, mtd)
220
- end
221
- else
222
- param.each do |row|
223
- next if row.nil?
224
-
225
- row_count += 1
226
- mtr = Latexmath::XML::Element.new(parent, 'mtr')
227
- iterable = Range.new(0, row.size - 1).each_compat
228
-
229
- index = 0
230
- has_row_line = false
231
-
232
- while i = iterable.next
233
- element = row[i]
234
- if element == '\\hline' && row_count > 1
235
- row_lines << 'solid'
236
- has_row_line = true
237
- next
238
- end
239
- align = _alignment[index]
240
-
241
- mtd = if %w[r l c].include?(align)
242
- column_align = { 'r' => 'right', 'l' => 'left', 'c' => 'center' }.fetch(align, nil)
243
- Latexmath::XML::Element.new(mtr, 'mtd', { columnalign: column_align })
244
- else
245
- Latexmath::XML::Element.new(mtr, 'mtd')
246
- end
247
-
248
- if element.is_a?(Array)
249
- classify_subgroup(element, mtd)
250
- else
251
- classify(element, mtd)
252
- end
253
-
254
- index += 1
255
-
256
- begin
257
- iterable.peek
258
- rescue StandardError
259
- break
260
- end
261
- end
262
-
263
- row_lines << 'none' if !has_row_line && row_count > 1
264
- end
265
- end
266
-
267
-
268
- parent.set_attribute('rowlines', row_lines.join(' ')) if row_lines.include?('solid')
269
- end
270
-
271
- def convert_command(element, elements, index, iterable, parent, style = nil)
272
- get_prefix_element(element, parent)
273
-
274
- parent = Latexmath::XML::Element.new(parent, 'mstyle', { scriptlevel: '1' }) if element == '\\substack'
275
-
276
- params, tag, attributes = COMMANDS[element]
277
-
278
- if (elements.size - 1) < params
279
- mo = Latexmath::XML::Element.new(parent, 'mo')
280
- mo.text = element[1..element.size]
281
- return
282
- end
283
-
284
- new_parent = Latexmath::XML::Element.new(parent, tag, attributes)
285
- alignment = ''
286
-
287
- if MATRICES.include?(element) && (element.end_with?('*') || element == '\\array')
288
- index += 1
289
- alignment = elements[index]
290
- iterable.next
291
- end
292
-
293
- if LIMITS.include?(element)
294
- limit = Latexmath::XML::Element.new(new_parent, 'mo')
295
- limit.text = element[1..element.size]
296
- end
297
-
298
- Range.new(0, (params - 1)).each do |_j|
299
- index += 1
300
- param = elements[index]
301
- if element == '_' && index == 1 && param == '\\sum'
302
- new_parent.tag = 'munder'
303
- classify(param, new_parent)
304
- elsif element == '_^' && param == '\\sum'
305
- new_parent.tag = 'munderover'
306
- classify(param, new_parent)
307
- elsif element == '\\left' || element == '\\right'
308
- if param == '.'
309
-
310
- else
311
- symbol = Latexmath::Symbol.get(param)
312
- new_parent.text = symbol.nil? ? param : "&#x#{symbol};"
313
- end
314
- elsif element == '\\array'
315
- convert_array_content(param, new_parent, alignment)
316
- elsif MATRICES.include?(element)
317
- convert_matrix_content(
318
- param, new_parent, alignment, element == '\\substack'
319
- )
320
- else
321
- if param.is_a?(Array)
322
- _parent = Latexmath::XML::Element.new(new_parent, 'mrow')
323
- classify_subgroup(param, _parent, false, style)
324
- else
325
- classify(param, new_parent, false, style)
326
- end
327
- end
328
- end
329
-
330
- get_postfix_element(element, parent)
331
- if ['\\overline', '\\bar'].include?(element)
332
- mo = Latexmath::XML::Element.new(new_parent, 'mo', { stretchy: 'true' })
333
- mo.text = '&#x000AF;'
334
- elsif element == '\\underline'
335
- mo = Latexmath::XML::Element.new(new_parent, 'mo', { stretchy: 'true' })
336
- mo.text = '&#x00332;'
337
- elsif element == '\\overrightarrow'
338
- mo = Latexmath::XML::Element.new(new_parent, 'mo', { stretchy: 'true' })
339
- mo.text = '&#x02192;'
340
- end
341
-
342
- params.times do
343
- iterable.next
344
- end
345
- end
346
-
347
- def convert_matrix_content(param, parent, alignment, single_mtd = true)
348
- return if param.size == 0
349
- return unless param.is_a?(Array)
350
-
351
- all_are_list = param.all? { |item| item.is_a?(Array) }
352
-
353
- if all_are_list
354
- param.each do |row|
355
- if row.empty?
356
- mtr = Latexmath::XML::Element.new(parent, 'mtr')
357
- Latexmath::XML::Element.new(mtr, 'mtd')
358
- else
359
- convert_matrix_row(row, parent, alignment, single_mtd)
360
- end
361
- end
362
- else
363
- convert_matrix_row(param, parent, alignment, single_mtd)
364
- end
365
- end
366
-
367
- def convert_matrix_row(row, parent, alignment, single_mtd)
368
- mtr = Latexmath::XML::Element.new(parent, 'mtr')
369
- iterable = Range.new(0, row.size - 1).each_compat
370
- mtd = Latexmath::XML::Element.new(mtr, 'mtd') if single_mtd
371
-
372
- while i = iterable.next
373
- element = row[i]
374
- if %w[r l c].include?(alignment)
375
- column_align = { 'r' => 'right', 'l' => 'left', 'c' => 'center' }.fetch(alignment, nil)
376
- mtd = Latexmath::XML::Element.new(mtr, 'mtd', { columnalign: column_align })
377
- elsif !single_mtd
378
- mtd = Latexmath::XML::Element.new(mtr, 'mtd')
379
- end
380
-
381
- if element.is_a?(Array)
382
- classify_subgroup(element, mtd)
383
- elsif COMMANDS.include?(element)
384
- convert_command(element, row, i, iterable, mtd)
385
- else
386
- classify(element, mtd)
387
- end
388
- begin
389
- iterable.peek
390
- rescue StandardError
391
- break
392
- end
393
- end
394
- end
395
-
396
- def get_postfix_element(element, row)
397
- if ['\\binom', '\\pmatrix'].include?(element)
398
- convert_and_append_operator('\\rparen', row)
399
- elsif element == '\\bmatrix'
400
- convert_and_append_operator('\\rbrack', row)
401
- elsif element == '\\Bmatrix'
402
- convert_and_append_operator('\\rbrace', row)
403
- elsif element == '\\vmatrix'
404
- convert_and_append_operator('\\vert', row)
405
- elsif element == '\\Vmatrix'
406
- convert_and_append_operator('\\Vert', row)
407
- end
408
- end
409
-
410
- def get_prefix_element(element, row)
411
- if ['\\binom', '\\pmatrix'].include?(element)
412
- convert_and_append_operator('\\lparen', row)
413
- elsif element == '\\bmatrix'
414
- convert_and_append_operator('\\lbrack', row)
415
- elsif element == '\\Bmatrix'
416
- convert_and_append_operator('\\lbrace', row)
417
- elsif element == '\\vmatrix'
418
- convert_and_append_operator('\\vert', row)
419
- elsif element == '\\Vmatrix'
420
- convert_and_append_operator('\\Vert', row)
421
- end
422
- end
423
- end
424
- end
@@ -1,13 +0,0 @@
1
- module Latexmath
2
- class Equation
3
- def initialize(string)
4
- @latex = string
5
- end
6
-
7
- def to_mathml
8
- tokens = Tokenizer.new(@latex).tokenize
9
- aggregate = Aggregator.new(tokens).aggregate
10
- Converter.new(aggregate).convert
11
- end
12
- end
13
- end
data/lib/latexmath/ext.rb DELETED
@@ -1,9 +0,0 @@
1
- class Range
2
- def each_compat
3
- if RUBY_ENGINE == 'opal'
4
- Pseudoenumerator.new(self)
5
- else
6
- each
7
- end
8
- end
9
- end
@@ -1,21 +0,0 @@
1
- require 'singleton'
2
- module Latexmath
3
- class Symbol
4
- include Singleton
5
-
6
- def self.get(symbol)
7
- instance.symbols[symbol]
8
- end
9
-
10
- def symbols
11
- if RUBY_ENGINE == 'opal'
12
- require 'unimathsymbols'
13
- require 'native'
14
- @symbols ||= Native::Object.new(`globalThis.unimathsymbols`)
15
- else
16
- @symbols ||= Latexmath::Constants::SYMBOLS
17
- end
18
- end
19
-
20
- end
21
- end
@@ -1,82 +0,0 @@
1
- require 'strscan'
2
-
3
- module Latexmath
4
- class Tokenizer < StringScanner
5
- def tokenize
6
- exp = []
7
-
8
- while token = fetch_token
9
- exp << token
10
- end
11
-
12
- if exp.first == '\[' && exp.last == '\]'
13
- exp.pop
14
- exp.shift
15
- end
16
-
17
- exp
18
- end
19
-
20
- def fetch_token
21
- skip(/\s+/)
22
- skip(/\$\$/)
23
-
24
- token = if scan(/\\mathbb\{[^}]+}/)
25
- if Latexmath::Symbol.get(matched)
26
- "&#x#{Latexmath::Symbol.get(matched)};"
27
- else
28
- matched
29
- end
30
- elsif scan(/{\\bf [^}]+}/)
31
- matches = matched.match(/{\\bf ([^}]+)}/)
32
- symbol = Latexmath::Symbol.get("\\mathbf{#{matches[1]}}")
33
- "&#x#{symbol};" if symbol
34
- elsif scan(/\\\\\[\d+mm\]/)
35
- '\\\\' # matched
36
- elsif scan(/\\mbox{[^\}]+}/)
37
- matched
38
- elsif scan(/\\Delta/)
39
- matched
40
- elsif scan(/\\vec{[^\}]+}/)
41
- matched
42
- elsif scan(/\\hat{[^\}]+}/)
43
- matched
44
- elsif scan(/\\textrm{[^\}]+}/)
45
- matched
46
- elsif scan(/{\\rm [^}]+}/)
47
- matches = matched.match(/{\\rm ([^}]+)}/)
48
- matches[1]
49
- elsif scan(/\\[a-z]+\{(bmatrix|Bmatrix|matrix|vmatrix|Vmatrix|array|pmatrix|split)\*?\}/)
50
- matched
51
- elsif scan(/\\[a-z]+/)
52
- matched
53
- elsif scan(/\\\\/)
54
- matched
55
- elsif scan(/\\\[/)
56
- matched
57
- elsif scan(/\\\]/)
58
- matched
59
- elsif scan(/\\\{/)
60
- matched
61
- elsif scan(/\\[:;,]/)
62
- matched
63
- elsif scan(/\\/)
64
- matched
65
- elsif scan(/[a-z]/)
66
- matched
67
- elsif scan(/[0-9]+\.[0-9]+/)
68
- matched
69
- elsif scan(/[0-9]+/)
70
- matched
71
- elsif scan(/ /)
72
- matched
73
- elsif scan(/[\{\}\*]/)
74
- matched
75
- elsif scan(/./)
76
- matched
77
- end
78
-
79
- token
80
- end
81
- end
82
- end
@@ -1,4 +0,0 @@
1
- module Latexmath::XML
2
- class Builder
3
- end
4
- end
@@ -1,25 +0,0 @@
1
- module Latexmath::XML
2
- class Element
3
- attr_accessor :element
4
- def initialize(parent, tag, attributes = {})
5
- @element = Ox::Element.new(tag)
6
- attributes.each do |key, value|
7
- @element[key] = value
8
- end
9
- @parent = parent.is_a?(Latexmath::XML::Element) ? parent.element : parent
10
- @parent << @element
11
- end
12
-
13
- def tag=(tag)
14
- @element.value = tag
15
- end
16
-
17
- def text=(value)
18
- @element << Ox::Raw.new(value)
19
- end
20
-
21
- def set_attribute(key, value)
22
- @element[key] = value
23
- end
24
- end
25
- end
@@ -1,4 +0,0 @@
1
- <%# This preprocesses Latexmath::SYMBOLS constant for Opal %>
2
- <% require_relative 'lib/latexmath/constants/symbols' %>
3
-
4
- globalThis.unimathsymbols = <%= Latexmath::Constants::SYMBOLS.to_json %>;