plurimath 0.8.17 → 0.8.19

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/plurimath/asciimath/parse.rb +1 -0
  3. data/lib/plurimath/asciimath/transform.rb +12 -0
  4. data/lib/plurimath/math/core.rb +63 -4
  5. data/lib/plurimath/math/formula/mrow.rb +193 -0
  6. data/lib/plurimath/math/formula/mstyle.rb +17 -0
  7. data/lib/plurimath/math/formula.rb +311 -32
  8. data/lib/plurimath/math/function/base.rb +4 -0
  9. data/lib/plurimath/math/function/color.rb +17 -4
  10. data/lib/plurimath/math/function/fenced.rb +219 -0
  11. data/lib/plurimath/math/function/frac.rb +4 -0
  12. data/lib/plurimath/math/function/linebreak.rb +2 -2
  13. data/lib/plurimath/math/function/longdiv.rb +3 -0
  14. data/lib/plurimath/math/function/menclose.rb +3 -0
  15. data/lib/plurimath/math/function/merror.rb +5 -2
  16. data/lib/plurimath/math/function/mglyph.rb +27 -0
  17. data/lib/plurimath/math/function/mlabeledtr.rb +19 -0
  18. data/lib/plurimath/math/function/mpadded.rb +28 -1
  19. data/lib/plurimath/math/function/ms.rb +80 -0
  20. data/lib/plurimath/math/function/msgroup.rb +15 -0
  21. data/lib/plurimath/math/function/msline.rb +5 -2
  22. data/lib/plurimath/math/function/multiscript.rb +14 -0
  23. data/lib/plurimath/math/function/over.rb +3 -0
  24. data/lib/plurimath/math/function/overset.rb +11 -0
  25. data/lib/plurimath/math/function/phantom.rb +3 -0
  26. data/lib/plurimath/math/function/power.rb +3 -0
  27. data/lib/plurimath/math/function/power_base.rb +3 -0
  28. data/lib/plurimath/math/function/root.rb +3 -0
  29. data/lib/plurimath/math/function/scarries.rb +3 -0
  30. data/lib/plurimath/math/function/semantics.rb +14 -0
  31. data/lib/plurimath/math/function/sqrt.rb +3 -0
  32. data/lib/plurimath/math/function/stackrel.rb +3 -0
  33. data/lib/plurimath/math/function/table.rb +53 -0
  34. data/lib/plurimath/math/function/td.rb +4 -1
  35. data/lib/plurimath/math/function/text.rb +22 -2
  36. data/lib/plurimath/math/function/tr.rb +13 -0
  37. data/lib/plurimath/math/function/underover.rb +3 -0
  38. data/lib/plurimath/math/function/underset.rb +44 -0
  39. data/lib/plurimath/math/number.rb +10 -1
  40. data/lib/plurimath/math/symbols/gg.rb +4 -4
  41. data/lib/plurimath/math/symbols/ll.rb +4 -4
  42. data/lib/plurimath/math/symbols/minus.rb +1 -1
  43. data/lib/plurimath/math/symbols/symbol.rb +12 -4
  44. data/lib/plurimath/math.rb +2 -0
  45. data/lib/plurimath/mathml/parser.rb +45 -86
  46. data/lib/plurimath/mathml/utility/empty_defined_methods.rb +477 -0
  47. data/lib/plurimath/mathml/utility/formula_transformation.rb +472 -0
  48. data/lib/plurimath/mathml/utility.rb +363 -0
  49. data/lib/plurimath/mathml.rb +1 -0
  50. data/lib/plurimath/unicode_math/transform.rb +2 -2
  51. data/lib/plurimath/utility.rb +5 -23
  52. data/lib/plurimath/version.rb +1 -1
  53. data/lib/plurimath.rb +9 -0
  54. data/plurimath.gemspec +4 -2
  55. metadata +37 -5
  56. 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