plurimath 0.8.17 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/lib/plurimath/asciimath/parse.rb +1 -0
  4. data/lib/plurimath/asciimath/transform.rb +12 -0
  5. data/lib/plurimath/math/core.rb +63 -4
  6. data/lib/plurimath/math/formula/mrow.rb +193 -0
  7. data/lib/plurimath/math/formula/mstyle.rb +17 -0
  8. data/lib/plurimath/math/formula.rb +307 -4
  9. data/lib/plurimath/math/function/base.rb +4 -0
  10. data/lib/plurimath/math/function/color.rb +17 -4
  11. data/lib/plurimath/math/function/fenced.rb +219 -0
  12. data/lib/plurimath/math/function/frac.rb +4 -0
  13. data/lib/plurimath/math/function/linebreak.rb +2 -2
  14. data/lib/plurimath/math/function/longdiv.rb +3 -0
  15. data/lib/plurimath/math/function/menclose.rb +3 -0
  16. data/lib/plurimath/math/function/merror.rb +5 -2
  17. data/lib/plurimath/math/function/mglyph.rb +27 -0
  18. data/lib/plurimath/math/function/mlabeledtr.rb +19 -0
  19. data/lib/plurimath/math/function/mpadded.rb +28 -1
  20. data/lib/plurimath/math/function/ms.rb +80 -0
  21. data/lib/plurimath/math/function/msgroup.rb +15 -0
  22. data/lib/plurimath/math/function/msline.rb +5 -2
  23. data/lib/plurimath/math/function/multiscript.rb +14 -0
  24. data/lib/plurimath/math/function/over.rb +3 -0
  25. data/lib/plurimath/math/function/overset.rb +11 -0
  26. data/lib/plurimath/math/function/phantom.rb +3 -0
  27. data/lib/plurimath/math/function/power.rb +3 -0
  28. data/lib/plurimath/math/function/power_base.rb +3 -0
  29. data/lib/plurimath/math/function/root.rb +3 -0
  30. data/lib/plurimath/math/function/scarries.rb +3 -0
  31. data/lib/plurimath/math/function/semantics.rb +14 -0
  32. data/lib/plurimath/math/function/sqrt.rb +3 -0
  33. data/lib/plurimath/math/function/stackrel.rb +3 -0
  34. data/lib/plurimath/math/function/table.rb +53 -0
  35. data/lib/plurimath/math/function/td.rb +4 -1
  36. data/lib/plurimath/math/function/text.rb +22 -2
  37. data/lib/plurimath/math/function/tr.rb +13 -0
  38. data/lib/plurimath/math/function/underover.rb +3 -0
  39. data/lib/plurimath/math/function/underset.rb +44 -0
  40. data/lib/plurimath/math/number.rb +10 -1
  41. data/lib/plurimath/math/symbols/gg.rb +4 -4
  42. data/lib/plurimath/math/symbols/ll.rb +4 -4
  43. data/lib/plurimath/math/symbols/minus.rb +1 -1
  44. data/lib/plurimath/math/symbols/symbol.rb +12 -4
  45. data/lib/plurimath/math.rb +2 -0
  46. data/lib/plurimath/mathml/parser.rb +45 -86
  47. data/lib/plurimath/mathml/utility/empty_defined_methods.rb +477 -0
  48. data/lib/plurimath/mathml/utility/formula_transformation.rb +472 -0
  49. data/lib/plurimath/mathml/utility.rb +363 -0
  50. data/lib/plurimath/mathml.rb +1 -0
  51. data/lib/plurimath/unicode_math/transform.rb +2 -2
  52. data/lib/plurimath/utility.rb +5 -23
  53. data/lib/plurimath/version.rb +1 -1
  54. data/lib/plurimath.rb +9 -0
  55. data/plurimath.gemspec +4 -2
  56. metadata +37 -5
  57. data/lib/plurimath/mathml/transform.rb +0 -413
