latexmath 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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