plurimath 0.10.0 → 0.10.2

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec-opal +1 -1
  3. data/.rubocop.yml +9 -5
  4. data/.rubocop_todo.yml +0 -0
  5. data/Gemfile +7 -5
  6. data/README.adoc +57 -1
  7. data/Rakefile +11 -0
  8. data/lib/plurimath/asciimath/parse.rb +0 -1
  9. data/lib/plurimath/asciimath/parser.rb +0 -3
  10. data/lib/plurimath/asciimath.rb +5 -0
  11. data/lib/plurimath/cli.rb +28 -1
  12. data/lib/plurimath/errors.rb +0 -4
  13. data/lib/plurimath/formatter/numbers.rb +12 -0
  14. data/lib/plurimath/formatter/numeric_formatter.rb +4 -1
  15. data/lib/plurimath/formatter/standard.rb +0 -1
  16. data/lib/plurimath/formatter.rb +6 -9
  17. data/lib/plurimath/html/parse.rb +0 -1
  18. data/lib/plurimath/html/parser.rb +0 -3
  19. data/lib/plurimath/html.rb +5 -0
  20. data/lib/plurimath/latex/parse.rb +0 -1
  21. data/lib/plurimath/latex/parser.rb +2 -5
  22. data/lib/plurimath/latex.rb +5 -0
  23. data/lib/plurimath/math/core.rb +6 -2
  24. data/lib/plurimath/math/formula/mrow.rb +2 -41
  25. data/lib/plurimath/math/formula/mstyle.rb +6 -0
  26. data/lib/plurimath/math/formula.rb +59 -301
  27. data/lib/plurimath/math/function/abs.rb +0 -1
  28. data/lib/plurimath/math/function/arccos.rb +0 -1
  29. data/lib/plurimath/math/function/arcsin.rb +0 -1
  30. data/lib/plurimath/math/function/arctan.rb +0 -1
  31. data/lib/plurimath/math/function/arg.rb +0 -1
  32. data/lib/plurimath/math/function/bar.rb +0 -1
  33. data/lib/plurimath/math/function/base.rb +0 -3
  34. data/lib/plurimath/math/function/binary_function.rb +5 -3
  35. data/lib/plurimath/math/function/cancel.rb +0 -1
  36. data/lib/plurimath/math/function/ceil.rb +0 -1
  37. data/lib/plurimath/math/function/color.rb +0 -1
  38. data/lib/plurimath/math/function/cos.rb +0 -1
  39. data/lib/plurimath/math/function/cosh.rb +0 -1
  40. data/lib/plurimath/math/function/cot.rb +0 -1
  41. data/lib/plurimath/math/function/coth.rb +0 -1
  42. data/lib/plurimath/math/function/csc.rb +0 -1
  43. data/lib/plurimath/math/function/csch.rb +0 -1
  44. data/lib/plurimath/math/function/ddot.rb +0 -1
  45. data/lib/plurimath/math/function/deg.rb +0 -1
  46. data/lib/plurimath/math/function/det.rb +0 -1
  47. data/lib/plurimath/math/function/dim.rb +0 -1
  48. data/lib/plurimath/math/function/dot.rb +0 -1
  49. data/lib/plurimath/math/function/exp.rb +0 -1
  50. data/lib/plurimath/math/function/fenced.rb +1 -197
  51. data/lib/plurimath/math/function/floor.rb +0 -1
  52. data/lib/plurimath/math/function/font_style/bold-fraktur.rb +0 -1
  53. data/lib/plurimath/math/function/font_style/bold-italic.rb +0 -1
  54. data/lib/plurimath/math/function/font_style/bold-sans-serif.rb +0 -1
  55. data/lib/plurimath/math/function/font_style/bold-script.rb +0 -1
  56. data/lib/plurimath/math/function/font_style/bold.rb +0 -1
  57. data/lib/plurimath/math/function/font_style/double_struck.rb +0 -1
  58. data/lib/plurimath/math/function/font_style/fraktur.rb +0 -1
  59. data/lib/plurimath/math/function/font_style/italic.rb +0 -1
  60. data/lib/plurimath/math/function/font_style/monospace.rb +0 -1
  61. data/lib/plurimath/math/function/font_style/normal.rb +0 -1
  62. data/lib/plurimath/math/function/font_style/sans-serif-bold-italic.rb +0 -1
  63. data/lib/plurimath/math/function/font_style/sans-serif-italic.rb +0 -1
  64. data/lib/plurimath/math/function/font_style/sans-serif.rb +0 -1
  65. data/lib/plurimath/math/function/font_style/script.rb +0 -1
  66. data/lib/plurimath/math/function/font_style.rb +29 -2
  67. data/lib/plurimath/math/function/frac.rb +0 -3
  68. data/lib/plurimath/math/function/gcd.rb +0 -1
  69. data/lib/plurimath/math/function/glb.rb +0 -1
  70. data/lib/plurimath/math/function/hat.rb +0 -1
  71. data/lib/plurimath/math/function/hom.rb +0 -1
  72. data/lib/plurimath/math/function/inf.rb +1 -2
  73. data/lib/plurimath/math/function/int.rb +2 -3
  74. data/lib/plurimath/math/function/intent.rb +0 -1
  75. data/lib/plurimath/math/function/ker.rb +0 -1
  76. data/lib/plurimath/math/function/lcm.rb +0 -1
  77. data/lib/plurimath/math/function/left.rb +0 -1
  78. data/lib/plurimath/math/function/lg.rb +0 -1
  79. data/lib/plurimath/math/function/lim.rb +1 -2
  80. data/lib/plurimath/math/function/liminf.rb +0 -1
  81. data/lib/plurimath/math/function/limits.rb +0 -1
  82. data/lib/plurimath/math/function/limsup.rb +0 -1
  83. data/lib/plurimath/math/function/linebreak.rb +0 -1
  84. data/lib/plurimath/math/function/ln.rb +0 -1
  85. data/lib/plurimath/math/function/log.rb +1 -2
  86. data/lib/plurimath/math/function/longdiv.rb +0 -3
  87. data/lib/plurimath/math/function/lub.rb +0 -1
  88. data/lib/plurimath/math/function/max.rb +0 -1
  89. data/lib/plurimath/math/function/mbox.rb +0 -1
  90. data/lib/plurimath/math/function/menclose.rb +0 -3
  91. data/lib/plurimath/math/function/merror.rb +0 -3
  92. data/lib/plurimath/math/function/mglyph.rb +0 -3
  93. data/lib/plurimath/math/function/min.rb +0 -1
  94. data/lib/plurimath/math/function/mlabeledtr.rb +0 -20
  95. data/lib/plurimath/math/function/mod.rb +0 -1
  96. data/lib/plurimath/math/function/mpadded.rb +0 -3
  97. data/lib/plurimath/math/function/ms.rb +1 -77
  98. data/lib/plurimath/math/function/msgroup.rb +0 -27
  99. data/lib/plurimath/math/function/msline.rb +0 -3
  100. data/lib/plurimath/math/function/multiscript.rb +0 -14
  101. data/lib/plurimath/math/function/nary.rb +4 -0
  102. data/lib/plurimath/math/function/none.rb +1 -4
  103. data/lib/plurimath/math/function/norm.rb +0 -1
  104. data/lib/plurimath/math/function/obrace.rb +0 -1
  105. data/lib/plurimath/math/function/oint.rb +2 -3
  106. data/lib/plurimath/math/function/over.rb +0 -3
  107. data/lib/plurimath/math/function/overset.rb +3 -3
  108. data/lib/plurimath/math/function/phantom.rb +0 -3
  109. data/lib/plurimath/math/function/power.rb +0 -3
  110. data/lib/plurimath/math/function/power_base.rb +0 -3
  111. data/lib/plurimath/math/function/prod.rb +2 -3
  112. data/lib/plurimath/math/function/right.rb +0 -1
  113. data/lib/plurimath/math/function/root.rb +0 -3
  114. data/lib/plurimath/math/function/rule.rb +0 -1
  115. data/lib/plurimath/math/function/scarries.rb +0 -3
  116. data/lib/plurimath/math/function/scarry.rb +22 -0
  117. data/lib/plurimath/math/function/sec.rb +0 -1
  118. data/lib/plurimath/math/function/sech.rb +0 -1
  119. data/lib/plurimath/math/function/semantics.rb +0 -15
  120. data/lib/plurimath/math/function/sin.rb +0 -1
  121. data/lib/plurimath/math/function/sinh.rb +0 -1
  122. data/lib/plurimath/math/function/sqrt.rb +0 -3
  123. data/lib/plurimath/math/function/stackrel.rb +0 -3
  124. data/lib/plurimath/math/function/substack.rb +0 -1
  125. data/lib/plurimath/math/function/sum.rb +2 -3
  126. data/lib/plurimath/math/function/sup.rb +0 -1
  127. data/lib/plurimath/math/function/table/align.rb +0 -1
  128. data/lib/plurimath/math/function/table/array.rb +0 -1
  129. data/lib/plurimath/math/function/table/bmatrix.rb +0 -1
  130. data/lib/plurimath/math/function/table/cases.rb +0 -1
  131. data/lib/plurimath/math/function/table/eqarray.rb +0 -1
  132. data/lib/plurimath/math/function/table/matrix.rb +0 -1
  133. data/lib/plurimath/math/function/table/multline.rb +0 -1
  134. data/lib/plurimath/math/function/table/pmatrix.rb +0 -1
  135. data/lib/plurimath/math/function/table/split.rb +0 -1
  136. data/lib/plurimath/math/function/table/vmatrix.rb +0 -1
  137. data/lib/plurimath/math/function/table.rb +10 -23
  138. data/lib/plurimath/math/function/tan.rb +0 -1
  139. data/lib/plurimath/math/function/tanh.rb +0 -1
  140. data/lib/plurimath/math/function/td.rb +0 -4
  141. data/lib/plurimath/math/function/ternary_function.rb +6 -2
  142. data/lib/plurimath/math/function/text.rb +1 -6
  143. data/lib/plurimath/math/function/tilde.rb +4 -1
  144. data/lib/plurimath/math/function/tr.rb +0 -13
  145. data/lib/plurimath/math/function/ubrace.rb +0 -1
  146. data/lib/plurimath/math/function/ul.rb +4 -1
  147. data/lib/plurimath/math/function/underover.rb +0 -3
  148. data/lib/plurimath/math/function/underset.rb +22 -45
  149. data/lib/plurimath/math/function/unitsml.rb +2 -1
  150. data/lib/plurimath/math/function/vec.rb +4 -1
  151. data/lib/plurimath/math/function.rb +107 -10
  152. data/lib/plurimath/math/number.rb +9 -7
  153. data/lib/plurimath/math/symbols/comma.rb +1 -1
  154. data/lib/plurimath/math/symbols/plus.rb +1 -1
  155. data/lib/plurimath/math/symbols/symbol.rb +17 -4
  156. data/lib/plurimath/math.rb +8 -26
  157. data/lib/plurimath/mathml/constants.rb +1 -0
  158. data/lib/plurimath/mathml/formula_transformation.rb +442 -0
  159. data/lib/plurimath/mathml/parser.rb +11 -50
  160. data/lib/plurimath/mathml/translator.rb +584 -0
  161. data/lib/plurimath/mathml/utility/formula_transformation.rb +2 -341
  162. data/lib/plurimath/mathml.rb +5 -0
  163. data/lib/plurimath/number_formatter.rb +2 -1
  164. data/lib/plurimath/omml/parser.rb +10 -7
  165. data/lib/plurimath/omml/transform.rb +17 -5
  166. data/lib/plurimath/omml.rb +3 -0
  167. data/lib/plurimath/setup/opal.rb.erb +3 -4
  168. data/lib/plurimath/unicode_math/parse.rb +0 -5
  169. data/lib/plurimath/unicode_math/parser.rb +1 -6
  170. data/lib/plurimath/unicode_math/parsing_rules/absence_rules.rb +0 -1
  171. data/lib/plurimath/unicode_math/parsing_rules/common_rules.rb +0 -1
  172. data/lib/plurimath/unicode_math/parsing_rules/constants_rules.rb +0 -1
  173. data/lib/plurimath/unicode_math/parsing_rules/masked.rb +0 -1
  174. data/lib/plurimath/unicode_math/parsing_rules/sub_sup.rb +0 -1
  175. data/lib/plurimath/unicode_math/parsing_rules.rb +14 -0
  176. data/lib/plurimath/unicode_math.rb +6 -0
  177. data/lib/plurimath/utility.rb +1 -1
  178. data/lib/plurimath/version.rb +1 -1
  179. data/lib/plurimath/xml_engine/oga.rb +6 -6
  180. data/lib/plurimath/xml_engine/ox_engine.rb +2 -2
  181. data/lib/plurimath/xml_engine.rb +2 -0
  182. data/lib/plurimath.rb +43 -18
  183. data/plurimath.gemspec +5 -4
  184. metadata +33 -15
  185. data/lib/plurimath/mathml/utility/empty_defined_methods.rb +0 -483
  186. data/lib/plurimath/mathml/utility.rb +0 -369