@@ -0,0 +1,472 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plurimath
4
+ class Mathml
5
+ module Utility
6
+ module FormulaTransformation
7
+ CONDITIONAL_COMMON_UNARY_FUNCTIONS = %w[
8
+ scarries
9
+ phantom
10
+ mpadded
11
+ merror
12
+ ].freeze
13
+ SYMBOL_UPDATABLE_FUNCTIONS = %w[
14
+ font_style
15
+ linebreak
16
+ ].freeze
17
+
18
+ private
19
+
20
+ def update_temp(value)
21
+ @temp_mathml_order = Array(value)
22
+ end
23
+
24
+ def update_temp_order(value, order_name)
25
+ update_temp(
26
+ replace_order_with_value(
27
+ Array(@temp_mathml_order),
28
+ Array(update_temp_mathml_values(value)),
29
+ order_name,
30
+ ),
31
+ )
32
+ end
33
+
34
+ def filter_values(value, array_to_instance: false, replacing_order: true)
35
+ return value unless value.is_a?(Array)
36
+ return array_to_instance ? nil : value if value.empty?
37
+
38
+ if is_a?(Math::Formula) && value.length == 1 && value.first.is_mstyle?
39
+ @displaystyle = value.first.displaystyle
40
+ end
41
+
42
+ if value.length == 1 && value.all? { |val| val.is_a?(Math::Formula) }
43
+ if array_to_instance
44
+ filter_values(value.first.value, array_to_instance: true)
45
+ else
46
+ value.first.value
47
+ end
48
+ elsif value.is_a?(Array) && value.any?(Math::Formula::Mrow)
49
+ value.each_with_index do |element, index|
50
+ next unless element.is_a?(Math::Formula::Mrow)
51
+
52
+ value[index] = filter_values(
53
+ Array(element),
54
+ array_to_instance: true,
55
+ replacing_order: replacing_order,
56
+ )
57
+ end
58
+ value
59
+ elsif value_is_ternary_or_nary?(value)
60
+ value.first.parameter_three = value.delete_at(1)
61
+ filter_values(
62
+ Array(value),
63
+ array_to_instance: array_to_instance,
64
+ replacing_order: replacing_order,
65
+ )
66
+ elsif ternary_naryable?(value)
67
+ [
68
+ Math::Function::Nary.new(
69
+ value.first.parameter_one,
70
+ value.first.parameter_two,
71
+ value.first.parameter_three,
72
+ value.last,
73
+ ),
74
+ ]
75
+ elsif overset_naryable?(value)
76
+ [
77
+ Math::Function::Nary.new(
78
+ value.first.parameter_two,
79
+ nil,
80
+ value.first.parameter_one,
81
+ value.last,
82
+ { type: "undOvr" },
83
+ ),
84
+ ]
85
+ elsif power_naryable?(value)
86
+ [
87
+ Math::Function::Nary.new(
88
+ value.first.parameter_one,
89
+ nil,
90
+ value.first.parameter_two,
91
+ value.last,
92
+ { type: "subSup" },
93
+ ),
94
+ ]
95
+ elsif array_to_instance && replacing_order
96
+ value.length > 1 ? Math::Formula.new(value) : value.first
97
+ else
98
+ value
99
+ end
100
+ end
101
+
102
+ def validate_symbols(value)
103
+ case value
104
+ when Array
105
+ array_validations(value)
106
+ when Math::Symbols::Symbol
107
+ mathml_symbol_to_class(value.value)
108
+ when String
109
+ mathml_symbol_to_class(value)
110
+ end
111
+ end
112
+
113
+ def array_validations(value)
114
+ value.each_with_index do |val, index|
115
+ next unless val.is_a?(Math::Symbols::Symbol)
116
+
117
+ value[index] = mathml_symbol_to_class(val.value)
118
+ end
119
+ value
120
+ end
121
+
122
+ def mathml_symbol_to_class(symbol)
123
+ Plurimath::Utility.mathml_unary_classes(
124
+ Array(symbol),
125
+ lang: :mathml,
126
+ )
127
+ end
128
+
129
+ def replace_order_with_value(order, value, tag_name)
130
+ value_index = 0
131
+ value_array = Array(value)
132
+
133
+ order.each_with_object([]) do |item, result|
134
+ next result << item if item != tag_name
135
+ next if value_index >= value_array.length
136
+
137
+ result << value_array[value_index]
138
+ value_index += 1
139
+ end
140
+ end
141
+
142
+ def update_temp_mathml_values(value)
143
+ value.each_with_index do |element, index|
144
+ next unless element.respond_to?(:temp_mathml_order)
145
+
146
+ if element.symbol? && element.temp_mathml_order&.none?
147
+ value[index] = validate_symbols(element)
148
+ end
149
+
150
+ next unless element.temp_mathml_order&.any?
151
+
152
+ if element.is_ternary_function?
153
+ if element.is_a?(Math::Function::Multiscript)
154
+ multi = multiscript(element.clear_temp_order)
155
+ element.parameter_one = multi[0]
156
+ element.parameter_two = filter_values(Array(multi[1])[0])
157
+ element.parameter_three = filter_values(Array(multi[1])[1])
158
+ elsif first_element_is_ternary_function?(element)
159
+ new_element = ternary_element_from(element.temp_mathml_order)
160
+ new_element.parameter_one = filter_values(
161
+ Array(element.temp_mathml_order.shift),
162
+ array_to_instance: true,
163
+ )
164
+ new_element.parameter_two = filter_values(
165
+ Array(element.temp_mathml_order.shift),
166
+ array_to_instance: true,
167
+ )
168
+ value[index] = new_element
169
+ elsif first_element_is_binary_function?(element)
170
+ new_element = element.temp_mathml_order.shift
171
+ new_element.parameter_one = element.temp_mathml_order.shift
172
+ new_element.parameter_two = element.temp_mathml_order.shift
173
+ value[index] = new_element
174
+ elsif element.is_a?(Math::Function::PowerBase)
175
+ update_power_base_value(element)
176
+ else
177
+ element.parameter_one = element.temp_mathml_order.shift
178
+ element.parameter_two = element.temp_mathml_order.shift
179
+ element.parameter_three = element.temp_mathml_order.shift
180
+ end
181
+ elsif element.is_binary_function?
182
+ update_binary_function_value(element, value, index)
183
+ elsif element.is_unary?
184
+ element.parameter_one = update_unary_function(element)
185
+ elsif element.symbol? && symbol_updatable?(element)
186
+ update_symbol(element, value, index)
187
+ end
188
+ end
189
+ value
190
+ end
191
+
192
+ def validated_order(order, rejectable_array: nil)
193
+ rejectable_array ||= ["comment", "text"]
194
+ order.reject { |str| rejectable_array.include?(str) }
195
+ end
196
+
197
+ def value_is_ternary_or_nary?(value)
198
+ return if value.any?(String)
199
+
200
+ value.length >= 2 &&
201
+ value.first.is_ternary_function? &&
202
+ value.first.parameter_three.nil? &&
203
+ (!value.first.parameter_one.nil? || !value.first.parameter_two.nil?)
204
+ end
205
+
206
+ def first_element_is_ternary_function?(element)
207
+ (
208
+ element.temp_mathml_order.first&.is_ternary_function? &&
209
+ !(
210
+ element.temp_mathml_order.first.is_a?(Math::Function::FontStyle) ||
211
+ element.temp_mathml_order.first.is_a?(Math::Function::Fenced)
212
+ )
213
+ ) ||
214
+ ternary_filtered_function?(element)
215
+ end
216
+
217
+ def first_element_is_binary_function?(element)
218
+ (
219
+ element.temp_mathml_order.first&.is_binary_function? &&
220
+ !element.temp_mathml_order.first.is_a?(Math::Function::FontStyle)
221
+ ) ||
222
+ binary_filtered_function?(element)
223
+ end
224
+
225
+ def binary_filtered_function?(element)
226
+ new_element = filter_values(
227
+ [element.temp_mathml_order.first],
228
+ array_to_instance: true,
229
+ )
230
+ new_element&.is_binary_function? && !new_element.all_values_exist?
231
+ end
232
+
233
+ def ternary_filtered_function?(element)
234
+ new_element = filter_values(
235
+ [element.temp_mathml_order.first],
236
+ array_to_instance: true,
237
+ )
238
+ new_element&.is_ternary_function? &&
239
+ !new_element.all_values_exist?
240
+ end
241
+
242
+ def ternary_element_from(order)
243
+ element = order.shift
244
+ return element if element.is_ternary_function?
245
+
246
+ filter_values([element], array_to_instance: true)
247
+ end
248
+
249
+ def period_to_dot(element, value, index)
250
+ return element unless element.is_a?(Math::Symbols::Period)
251
+
252
+ value[index] = Math::Function::Dot.new
253
+ end
254
+
255
+ def multiscript(value)
256
+ value.slice_before("mprescripts").map do |val|
257
+ base_value = val.shift
258
+ val = Plurimath::Utility.nil_to_none_object(val)
259
+ part_val = val.partition.with_index { |_, i| i.even? }
260
+ first_value = part_val[0].empty? ? nil : part_val[0]
261
+ second_value = part_val[1].empty? ? nil : part_val[1]
262
+ if base_value.to_s.include?("mprescripts")
263
+ [first_value, second_value]
264
+ else
265
+ Math::Function::PowerBase.new(
266
+ filter_values(base_value, array_to_instance: true),
267
+ filter_values(first_value, array_to_instance: true),
268
+ filter_values(second_value, array_to_instance: true),
269
+ )
270
+ end
271
+ end
272
+ end
273
+
274
+ def ternary_naryable?(value)
275
+ value.length == 2 &&
276
+ value.first.is_a?(Math::Function::PowerBase) &&
277
+ value.first.parameter_one.is_nary_symbol?
278
+ end
279
+
280
+ def overset_naryable?(value)
281
+ value.length == 2 &&
282
+ value.first.is_a?(Math::Function::Overset) &&
283
+ value.first.parameter_two.is_nary_symbol?
284
+ end
285
+
286
+ def power_naryable?(value)
287
+ value.length == 2 &&
288
+ value.first.is_a?(Math::Function::Power) &&
289
+ value.first.parameter_one.is_nary_symbol?
290
+ end
291
+
292
+ def update_power_base_value(element)
293
+ element.parameter_one = filter_values(
294
+ Array(element.temp_mathml_order.shift),
295
+ array_to_instance: true,
296
+ )
297
+ element.parameter_two = filter_values(
298
+ Array(element.temp_mathml_order.shift),
299
+ array_to_instance: true,
300
+ )
301
+ return unless element.is_a?(Math::Function::PowerBase)
302
+
303
+ element.parameter_three = filter_values(
304
+ Array(element.temp_mathml_order.shift),
305
+ array_to_instance: true,
306
+ )
307
+ end
308
+
309
+ def update_overset_value(element, value, index)
310
+ case element.temp_mathml_order[1].class_name
311
+ when "obrace", "ubrace"
312
+ new_element = element.temp_mathml_order.pop
313
+ new_element.parameter_one = element.temp_mathml_order.shift
314
+ when "hat", "ddot"
315
+ new_element = element.temp_mathml_order.pop
316
+ new_element.parameter_one = filter_values(
317
+ Array(element.temp_mathml_order.shift),
318
+ array_to_instance: true,
319
+ )
320
+ return unless element.respond_to?(:options)
321
+
322
+ new_element.attributes = element.options
323
+ when "period", "dot"
324
+ new_element = period_to_dot(
325
+ element.temp_mathml_order.pop,
326
+ value,
327
+ index,
328
+ )
329
+ new_element.parameter_one = filter_values(
330
+ Array(element.temp_mathml_order.shift),
331
+ array_to_instance: true,
332
+ )
333
+ new_element.attributes = element.options
334
+ when "bar"
335
+ new_element = element.temp_mathml_order.pop
336
+ new_element.parameter_one = element.temp_mathml_order.shift
337
+ when "vec"
338
+ new_element = element.temp_mathml_order.pop
339
+ new_element.parameter_one = filter_values(
340
+ Array(element.temp_mathml_order.shift),
341
+ array_to_instance: true,
342
+ )
343
+ new_element.attributes = element.options
344
+ when "ul", "underline"
345
+ element.temp_mathml_order.pop
346
+ new_element = Math::Function::Bar.new(
347
+ filter_values(
348
+ element.clear_temp_order,
349
+ array_to_instance: true,
350
+ ),
351
+ element.options,
352
+ )
353
+ when "tilde"
354
+ new_element = element.temp_mathml_order.pop
355
+ new_element.parameter_one = filter_values(
356
+ element.clear_temp_order,
357
+ array_to_instance: true,
358
+ )
359
+ new_element.attributes = element.options
360
+ else
361
+ element.parameter_two = element.temp_mathml_order.shift
362
+ element.parameter_one = element.temp_mathml_order.shift
363
+ end
364
+ value[index] = new_element if new_element
365
+ end
366
+
367
+ def update_underset_value(element, value, index)
368
+ return if update_underset_first_temp_element(element, value, index)
369
+
370
+ case element.temp_mathml_order[1]&.class_name
371
+ when "obrace", "ubrace", "ul", "underline"
372
+ new_element = element.temp_mathml_order.pop
373
+ new_element.parameter_one = element.temp_mathml_order.shift
374
+ when "bar"
375
+ element.temp_mathml_order.pop
376
+ new_element = Math::Function::Ul.new(
377
+ filter_values(
378
+ element.clear_temp_order,
379
+ array_to_instance: true,
380
+ ),
381
+ element.options,
382
+ )
383
+ else
384
+ element.parameter_two = element.temp_mathml_order.shift
385
+ element.parameter_one = element.temp_mathml_order.shift
386
+ end
387
+ value[index] = new_element if new_element
388
+ end
389
+
390
+ def update_underset_first_temp_element(element, value, index)
391
+ case element.temp_mathml_order[0]
392
+ when Math::Function::Vec,
393
+ ->(block_element) { updatable_ternary_function?(block_element) }
394
+ new_element = element.temp_mathml_order.shift
395
+ new_element.parameter_one = element.temp_mathml_order.shift
396
+ value[index] = new_element
397
+ return unless new_element.is_a?(Math::Function::Vec)
398
+
399
+ new_element.attributes = element.options
400
+ end
401
+ end
402
+
403
+ def updatable_ternary_function?(element)
404
+ element.is_ternary_function? && !element.any_value_exist?
405
+ end
406
+
407
+ def update_binary_function_value(element, value, index)
408
+ case element
409
+ when Math::Function::Semantics, Math::Function::Stackrel
410
+ element.parameter_one = filter_values(
411
+ Array(element.clear_temp_order),
412
+ array_to_instance: true,
413
+ )
414
+ when Math::Function::Overset
415
+ update_overset_value(element, value, index)
416
+ when Math::Function::Underset
417
+ update_underset_value(element, value, index)
418
+ when Math::Function::Power, Math::Function::Base
419
+ update_power_base_value(element)
420
+ when Math::Function::Td
421
+ element.parameter_one = element.clear_temp_order
422
+ when Math::Function::Root
423
+ element.parameter_two = element.temp_mathml_order.shift
424
+ element.parameter_one = element.temp_mathml_order.shift
425
+ when Math::Function::Menclose
426
+ element.parameter_two = filter_values(
427
+ Array(element.clear_temp_order),
428
+ array_to_instance: true,
429
+ )
430
+ else
431
+ element.parameter_one = element.temp_mathml_order.shift
432
+ element.parameter_two = element.temp_mathml_order.shift
433
+ end
434
+ end
435
+
436
+ def update_unary_function(element)
437
+ case element.class_name
438
+ when *CONDITIONAL_COMMON_UNARY_FUNCTIONS
439
+ filter_values(
440
+ element.clear_temp_order,
441
+ array_to_instance: true,
442
+ )
443
+ when "sqrt"
444
+ element.temp_mathml_order.shift
445
+ else
446
+ element.clear_temp_order
447
+ end
448
+ end
449
+
450
+ def symbol_updatable?(element)
451
+ temp_element = element.temp_mathml_order.first
452
+ return false unless temp_element
453
+
454
+ temp_element.is_a?(Math::Function::FontStyle) ||
455
+ temp_element.is_a?(Math::Function::Linebreak)
456
+ end
457
+
458
+ def update_symbol(element, value, index)
459
+ new_element = element.temp_mathml_order.shift
460
+ case new_element
461
+ when Math::Function::FontStyle
462
+ new_element.parameter_one = validate_symbols(element)
463
+ value[index] = new_element
464
+ when Math::Function::Linebreak
465
+ new_element.parameter_one = validate_symbols(element)
466
+ value[index] = new_element
467
+ end
468
+ end
469
+ end
470
+ end
471
+ end
472
+ end