latexmath 0.1.0 → 0.1.1

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,421 @@
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)
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 == '\\left' || element == '\\right'
305
+ if param == '.'
306
+
307
+ else
308
+ symbol = Latexmath::Symbol.get(param)
309
+ new_parent.text = symbol.nil? ? param : "&#x#{symbol};"
310
+ end
311
+ elsif element == '\\array'
312
+ convert_array_content(param, new_parent, alignment)
313
+ elsif MATRICES.include?(element)
314
+ convert_matrix_content(
315
+ param, new_parent, alignment, element == '\\substack'
316
+ )
317
+ else
318
+ if param.is_a?(Array)
319
+ _parent = Latexmath::XML::Element.new(new_parent, 'mrow')
320
+ classify_subgroup(param, _parent, false, style)
321
+ else
322
+ classify(param, new_parent, false, style)
323
+ end
324
+ end
325
+ end
326
+
327
+ get_postfix_element(element, parent)
328
+ if ['\\overline', '\\bar'].include?(element)
329
+ mo = Latexmath::XML::Element.new(new_parent, 'mo', { stretchy: 'true' })
330
+ mo.text = '&#x000AF;'
331
+ elsif element == '\\underline'
332
+ mo = Latexmath::XML::Element.new(new_parent, 'mo', { stretchy: 'true' })
333
+ mo.text = '&#x00332;'
334
+ elsif element == '\\overrightarrow'
335
+ mo = Latexmath::XML::Element.new(new_parent, 'mo', { stretchy: 'true' })
336
+ mo.text = '&#x02192;'
337
+ end
338
+
339
+ params.times do
340
+ iterable.next
341
+ end
342
+ end
343
+
344
+ def convert_matrix_content(param, parent, alignment, single_mtd = true)
345
+ return if param.size == 0
346
+ return unless param.is_a?(Array)
347
+
348
+ all_are_list = param.all? { |item| item.is_a?(Array) }
349
+
350
+ if all_are_list
351
+ param.each do |row|
352
+ if row.empty?
353
+ mtr = Latexmath::XML::Element.new(parent, 'mtr')
354
+ Latexmath::XML::Element.new(mtr, 'mtd')
355
+ else
356
+ convert_matrix_row(row, parent, alignment, single_mtd)
357
+ end
358
+ end
359
+ else
360
+ convert_matrix_row(param, parent, alignment, single_mtd)
361
+ end
362
+ end
363
+
364
+ def convert_matrix_row(row, parent, alignment, single_mtd)
365
+ mtr = Latexmath::XML::Element.new(parent, 'mtr')
366
+ iterable = Range.new(0, row.size - 1).each_compat
367
+ mtd = Latexmath::XML::Element.new(mtr, 'mtd') if single_mtd
368
+
369
+ while i = iterable.next
370
+ element = row[i]
371
+ if %w[r l c].include?(alignment)
372
+ column_align = { 'r' => 'right', 'l' => 'left', 'c' => 'center' }.fetch(alignment, nil)
373
+ mtd = Latexmath::XML::Element.new(mtr, 'mtd', { columnalign: column_align })
374
+ elsif !single_mtd
375
+ mtd = Latexmath::XML::Element.new(mtr, 'mtd')
376
+ end
377
+
378
+ if element.is_a?(Array)
379
+ classify_subgroup(element, mtd)
380
+ elsif COMMANDS.include?(element)
381
+ convert_command(element, row, i, iterable, mtd)
382
+ else
383
+ classify(element, mtd)
384
+ end
385
+ begin
386
+ iterable.peek
387
+ rescue StandardError
388
+ break
389
+ end
390
+ end
391
+ end
392
+
393
+ def get_postfix_element(element, row)
394
+ if ['\\binom', '\\pmatrix'].include?(element)
395
+ convert_and_append_operator('\\rparen', row)
396
+ elsif element == '\\bmatrix'
397
+ convert_and_append_operator('\\rbrack', row)
398
+ elsif element == '\\Bmatrix'
399
+ convert_and_append_operator('\\rbrace', row)
400
+ elsif element == '\\vmatrix'
401
+ convert_and_append_operator('\\vert', row)
402
+ elsif element == '\\Vmatrix'
403
+ convert_and_append_operator('\\Vert', row)
404
+ end
405
+ end
406
+
407
+ def get_prefix_element(element, row)
408
+ if ['\\binom', '\\pmatrix'].include?(element)
409
+ convert_and_append_operator('\\lparen', row)
410
+ elsif element == '\\bmatrix'
411
+ convert_and_append_operator('\\lbrack', row)
412
+ elsif element == '\\Bmatrix'
413
+ convert_and_append_operator('\\lbrace', row)
414
+ elsif element == '\\vmatrix'
415
+ convert_and_append_operator('\\vert', row)
416
+ elsif element == '\\Vmatrix'
417
+ convert_and_append_operator('\\Vert', row)
418
+ end
419
+ end
420
+ end
421
+ 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