@@ -0,0 +1,584 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mml"
4
+
5
+ module Plurimath
6
+ class Mathml
7
+ class Translator
8
+ include Mathml::FormulaTransformation
9
+
10
+ def initialize
11
+ @memoized = {}
12
+ end
13
+
14
+ # Main entry point: Mml model → Plurimath model
15
+ def mml_to_plurimath(mml_node)
16
+ return nil if mml_node.nil?
17
+
18
+ case mml_node
19
+ when String then text_node_to_plurimath(mml_node)
20
+ when Mml::V4::Math then math_to_formula(mml_node)
21
+ when Mml::V4::Mrow then mrow_to_mrow(mml_node)
22
+ when Mml::V4::Mover then mover_to_overset(mml_node)
23
+ when Mml::V4::Munder then munder_to_underset(mml_node)
24
+ when Mml::V4::Munderover then munderover_to_underover(mml_node)
25
+ when Mml::V4::Msup then msup_to_power(mml_node)
26
+ when Mml::V4::Msub then msub_to_base(mml_node)
27
+ when Mml::V4::Msubsup then msubsup_to_powerbase(mml_node)
28
+ when Mml::V4::Mfrac then mfrac_to_frac(mml_node)
29
+ when Mml::V4::Mfraction then mfrac_to_frac(mml_node)
30
+ when Mml::V4::Msqrt then msqrt_to_sqrt(mml_node)
31
+ when Mml::V4::Mroot then mroot_to_root(mml_node)
32
+ when Mml::V4::Mi then mi_to_symbol(mml_node)
33
+ when Mml::V4::Mo then mo_to_symbol(mml_node)
34
+ when Mml::V4::Mn then mn_to_number(mml_node)
35
+ when Mml::V4::Mtext then mtext_to_text(mml_node)
36
+ when Mml::V4::Mstyle then mstyle_to_mstyle(mml_node)
37
+ when Mml::V4::Mtable then mtable_to_table(mml_node)
38
+ when Mml::V4::Mtr then mtr_to_tr(mml_node)
39
+ when Mml::V4::Mtd then mtd_to_td(mml_node)
40
+ when Mml::V4::Mfenced then mfenced_to_fenced(mml_node)
41
+ when Mml::V4::Mphantom then mphantom_to_phantom(mml_node)
42
+ when Mml::V4::Menclose then menclose_to_menclose(mml_node)
43
+ when Mml::V4::Merror then merror_to_merror(mml_node)
44
+ when Mml::V4::Mlongdiv then mlongdiv_to_longdiv(mml_node)
45
+ when Mml::V4::Mstack then mstack_to_stackrel(mml_node)
46
+ when Mml::V4::Msrow then msrow_to_formula(mml_node)
47
+ when Mml::V4::Msgroup then msgroup_to_msgroup(mml_node)
48
+ when Mml::V4::Msline then msline_to_msline(mml_node)
49
+ when Mml::V4::Mpadded then mpadded_to_mpadded(mml_node)
50
+ when Mml::V4::Mglyph then mglyph_to_mglyph(mml_node)
51
+ when Mml::V4::Mmultiscripts then mmultiscripts_to_multiscript(mml_node)
52
+ when Mml::V4::Mlabeledtr then mlabeledtr_to_mlabeledtr(mml_node)
53
+ when Mml::V4::Semantics then semantics_to_semantics(mml_node)
54
+ when Mml::V4::None then none_to_none
55
+ when Mml::V4::Ms then ms_to_ms(mml_node)
56
+ when Mml::V4::Mscarries then mscarries_to_scarries(mml_node)
57
+ when Mml::V4::Mscarry then mscarry_to_mscarry(mml_node)
58
+ when Mml::V4::Annotation then nil
59
+ when Mml::V4::AnnotationXml then nil
60
+ when Mml::V4::Malignmark then nil
61
+ when Mml::V4::Maligngroup then nil
62
+ when Mml::V4::Mspace then mspace_to_space(mml_node)
63
+ when Mml::V4::Mprescripts then mprescripts_to_prescripts(mml_node)
64
+ else
65
+ raise "Unknown mml node type: #{mml_node.class}"
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # MathML element: <math>
72
+ def math_to_formula(math)
73
+ children = ordered_children(math)
74
+ formula_values = children.map { |child| mml_to_plurimath(child) }.compact
75
+ formula_values = nary_check(formula_values)
76
+ display_style = boolean_to_displaystyle(math.display)
77
+
78
+ if formula_values.length == 1 && formula_values.first.respond_to?(:is_mstyle?) && formula_values.first.is_mstyle?
79
+ mstyle = formula_values.first
80
+ formula_values = Array(mstyle.value)
81
+ display_style = mstyle.displaystyle
82
+ end
83
+
84
+ Plurimath::Math::Formula.new(
85
+ formula_values,
86
+ display_style: display_style,
87
+ )
88
+ end
89
+
90
+ # MathML element: <mrow>
91
+ def mrow_to_mrow(mrow)
92
+ children = ordered_children(mrow)
93
+ formula_values = children.map { |child| mml_to_plurimath(child) }.compact
94
+ return nil if formula_values.empty?
95
+
96
+ combined = nary_check(combine_function_with_parens(formula_values))
97
+ mrow_obj = Plurimath::Math::Formula::Mrow.new(combined)
98
+ mrow_obj.send(:organize_value)
99
+ preserve_explicit_nary_body!(mrow_obj.value)
100
+ fill_ternary_third_values(mrow_obj.value)
101
+ return mrow_obj.value.first if mrow_obj.value.length == 1
102
+
103
+ if mrow.respond_to?(:intent) && mrow.intent && !mrow.intent.empty?
104
+ content = mrow_obj.is_a?(Plurimath::Math::Formula) ? mrow_obj : Plurimath::Math::Formula.new([mrow_obj])
105
+ return Plurimath::Math::Function::Intent.new(
106
+ content,
107
+ Plurimath::Math::Function::Text.new(mrow.intent),
108
+ )
109
+ end
110
+
111
+ Plurimath::Math::Formula.new(mrow_obj.value)
112
+ end
113
+
114
+ # MathML element: <mover> base overscript
115
+ # Note: Overset convention is parameter_one=overscript, parameter_two=base
116
+ # (reversed from MathML document order) to match rendering methods
117
+ def mover_to_overset(mover)
118
+ children = content_children(mover)
119
+ base = filter_child(mml_to_plurimath(children[0]))
120
+ overscript = filter_child(mml_to_plurimath(children[1]))
121
+ options = {}
122
+ options[:accent] = true if truthy_mathml_bool?(mover.accent)
123
+
124
+ case overscript&.class_name
125
+ when "obrace", "ubrace"
126
+ overscript.parameter_one = base
127
+ overscript
128
+ when "hat", "ddot", "vec", "tilde"
129
+ overscript.parameter_one = base
130
+ overscript.attributes = options if overscript.respond_to?(:attributes=)
131
+ overscript
132
+ when "period", "dot"
133
+ new_element = overscript.is_a?(Plurimath::Math::Symbols::Period) ? Plurimath::Math::Function::Dot.new : overscript
134
+ new_element.parameter_one = base
135
+ new_element.attributes = options if new_element.respond_to?(:attributes=)
136
+ new_element
137
+ when "bar"
138
+ overscript.parameter_one = base
139
+ overscript
140
+ when "ul", "underline"
141
+ Plurimath::Math::Function::Bar.new(base, options)
142
+ else
143
+ Plurimath::Math::Function::Overset.new(overscript, base, options)
144
+ end
145
+ end
146
+
147
+ # MathML element: <munder> base underscript
148
+ def munder_to_underset(munder)
149
+ children = content_children(munder)
150
+ base = mml_to_plurimath(children[0])
151
+ underscript = mml_to_plurimath(children[1])
152
+ options = {}
153
+ options[:accentunder] = true if truthy_mathml_bool?(munder.accentunder)
154
+
155
+ if base.is_a?(Plurimath::Math::Function::Vec) ||
156
+ (base.respond_to?(:is_ternary_function?) && base.is_ternary_function? && !base.any_value_exist?)
157
+ base.parameter_one = underscript
158
+ base.attributes = options if base.respond_to?(:attributes=)
159
+ return base
160
+ end
161
+
162
+ if base.respond_to?(:is_binary_function?) && base.is_binary_function? && !base.any_value_exist?
163
+ base.parameter_one = underscript
164
+ return base
165
+ end
166
+
167
+ case underscript&.class_name
168
+ when "obrace", "ubrace", "ul", "underline"
169
+ underscript.parameter_one = base
170
+ underscript
171
+ when "bar"
172
+ Plurimath::Math::Function::Ul.new(base, options)
173
+ else
174
+ Plurimath::Math::Function::Underset.new(underscript, base, options)
175
+ end
176
+ end
177
+
178
+ # MathML element: <munderover> base underscript overscript
179
+ def munderover_to_underover(munderover)
180
+ children = content_children(munderover)
181
+ base = filter_child(mml_to_plurimath(children[0]))
182
+ underscript = filter_child(mml_to_plurimath(children[1]))
183
+ overscript = filter_child(mml_to_plurimath(children[2]))
184
+
185
+ if base&.is_ternary_function? && !base.any_value_exist?
186
+ base.parameter_one = underscript
187
+ base.parameter_two = overscript
188
+ base
189
+ elsif base&.is_nary_symbol?
190
+ Plurimath::Math::Function::Nary.new(
191
+ base,
192
+ underscript,
193
+ overscript,
194
+ nil,
195
+ { type: "undOvr" },
196
+ )
197
+ else
198
+ Plurimath::Math::Function::Underover.new(base, underscript, overscript)
199
+ end
200
+ end
201
+
202
+ # MathML element: <msup> base superscript
203
+ def msup_to_power(sup)
204
+ children = content_children(sup)
205
+ base = filter_child(mml_to_plurimath(children[0]))
206
+ superscript = filter_child(mml_to_plurimath(children[1]))
207
+ if base&.is_binary_function? && !base.any_value_exist?
208
+ base.parameter_one = superscript
209
+ return base
210
+ end
211
+
212
+ Plurimath::Math::Function::Power.new(base, superscript)
213
+ end
214
+
215
+ # MathML element: <msub> base subscript
216
+ def msub_to_base(sub)
217
+ children = content_children(sub)
218
+ base = filter_child(mml_to_plurimath(children[0]))
219
+ subscript = filter_child(mml_to_plurimath(children[1]))
220
+ if base&.is_binary_function? && !base.any_value_exist?
221
+ base.parameter_one = subscript
222
+ return base
223
+ end
224
+
225
+ Plurimath::Math::Function::Base.new(base, subscript)
226
+ end
227
+
228
+ # MathML element: <msubsup> base subscript superscript
229
+ def msubsup_to_powerbase(subsup)
230
+ children = content_children(subsup)
231
+ base = filter_child(mml_to_plurimath(children[0]))
232
+ subscript = filter_child(mml_to_plurimath(children[1]))
233
+ superscript = filter_child(mml_to_plurimath(children[2]))
234
+
235
+ if base&.is_ternary_function? && !base.any_value_exist?
236
+ base.parameter_one = subscript
237
+ base.parameter_two = superscript
238
+ return base
239
+ elsif base&.is_binary_function? && !base.any_value_exist?
240
+ base.parameter_one = subscript
241
+ base.parameter_two = superscript
242
+ return base
243
+ end
244
+
245
+ Plurimath::Math::Function::PowerBase.new(base, subscript, superscript)
246
+ end
247
+
248
+ # MathML element: <mi> - identifier
249
+ def mi_to_symbol(mi)
250
+ value = text_value(mi.value)
251
+ return nil if value.nil? || (value.respond_to?(:empty?) && value.empty?)
252
+
253
+ apply_font_style(mi, mathml_symbol(value))
254
+ end
255
+
256
+ # MathML element: <mo> - operator
257
+ def mo_to_symbol(mo)
258
+ value = text_value(mo.value)
259
+ # Handle linebreak="newline" attribute - creates a Linebreak wrapper
260
+ if mo.linebreak == "newline"
261
+ attributes = {}
262
+ attributes[:linebreakstyle] = mo.linebreakstyle if mo.linebreakstyle
263
+ symbol =
264
+ if value.nil? || value == ""
265
+ Plurimath::Math::Symbols::Symbol.new(nil)
266
+ else
267
+ mathml_symbol(value)
268
+ end
269
+ symbol = Plurimath::Math::Symbols::Symbol.new(value) if symbol.nil? || symbol.is_a?(Array)
270
+ return Plurimath::Math::Function::Linebreak.new(
271
+ symbol,
272
+ attributes
273
+ )
274
+ end
275
+
276
+ if value.nil? || value == ""
277
+ result = Plurimath::Math::Symbols::Symbol.new(nil)
278
+ options = {}
279
+ options[:rspace] = mo.rspace if mo.respond_to?(:rspace) && mo.rspace
280
+ result.options = options if options.any?
281
+ return result
282
+ end
283
+
284
+ result = mathml_symbol(value)
285
+ result = Plurimath::Math::Symbols::Symbol.new(value) if result.nil? || result.is_a?(Array)
286
+ if result.instance_of?(Plurimath::Math::Symbols::Symbol)
287
+ options = {}
288
+ options[:rspace] = mo.rspace if mo.respond_to?(:rspace) && mo.rspace
289
+ result.options = options if options.any?
290
+ end
291
+ apply_font_style(mo, result)
292
+ end
293
+
294
+ # MathML element: <mn> - number
295
+ def mn_to_number(mn)
296
+ value = text_value(mn.value) || ""
297
+ Plurimath::Math::Number.new(value)
298
+ end
299
+
300
+ # MathML element: <mtext> - text
301
+ def mtext_to_text(mtext)
302
+ text_obj = Plurimath::Math::Function::Text.new
303
+ text_obj.value = text_value(mtext.value)
304
+ text_obj
305
+ end
306
+
307
+ # MathML element: <none>
308
+ def none_to_none
309
+ nil
310
+ end
311
+
312
+ # MathML element: <ms> - string
313
+ def ms_to_ms(ms)
314
+ children = ordered_children(ms)
315
+ text_content = children.filter_map { |child| extract_ms_text(child) }.join(" ")
316
+ Plurimath::Math::Function::Ms.new(text_content.empty? ? nil : text_content)
317
+ end
318
+
319
+ # MathML element: <mfrac> numerator denominator
320
+ def mfrac_to_frac(frac)
321
+ children = content_children(frac)
322
+ numerator = mml_to_plurimath(children[0])
323
+ denominator = mml_to_plurimath(children[1])
324
+ options = {}
325
+ options[:linethickness] = frac.linethickness if frac.linethickness
326
+ options[:bevelled] = frac.bevelled if frac.bevelled
327
+ Plurimath::Math::Function::Frac.new(numerator, denominator, options)
328
+ end
329
+
330
+ # MathML element: <msqrt> - square root
331
+ def msqrt_to_sqrt(sqrt)
332
+ children = content_children(sqrt)
333
+ radicand = children.map { |child| mml_to_plurimath(child) }.compact
334
+ radicand = radicand.first if radicand.size == 1
335
+ Plurimath::Math::Function::Sqrt.new(radicand)
336
+ end
337
+
338
+ # MathML element: <mroot> radicand index
339
+ def mroot_to_root(root)
340
+ children = content_children(root)
341
+ radicand = filter_child(mml_to_plurimath(children[0]))
342
+ index = filter_child(mml_to_plurimath(children[1]))
343
+ Plurimath::Math::Function::Root.new(index, radicand)
344
+ end
345
+
346
+ # MathML element: <mphantom> - phantom
347
+ def mphantom_to_phantom(phantom)
348
+ children = ordered_children(phantom)
349
+ content = children.map { |child| mml_to_plurimath(child) }.compact
350
+ content_obj = wrap_children(content)
351
+ content_obj = normalize_phantom_child(content_obj)
352
+ Plurimath::Math::Function::Phantom.new(content_obj)
353
+ end
354
+
355
+ # MathML element: <menclose> - enclose
356
+ def menclose_to_menclose(enclose)
357
+ children = ordered_children(enclose)
358
+ content = children.map { |child| mml_to_plurimath(child) }.compact
359
+ notation = enclose.notation
360
+ content_obj = content.size == 1 ? content.first : Plurimath::Math::Formula.new(content)
361
+ Plurimath::Math::Function::Menclose.new(notation, content_obj)
362
+ end
363
+
364
+ # MathML element: <merror> - error
365
+ def merror_to_merror(error)
366
+ children = ordered_children(error)
367
+ content = children.map { |child| mml_to_plurimath(child) }.compact
368
+ Plurimath::Math::Function::Merror.new(wrap_children(content))
369
+ end
370
+
371
+ # MathML element: <mstyle> - style
372
+ def mstyle_to_mstyle(style)
373
+ children = ordered_children(style)
374
+ content = children.map { |child| mml_to_plurimath(child) }.compact
375
+ content_obj = wrap_children(content)
376
+
377
+ has_color = style.respond_to?(:mathcolor) && style.mathcolor && !style.mathcolor.empty?
378
+ has_variant = style.respond_to?(:mathvariant) && style.mathvariant && !style.mathvariant.empty?
379
+
380
+ if has_color
381
+ content_obj = Plurimath::Math::Function::Color.new(
382
+ Plurimath::Math::Function::Text.new(style.mathcolor),
383
+ content_obj,
384
+ )
385
+ end
386
+
387
+ if has_variant
388
+ font_class = Plurimath::Utility::FONT_STYLES[style.mathvariant.to_sym]
389
+ return font_class.new(content_obj, style.mathvariant) if font_class
390
+ end
391
+
392
+ return content_obj if has_color
393
+
394
+ fill_ternary_third_values(content)
395
+ Plurimath::Math::Formula::Mstyle.new(
396
+ content,
397
+ display_style: boolean_to_displaystyle(style.displaystyle),
398
+ )
399
+ end
400
+
401
+ # MathML element: <mfenced> - fenced
402
+ def mfenced_to_fenced(fenced)
403
+ children = ordered_children(fenced)
404
+ content = children.map { |child| mml_to_plurimath(child) }.compact.map { |child| filter_child(child) }
405
+ open_value = fenced.open || default_fenced_open(fenced)
406
+ close_value = fenced.close || default_fenced_close(fenced)
407
+
408
+ Plurimath::Math::Function::Fenced.new(
409
+ resolve_paren(open_value),
410
+ content,
411
+ resolve_paren(close_value),
412
+ { separators: fenced.separators }.compact,
413
+ )
414
+ end
415
+
416
+ # MathML element: <mtable> - table
417
+ def mtable_to_table(table)
418
+ table_content = ordered_children(table).filter_map { |row| mml_to_plurimath(row) }
419
+ table_obj = Plurimath::Math::Function::Table.new(table_content)
420
+ # Set table attributes
421
+ table_obj.frame = table.frame if table.respond_to?(:frame) && table.frame
422
+ table_obj.rowlines = table.rowlines if table.respond_to?(:rowlines) && table.rowlines
423
+ table_obj.columnlines = table.columnlines if table.respond_to?(:columnlines) && table.columnlines
424
+ table_obj
425
+ end
426
+
427
+ # MathML element: <mtr> - table row
428
+ def mtr_to_tr(tr)
429
+ cells = ordered_children(tr).map { |cell| mml_to_plurimath(cell) }
430
+ Plurimath::Math::Function::Tr.new(cells)
431
+ end
432
+
433
+ # MathML element: <mtd> - table cell
434
+ def mtd_to_td(td)
435
+ children = ordered_children(td)
436
+ content = children.map { |child| mml_to_plurimath(child) }.compact
437
+ Plurimath::Math::Function::Td.new(content)
438
+ end
439
+
440
+ # MathML element: <mlongdiv> - long division
441
+ def mlongdiv_to_longdiv(longdiv)
442
+ children = ordered_children(longdiv)
443
+ content = children.map { |child| mml_to_plurimath(child) }.compact
444
+ Plurimath::Math::Function::Longdiv.new(content)
445
+ end
446
+
447
+ # MathML element: <mstack> - stacked
448
+ def mstack_to_stackrel(stack)
449
+ children = ordered_children(stack)
450
+ content = children.map { |child| mml_to_plurimath(child) }.compact
451
+ Plurimath::Math::Function::Stackrel.new(wrap_children(content))
452
+ end
453
+
454
+ # MathML element: <msrow> - stack row
455
+ def msrow_to_formula(msrow)
456
+ children = ordered_children(msrow)
457
+ content = children.map { |child| mml_to_plurimath(child) }.compact
458
+ return nil if content.empty?
459
+
460
+ Plurimath::Math::Formula.new(content)
461
+ end
462
+
463
+ # MathML element: <msgroup> - group
464
+ def msgroup_to_msgroup(sgroup)
465
+ content = ordered_children(sgroup).filter_map do |child|
466
+ if child.is_a?(String)
467
+ next if child.strip.empty?
468
+
469
+ Plurimath::Math::Function::Text.new(child)
470
+ else
471
+ mml_to_plurimath(child)
472
+ end
473
+ end
474
+ Plurimath::Math::Function::Msgroup.new(content)
475
+ end
476
+
477
+ # MathML element: <msline> - line
478
+ def msline_to_msline(sline)
479
+ children = ordered_children(sline)
480
+ content = children.map { |child| mml_to_plurimath(child) }.compact
481
+ Plurimath::Math::Function::Msline.new(wrap_children(content))
482
+ end
483
+
484
+ # MathML element: <mpadded> - padded
485
+ def mpadded_to_mpadded(padded)
486
+ children = ordered_children(padded)
487
+ content = children.map { |child| mml_to_plurimath(child) }.compact
488
+ options = {}
489
+ options[:height] = padded.height if padded.height
490
+ options[:depth] = padded.depth if padded.depth
491
+ options[:width] = padded.width if padded.width
492
+ Plurimath::Math::Function::Mpadded.new(wrap_children(content), options)
493
+ end
494
+
495
+ # MathML element: <mglyph> - glyph
496
+ def mglyph_to_mglyph(glyph)
497
+ src = glyph.src
498
+ alt = glyph.alt
499
+ index = glyph.index
500
+ Plurimath::Math::Function::Mglyph.new(src: src, alt: alt, index: index)
501
+ end
502
+
503
+ # MathML element: <mmultiscripts> - multiscripts
504
+ def mmultiscripts_to_multiscript(multiscripts)
505
+ children = ordered_children(multiscripts)
506
+ return Plurimath::Math::Function::Multiscript.new if children.empty?
507
+
508
+ base = convert_multiscript_child(children[0])
509
+ has_prescripts = !multiscripts.mprescripts_value.nil?
510
+ element_order = multiscripts.element_order
511
+ mpre_index = element_order.index { |el| el.name == "mprescripts" }
512
+
513
+ if has_prescripts && mpre_index
514
+ post_element_count = multiscript_post_element_count(element_order, mpre_index)
515
+ post_children, pre_children = split_multiscript_children(children, post_element_count)
516
+
517
+ post_subs, post_sups = split_script_pairs(post_children)
518
+ pre_subs, pre_sups = split_script_pairs(pre_children, compact: true)
519
+ base_with_posts = attach_postscripts(base, post_subs, post_sups)
520
+ Plurimath::Math::Function::Multiscript.new(base_with_posts, pre_subs, pre_sups)
521
+ else
522
+ remaining = convert_multiscript_children(children[1..-1])
523
+ subscripts, superscripts = split_script_pairs(remaining, compact: true)
524
+ base_with_posts = attach_postscripts(base, subscripts, superscripts)
525
+ Plurimath::Math::Function::Multiscript.new(base_with_posts)
526
+ end
527
+ end
528
+
529
+ # MathML element: <mlabeledtr> - labeled row
530
+ def mlabeledtr_to_mlabeledtr(labeledtr)
531
+ children = ordered_children(labeledtr)
532
+ content = children.map { |child| mml_to_plurimath(child) }.compact
533
+ label = labeledtr.id && Plurimath::Math::Function::Text.new(labeledtr.id)
534
+ Plurimath::Math::Function::Mlabeledtr.new(content, label)
535
+ end
536
+
537
+ # MathML element: <semantics> - semantics
538
+ def semantics_to_semantics(semantics)
539
+ children = ordered_children(semantics)
540
+ content = filter_child(wrap_children(children.map { |child| mml_to_plurimath(child) }.compact))
541
+ annotations = []
542
+ annotations += build_annotation_entries(semantics.annotation_value, :annotation)
543
+ annotations += build_annotation_entries(semantics.annotation_xml_value, :"annotation-xml")
544
+ Plurimath::Math::Function::Semantics.new(content, annotations.empty? ? nil : annotations)
545
+ end
546
+
547
+ # MathML element: <mscarries> - carries
548
+ def mscarries_to_scarries(scarries)
549
+ children = ordered_children(scarries)
550
+ content = children.map { |child| mml_to_plurimath(child) }.compact
551
+ Plurimath::Math::Function::Scarries.new(wrap_children(content))
552
+ end
553
+
554
+ # MathML element: <mscarry> - carry
555
+ # The legacy MathML flow treated <mscarry> as a layout marker inside
556
+ # <mscarries>, so it was removed before AST construction.
557
+ def mscarry_to_mscarry(_scarry)
558
+ nil
559
+ end
560
+
561
+ # MathML element: <mprescripts> - prescripts marker
562
+ # This is a marker element, returns nil (filtered out)
563
+ def mprescripts_to_prescripts(_)
564
+ nil
565
+ end
566
+
567
+ # MathML element: <mspace> - space
568
+ def mspace_to_space(space)
569
+ if space.linebreak && !space.linebreak.empty?
570
+ Plurimath::Math::Function::Linebreak.new(
571
+ nil,
572
+ { linebreak: space.linebreak },
573
+ )
574
+ end
575
+ end
576
+
577
+ def text_node_to_plurimath(text)
578
+ return nil if text.nil? || text.match?(/\A[[:space:]]*\z/)
579
+
580
+ mathml_symbol(text)
581
+ end
582
+ end
583
+ end
584
+ end