latexmath 0.1.0 → 0.1.5

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.
@@ -0,0 +1,424 @@
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,40 +1,13 @@
1
1
  module Latexmath
2
2
  class Equation
3
-
4
3
  def initialize(string)
5
4
  @latex = string
6
5
  end
7
6
 
8
7
  def to_mathml
9
- <<~'INPUT'
10
- <?xml version="1.0" encoding="UTF-8"?>
11
- <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\varepsilon=\frac{1}{2}(J+J^{t})" display="block">
12
- <mrow>
13
- <mi>ε</mi>
14
- <mo>=</mo>
15
- <mrow>
16
- <mfrac>
17
- <mn>1</mn>
18
- <mn>2</mn>
19
- </mfrac>
20
- <mo>⁢</mo>
21
- <mrow>
22
- <mo stretchy="false">(</mo>
23
- <mrow>
24
- <mi>J</mi>
25
- <mo>+</mo>
26
- <msup>
27
- <mi>J</mi>
28
- <mi>t</mi>
29
- </msup>
30
- </mrow>
31
- <mo stretchy="false">)</mo>
32
- </mrow>
33
- </mrow>
34
- </mrow>
35
- </math>
36
- INPUT
8
+ tokens = Tokenizer.new(@latex).tokenize
9
+ aggregate = Aggregator.new(tokens).aggregate
10
+ Converter.new(aggregate).convert
37
11
  end
38
-
39
12
  end
40
13
  end