mml 2.1.0 → 2.2.0

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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +14 -22
  3. data/README.adoc +205 -153
  4. data/lib/mml/v3/common_attributes.rb +22 -0
  5. data/lib/mml/v3/configuration.rb +105 -0
  6. data/lib/mml/v3/maction.rb +22 -0
  7. data/lib/mml/v3/maligngroup.rb +20 -0
  8. data/lib/mml/v3/malignmark.rb +20 -0
  9. data/lib/mml/v3/math.rb +20 -0
  10. data/lib/mml/v3/menclose.rb +21 -0
  11. data/lib/mml/v3/merror.rb +19 -0
  12. data/lib/mml/v3/mfenced.rb +28 -0
  13. data/lib/mml/v3/mfrac.rb +27 -0
  14. data/lib/mml/v3/mfraction.rb +27 -0
  15. data/lib/mml/v3/mglyph.rb +46 -0
  16. data/lib/mml/v3/mi.rb +38 -0
  17. data/lib/mml/v3/mlabeledtr.rb +31 -0
  18. data/lib/mml/v3/mlongdiv.rb +25 -0
  19. data/lib/mml/v3/mmultiscripts.rb +27 -0
  20. data/lib/mml/v3/mn.rb +38 -0
  21. data/lib/mml/v3/mo.rb +86 -0
  22. data/lib/mml/v3/mover.rb +23 -0
  23. data/lib/mml/v3/mpadded.rb +29 -0
  24. data/lib/mml/v3/mphantom.rb +19 -0
  25. data/lib/mml/v3/mprescripts.rb +18 -0
  26. data/lib/mml/v3/mroot.rb +19 -0
  27. data/lib/mml/v3/mrow.rb +25 -0
  28. data/lib/mml/v3/ms.rb +43 -0
  29. data/lib/mml/v3/mscarries.rb +27 -0
  30. data/lib/mml/v3/mscarry.rb +23 -0
  31. data/lib/mml/v3/msgroup.rb +25 -0
  32. data/lib/mml/v3/msline.rb +28 -0
  33. data/lib/mml/v3/mspace.rb +58 -0
  34. data/lib/mml/v3/msqrt.rb +19 -0
  35. data/lib/mml/v3/msrow.rb +21 -0
  36. data/lib/mml/v3/mstack.rb +27 -0
  37. data/lib/mml/v3/mstyle.rb +211 -0
  38. data/lib/mml/v3/msub.rb +21 -0
  39. data/lib/mml/v3/msubsup.rb +23 -0
  40. data/lib/mml/v3/msup.rb +21 -0
  41. data/lib/mml/v3/mtable.rb +61 -0
  42. data/lib/mml/v3/mtd.rb +23 -0
  43. data/lib/mml/v3/mtext.rb +38 -0
  44. data/lib/mml/v3/mtr.rb +29 -0
  45. data/lib/mml/v3/munder.rb +25 -0
  46. data/lib/mml/v3/munderover.rb +25 -0
  47. data/lib/mml/v3/namespace.rb +10 -0
  48. data/lib/mml/v3/none.rb +18 -0
  49. data/lib/mml/v3/semantics.rb +19 -0
  50. data/lib/mml/v3/version.rb +7 -0
  51. data/lib/mml/v3.rb +85 -0
  52. data/lib/mml/v4/a.rb +31 -0
  53. data/lib/mml/v4/common_attributes.rb +26 -0
  54. data/lib/mml/v4/configuration.rb +106 -0
  55. data/lib/mml/v4/maction.rb +30 -0
  56. data/lib/mml/v4/maligngroup.rb +28 -0
  57. data/lib/mml/v4/malignmark.rb +28 -0
  58. data/lib/mml/v4/math.rb +27 -0
  59. data/lib/mml/v4/menclose.rb +29 -0
  60. data/lib/mml/v4/merror.rb +27 -0
  61. data/lib/mml/v4/mfenced.rb +36 -0
  62. data/lib/mml/v4/mfrac.rb +35 -0
  63. data/lib/mml/v4/mfraction.rb +31 -0
  64. data/lib/mml/v4/mglyph.rb +46 -0
  65. data/lib/mml/v4/mi.rb +41 -0
  66. data/lib/mml/v4/mlabeledtr.rb +34 -0
  67. data/lib/mml/v4/mlongdiv.rb +29 -0
  68. data/lib/mml/v4/mmultiscripts.rb +35 -0
  69. data/lib/mml/v4/mn.rb +40 -0
  70. data/lib/mml/v4/mo.rb +86 -0
  71. data/lib/mml/v4/mover.rb +31 -0
  72. data/lib/mml/v4/mpadded.rb +37 -0
  73. data/lib/mml/v4/mphantom.rb +27 -0
  74. data/lib/mml/v4/mprescripts.rb +22 -0
  75. data/lib/mml/v4/mroot.rb +27 -0
  76. data/lib/mml/v4/mrow.rb +31 -0
  77. data/lib/mml/v4/ms.rb +45 -0
  78. data/lib/mml/v4/mscarries.rb +31 -0
  79. data/lib/mml/v4/mscarry.rb +27 -0
  80. data/lib/mml/v4/msgroup.rb +29 -0
  81. data/lib/mml/v4/msline.rb +32 -0
  82. data/lib/mml/v4/mspace.rb +60 -0
  83. data/lib/mml/v4/msqrt.rb +27 -0
  84. data/lib/mml/v4/msrow.rb +29 -0
  85. data/lib/mml/v4/mstack.rb +35 -0
  86. data/lib/mml/v4/mstyle.rb +197 -0
  87. data/lib/mml/v4/msub.rb +29 -0
  88. data/lib/mml/v4/msubsup.rb +31 -0
  89. data/lib/mml/v4/msup.rb +29 -0
  90. data/lib/mml/v4/mtable.rb +66 -0
  91. data/lib/mml/v4/mtd.rb +31 -0
  92. data/lib/mml/v4/mtext.rb +40 -0
  93. data/lib/mml/v4/mtr.rb +36 -0
  94. data/lib/mml/v4/munder.rb +33 -0
  95. data/lib/mml/v4/munderover.rb +33 -0
  96. data/lib/mml/v4/namespace.rb +10 -0
  97. data/lib/mml/v4/none.rb +22 -0
  98. data/lib/mml/v4/opal_setup.rb.erb +6 -0
  99. data/lib/mml/v4/semantics.rb +23 -0
  100. data/lib/mml/v4/version.rb +7 -0
  101. data/lib/mml/v4.rb +81 -0
  102. data/lib/mml/version.rb +1 -1
  103. data/lib/mml.rb +24 -90
  104. metadata +101 -49
  105. data/lib/mml/common_attributes.rb +0 -21
  106. data/lib/mml/configuration.rb +0 -103
  107. data/lib/mml/maction.rb +0 -20
  108. data/lib/mml/maligngroup.rb +0 -18
  109. data/lib/mml/malignmark.rb +0 -18
  110. data/lib/mml/math.rb +0 -18
  111. data/lib/mml/menclose.rb +0 -19
  112. data/lib/mml/merror.rb +0 -17
  113. data/lib/mml/mfenced.rb +0 -26
  114. data/lib/mml/mfrac.rb +0 -25
  115. data/lib/mml/mfraction.rb +0 -25
  116. data/lib/mml/mglyph.rb +0 -44
  117. data/lib/mml/mi.rb +0 -36
  118. data/lib/mml/mlabeledtr.rb +0 -29
  119. data/lib/mml/mlongdiv.rb +0 -23
  120. data/lib/mml/mmultiscripts.rb +0 -25
  121. data/lib/mml/mn.rb +0 -36
  122. data/lib/mml/mo.rb +0 -84
  123. data/lib/mml/mover.rb +0 -21
  124. data/lib/mml/mpadded.rb +0 -27
  125. data/lib/mml/mphantom.rb +0 -17
  126. data/lib/mml/mprescripts.rb +0 -16
  127. data/lib/mml/mroot.rb +0 -17
  128. data/lib/mml/mrow.rb +0 -23
  129. data/lib/mml/ms.rb +0 -41
  130. data/lib/mml/mscarries.rb +0 -25
  131. data/lib/mml/mscarry.rb +0 -21
  132. data/lib/mml/msgroup.rb +0 -23
  133. data/lib/mml/msline.rb +0 -26
  134. data/lib/mml/mspace.rb +0 -56
  135. data/lib/mml/msqrt.rb +0 -17
  136. data/lib/mml/msrow.rb +0 -19
  137. data/lib/mml/mstack.rb +0 -25
  138. data/lib/mml/mstyle.rb +0 -209
  139. data/lib/mml/msub.rb +0 -19
  140. data/lib/mml/msubsup.rb +0 -21
  141. data/lib/mml/msup.rb +0 -19
  142. data/lib/mml/mtable.rb +0 -59
  143. data/lib/mml/mtd.rb +0 -21
  144. data/lib/mml/mtext.rb +0 -36
  145. data/lib/mml/mtr.rb +0 -27
  146. data/lib/mml/munder.rb +0 -23
  147. data/lib/mml/munderover.rb +0 -23
  148. data/lib/mml/namespace.rb +0 -9
  149. data/lib/mml/none.rb +0 -16
  150. data/lib/mml/semantics.rb +0 -17
  151. /data/lib/mml/{opal_setup.rb.erb → v3/opal_setup.rb.erb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0edf00bb97f74177c28a416132046bd265a61a8720de3a0a9d1b9515721170db
4
- data.tar.gz: 9b11f8ecc3869145617584f9afb422ae3ced36db62994f89750483f039e1bb99
3
+ metadata.gz: efbc2e4b8156916d34e524d2f7b6456168f8ef4a0c0c2ade8fbfb2b02bdc2efb
4
+ data.tar.gz: 8f826d13adfc7f0b556aa5ab6f32f8e8390bf06002a76570845605af7282058a
5
5
  SHA512:
6
- metadata.gz: ce86364277e4aae63f893d64ccbabb3752beb2acb11f634caaab20fb01b2768902180eab714dbcb69ab91fcc183778004dcbbf50fb3da4f976071effc687d293
7
- data.tar.gz: b3ae2643d010604f0a293fbf35ca7d5ad776055a67b9c90293e4fd7a97a7b8ae70735526c5cd3bf2c96cd2528f3d27048069a481590d6119f7fed7a2d9a365da
6
+ metadata.gz: 694df3fcc6b3611d6bac5b5c380b420b91823e697b219ba93a5d9ec22c70d910f8ecef6fef6c419b1acf80660d726259056717380faf8e77d235879b056e3c85
7
+ data.tar.gz: 72b8f2d1960adec6a3777e43c792cf3d1d3aa4658be47e5bdbf9f7e5cd428ac1dbd3990d7c1685ae4f6ed1a90566b80932046c15424bf4b6a94efa4e39360a29
data/.rubocop_todo.yml CHANGED
@@ -1,20 +1,12 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-03-26 05:47:46 UTC using RuboCop version 1.86.0.
3
+ # on 2026-03-31 03:44:08 UTC using RuboCop version 1.86.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 1
10
- # This cop supports safe autocorrection (--autocorrect).
11
- # Configuration parameters: EnforcedStyle, IndentationWidth.
12
- # SupportedStyles: with_first_argument, with_fixed_indentation
13
- Layout/ArgumentAlignment:
14
- Exclude:
15
- - 'spec/mml_spec.rb'
16
-
17
- # Offense count: 5
9
+ # Offense count: 3
18
10
  # This cop supports safe autocorrection (--autocorrect).
19
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
20
12
  # URISchemes: http, https
@@ -25,28 +17,28 @@ Layout/LineLength:
25
17
  - 'spec/spec_helper.rb'
26
18
 
27
19
  # Offense count: 1
28
- # This cop supports safe autocorrection (--autocorrect).
29
- # Configuration parameters: AllowInHeredoc.
30
- Layout/TrailingWhitespace:
31
- Exclude:
32
- - 'spec/mml_spec.rb'
20
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
21
+ # AllowedMethods: refine
22
+ Metrics/BlockLength:
23
+ Max: 27
33
24
 
34
- # Offense count: 1
25
+ # Offense count: 3
35
26
  # Configuration parameters: Prefixes, AllowedPatterns.
36
27
  # Prefixes: when, with, without
37
28
  RSpec/ContextWording:
38
29
  Exclude:
30
+ - 'spec/mml4_spec.rb'
39
31
  - 'spec/mml_spec.rb'
40
32
 
41
33
  # Offense count: 1
42
- RSpec/PendingWithoutReason:
34
+ RSpec/MultipleDescribes:
43
35
  Exclude:
44
36
  - 'spec/mml_spec.rb'
45
37
 
46
38
  # Offense count: 1
47
- # This cop supports safe autocorrection (--autocorrect).
48
- # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
49
- # SupportedStyles: single_quotes, double_quotes
50
- Style/StringLiterals:
39
+ # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
40
+ # SupportedInflectors: default, active_support
41
+ RSpec/SpecFilePathFormat:
51
42
  Exclude:
52
- - 'spec/mml_spec.rb'
43
+ - '**/spec/routing/**/*'
44
+ - 'spec/mml4_spec.rb'
data/README.adoc CHANGED
@@ -5,181 +5,269 @@
5
5
 
6
6
  == Purpose
7
7
 
8
- Mml provides MathML 3 XML parsing and serialization for Ruby. It maps the full
9
- MathML element set into Ruby model classes using the
8
+ Mml provides MathML 3 and MathML 4 XML parsing and serialization for Ruby. It maps
9
+ the full MathML element set into Ruby model classes using the
10
10
  https://github.com/lutaml/lutaml-model[lutaml-model] framework and is used by
11
11
  https://github.com/plurimath/plurimath[Plurimath] for mathematical formula
12
12
  representation.
13
13
 
14
14
  Key features:
15
15
 
16
- * **MathML 3 element support**: 43 elements including presentation markup,
17
- tables, scripts, fractions, and more
18
- * **Namespace handling**: Default `xmlns`, prefixed `mml:`, and namespace-less
19
- input
20
- * **Round-trip fidelity**: Parse XML to an object graph, modify, and serialize
21
- back
16
+ * **Dual MathML version support**: Separate class hierarchies for MathML 3 and
17
+ MathML 4 with explicit version selection
18
+ * **Round-trip fidelity**: Parse XML to an object graph, modify, and serialize back
19
+ * **Namespace handling**: Default `xmlns`, prefixed `mml:`, and namespace-less input
22
20
  * **Opal support**: Runs in the browser via Ruby-to-JavaScript compilation
23
21
 
24
22
  == Installation
25
23
 
26
- Add to your application's Gemfile:
27
-
28
24
  [source,ruby]
29
25
  ----
30
26
  gem 'mml'
31
27
  ----
32
28
 
33
- Then execute:
34
-
35
29
  [source,bash]
36
30
  ----
37
31
  $ bundle install
32
+ # or
33
+ $ gem install mml
38
34
  ----
39
35
 
40
- Or install directly:
36
+ == Quick start
41
37
 
42
- [source,bash]
38
+ [source,ruby]
43
39
  ----
44
- $ gem install mml
40
+ require "mml"
41
+
42
+ # Parse MathML (defaults to MathML 3)
43
+ math = Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
44
+
45
+ # Serialize back to XML
46
+ math.to_xml
47
+ # => "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>x</mi></math>"
48
+
49
+ # Parse with explicit version
50
+ math4 = Mml.parse(input, version: 4)
45
51
  ----
46
52
 
47
- == Quick start
53
+ == MathML version architecture
54
+
55
+ Mml maintains two parallel class hierarchies under `Mml::V3` and `Mml::V4`.
56
+ Both versions share the same namespace URI (`http://www.w3.org/1998/Math/MathML`)
57
+ for backward compatibility.
48
58
 
49
- === Parse with a namespace (compliant)
59
+ [source]
60
+ ----
61
+ ┌───────────────────────────────────────────┐
62
+ │ Mml │
63
+ │ (aliases Mml::V3 for backward compat) │
64
+ └────────────────────┬──────────────────────┘
65
+
66
+ ┌───────────────────┴────────────────────┐
67
+ │ │
68
+ ┌────┴────┐ ┌────┴────┐
69
+ │ Mml::V3 │ │ Mml::V4 │
70
+ └────┬────┘ └────┬────┘
71
+ │ │
72
+ ┌───────────────┼─────────────────────┐ ┌───────────────┼───────────────┐
73
+ │ ┌────────────┴───────────────┐ │ │ ┌────────────┴────────────┐ │
74
+ │ │ Element classes │ │ │ │ Element classes │ │
75
+ │ │ (Mi, Mn, Mo, Mrow, ...) │ │ │ │ (Mi, Mn, Mo, Mrow, ...) │ │
76
+ │ │ │ │ │ │ + intent, arg, │ │
77
+ │ │ Inherits from │ │ │ │ displaystyle, │ │
78
+ │ │ Lutaml::Model::Serializable│ │ │ │ scriptlevel │ │
79
+ │ └────────────┬───────────────┘ │ │ │ + <a> element │ │
80
+ │ │ │ │ └────────────┬────────────┘ │
81
+ │ ┌────────────┴────────────┐ │ │ ┌────────────┴────────────┐ │
82
+ │ │ CommonAttributes │ │ │ │ CommonAttributes │ │
83
+ │ │ (child element mixin) │ │ │ │ (v4 version) │ │
84
+ │ └─────────────────────────┘ │ | └─────────────────────────┘ |
85
+ └─────────────────────────────────────┘ └───────────────────────────────┘
86
+
87
+ ┌──────────────────┴──────────────────────┐
88
+ │ Lutaml::Model::Serializable │
89
+ │ (XML mapping framework) │
90
+ └─────────────────────────────────────────┘
91
+ ----
50
92
 
51
- `Mml.parse` expects a namespace by default (`namespace_exist: true`). It accepts
52
- both default and prefixed forms:
93
+ === Version selection
53
94
 
54
95
  [source,ruby]
55
96
  ----
56
- require "mml"
97
+ Mml.parse(input) # Default: MathML 3 (Mml::V3)
98
+ Mml.parse(input, version: 3) # Explicit MathML 3
99
+ Mml.parse(input, version: 4) # MathML 4 with intent/arg attributes
100
+ Mml::V4.parse(input) # Direct v4 parsing (no default fallback)
101
+ ----
57
102
 
58
- # Default namespace
59
- math = Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
103
+ === Key differences between MathML 3 and MathML 4
60
104
 
61
- # Prefixed namespace
62
- math = Mml.parse('<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>x</mml:mi></mml:math>')
105
+ [cols="1,2",options="header"]
106
+ |===
107
+ |Feature |MathML 4 additions
108
+ |Universal attributes |`intent`, `arg`, `displaystyle`, `scriptlevel` available on all presentation elements
109
+ |New element |`<a>` hyperlink element with `href`, `hreflang`
110
+ |Deprecated (not serialized) a|
111
+ * `fontfamily`, `fontweight`, `fontstyle`, `fontsize`, `color`, `background` on `mstyle`, `mglyph`, `mspace`
112
+ * `groupalign` on `mtable`, `mtr`, `mlabeledtr`
113
+ * `fence`, `separator` on `mo`
114
+ |Deprecated (recognized but hidden) |`<mlabeledtr>`, `<none>` removed from `CommonAttributes` but classes still exist
115
+ |===
116
+
117
+ === Migration from MathML 3 to MathML 4
118
+
119
+ ==== For gem users (upgrading parsing)
120
+
121
+ If you currently parse MathML and want to use MathML 4 features:
122
+
123
+ [source,ruby]
124
+ ----
125
+ # Change this:
126
+ math = Mml.parse(input)
127
+
128
+ # To this:
129
+ math = Mml.parse(input, version: 4)
130
+ ----
131
+
132
+ ==== For gem users (upgrading serialized output)
133
+
134
+ If you serialize MathML 4 documents and want to ensure compatibility:
135
+
136
+ [source,ruby]
137
+ ----
138
+ # MathML 4 serialization removes deprecated attributes from XML output
139
+ math = Mml.parse(input, version: 4)
140
+ math.to_xml # No fontfamily, fontweight, color, groupalign, etc. in output
141
+ ----
142
+
143
+ ==== For gem extenders (adding custom elements)
144
+
145
+ Custom elements registered via `Configuration.custom_models` are version-specific:
146
+
147
+ [source,ruby]
148
+ ----
149
+ # Register a custom element for MathML 4 only
150
+ Mml::V4::Configuration.custom_models = { Mi => MyCustomMiV4 }
151
+ ----
152
+
153
+ CommonAttributes is version-specific. Elements that exist only in v4 (like `<a>`)
154
+ are automatically handled:
155
+
156
+ [source,ruby]
157
+ ----
158
+ # The <a> element is only in v4 CommonAttributes
159
+ Mml::V4::Mrow.new(a_value: [Mml::V4::A.new(href: "https://...")])
63
160
  ----
64
161
 
65
- === Parse without namespace (non-compliant)
162
+ == Parsing and serialization
66
163
 
67
- Some XML schemas embed MathML without a namespace declaration, which is
68
- non-compliant but common in practice. Pass `namespace_exist: false` to handle
69
- this — the namespace is injected internally before parsing:
164
+ === Parsing
70
165
 
71
166
  [source,ruby]
72
167
  ----
73
- math = Mml.parse("<math><mi>x</mi></math>", namespace_exist: false)
168
+ # Default namespace
169
+ Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
170
+
171
+ # Prefixed namespace
172
+ Mml.parse('<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>x</mml:mi></mml:math>')
173
+
174
+ # No namespace (namespace injected internally)
175
+ Mml.parse("<math><mi>x</mi></math>", namespace_exist: false)
74
176
  ----
75
177
 
76
- === Serialize to XML
178
+ === Serialization
77
179
 
78
180
  [source,ruby]
79
181
  ----
80
- # Default namespace (default)
81
182
  math.to_xml
82
183
  # => "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>x</mi></math>"
83
184
 
84
- # Prefixed namespace
85
185
  math.to_xml(prefix: true)
86
186
  # => "<mml:math xmlns:mml=\"http://www.w3.org/1998/Math/MathML\"><mml:mi>x</mml:mi></mml:math>"
87
187
 
88
- # Omit XML declaration
89
188
  math.to_xml(declaration: false)
189
+ # => "<math xmlns=\"...\"><mi>x</mi></math>"
190
+ ----
191
+
192
+ === Round-trip (parse, modify, serialize)
193
+
194
+ [source,ruby]
90
195
  ----
196
+ math = Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
197
+ math.display = "block"
198
+ math.to_xml
199
+ # => "<math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\"><mi>x</mi></math>"
200
+ ----
201
+
202
+ == Element reference
203
+
204
+ === Element types
91
205
 
92
- == Building MathML with the API
206
+ *Token elements*: `mi`, `mn`, `mo`, `ms`, `mtext`, `mspace`, `mglyph`
93
207
 
94
- === Leaf elements
208
+ *General layout*: `mrow`, `mfrac`, `msqrt`, `mroot`, `mstyle`, `merror`, `mpadded`, `mphantom`, `mfenced`, `menclose`, `maction`
95
209
 
96
- Leaf elements (`Mi`, `Mn`, `Mo`, `Ms`, `Mtext`) hold text content in the `value`
97
- attribute:
210
+ *Script elements*: `msub`, `msup`, `msubsup`, `munder`, `mover`, `munderover`, `mmultiscripts`, `mprescripts`
211
+
212
+ *Table elements*: `mtable`, `mtr`, `mtd`
213
+
214
+ *Row and stack elements*: `mstack`, `msrow`, `mscarries`, `mscarry`, `msline`, `msgroup`, `mlongdiv`
215
+
216
+ *Semantic elements*: `mfraction`, `semantics`
217
+
218
+ *v4 only*: `a` (hyperlink)
219
+
220
+ *Deprecated*: `mlabeledtr`, `none` (classes exist but hidden from CommonAttributes in v4)
221
+
222
+ === Token elements (leaf nodes)
223
+
224
+ Token elements hold text content in the `value` attribute:
98
225
 
99
226
  [source,ruby]
100
227
  ----
101
- # An identifier: <mi>x</mi>
102
228
  Mml::Mi.new(value: "x")
103
-
104
- # A number: <mn>42</mn>
105
229
  Mml::Mn.new(value: "42")
106
-
107
- # An operator: <mo>+</mo>
108
230
  Mml::Mo.new(value: "+")
109
-
110
- # With attributes: <mi mathvariant="bold">x</mi>
111
- Mml::Mi.new(value: "x", mathvariant: "bold")
231
+ Mml::Ms.new(value: "text")
232
+ Mml::Mtext.new(value: "label")
233
+ Mml::Mspace.new(width: "1em")
234
+ Mml::Mglyph.new(alt: "symbol")
112
235
  ----
113
236
 
114
237
  === Container elements
115
238
 
116
- Container elements hold child elements via collection attributes named
117
- `#{tag}_value`. The supported child types come from the `CommonAttributes` model
118
- mixed into container classes.
239
+ Container elements hold child elements via `#{tag}_value` collection attributes:
119
240
 
120
241
  [source,ruby]
121
242
  ----
122
- # <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow>
123
243
  Mml::Mrow.new(
124
244
  mi_value: [Mml::Mi.new(value: "x")],
125
245
  mo_value: [Mml::Mo.new(value: "+")],
126
246
  mn_value: [Mml::Mn.new(value: "1")],
127
247
  )
248
+ # => <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow>
128
249
  ----
129
250
 
130
- === Composing complex expressions
251
+ === Composing expressions
131
252
 
132
- Build an expression tree by nesting elements, then wrap in `Mml::Math`:
253
+ Build an expression tree by nesting elements:
133
254
 
134
255
  [source,ruby]
135
256
  ----
136
- # <math xmlns="http://www.w3.org/1998/Math/MathML">
137
- # <mfrac><mi>a</mi><mi>b</mi></mfrac>
138
- # </math>
139
- math = Mml::Math.new(
257
+ Mml::Math.new(
140
258
  mfrac_value: [
141
259
  Mml::Mfrac.new(
142
260
  mi_value: [Mml::Mi.new(value: "a"), Mml::Mi.new(value: "b")],
143
261
  ),
144
262
  ],
145
263
  )
146
-
147
- math.to_xml
148
- ----
149
-
150
- === Fractions, subscripts, and superscripts
151
-
152
- [source,ruby]
153
- ----
154
- # <msup><mi>x</mi><mn>2</mn></msup> — x²
155
- Mml::Msup.new(mi_value: [Mml::Mi.new(value: "x")],
156
- mn_value: [Mml::Mn.new(value: "2")])
157
-
158
- # <msub><mi>a</mi><mn>1</mn></msup> — a₁
159
- Mml::Msub.new(mi_value: [Mml::Mi.new(value: "a")],
160
- mn_value: [Mml::Mn.new(value: "1")])
161
-
162
- # <msubsup><mi>b</mi><mn>1</mn><mn>2</mn></msubsup> — b₁²
163
- Mml::Msubsup.new(mi_value: [Mml::Mi.new(value: "b")],
164
- mn_value: [Mml::Mn.new(value: "1"),
165
- Mml::Mn.new(value: "2")])
166
-
167
- # <mfrac linethickness="0"><mi>a</mi><mi>b</mi></mfrac> — a/b
168
- Mml::Mfrac.new(
169
- linethickness: "0",
170
- mi_value: [Mml::Mi.new(value: "a"), Mml::Mi.new(value: "b")],
171
- )
264
+ # => <math><mfrac><mi>a</mi><mi>b</mi></mfrac></math>
172
265
  ----
173
266
 
174
267
  === Tables
175
268
 
176
- `Mtable` uses typed `mtr_value` and `mlabeledtr_value` attributes for its rows:
177
-
178
269
  [source,ruby]
179
270
  ----
180
- # <mtable>
181
- # <mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr>
182
- # </mtable>
183
271
  Mml::Mtable.new(
184
272
  mtr_value: [
185
273
  Mml::Mtr.new(
@@ -192,112 +280,76 @@ Mml::Mtable.new(
192
280
  )
193
281
  ----
194
282
 
195
- === Parsing, modifying, and re-serializing
196
-
197
- The object graph is fully mutable — parse, modify attributes or children, then
198
- serialize back:
283
+ === Hyperlinks (MathML 4 only)
199
284
 
200
285
  [source,ruby]
201
286
  ----
202
- math = Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
203
-
204
- # Modify the root element's display attribute
205
- math.display = "block"
206
-
207
- # Serialize with the changes
208
- math.to_xml
209
- # => "<math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"block\">...</math>"
287
+ Mml::V4::A.new(
288
+ href: "https://example.com",
289
+ hreflang: "en",
290
+ mi_value: [Mml::V4::Mi.new(value: "click")]
291
+ )
292
+ # => <a href="https://example.com" hreflang="en"><mi>click</mi></a>
210
293
  ----
211
294
 
212
- == Architecture
213
-
214
- Mml follows a data mapper pattern where each MathML element is a Ruby class
215
- under the `Mml::` namespace, inheriting from `Lutaml::Model::Serializable`.
295
+ == Internal architecture
216
296
 
217
- === Element classes
297
+ === Element class patterns
218
298
 
219
- Two element patterns:
299
+ All element classes inherit from `Lutaml::Model::Serializable`:
220
300
 
221
- * *Leaf elements* (`Mi`, `Mn`, `Mo`, `Ms`, `Mtext`, `Mspace`): hold text content
222
- via `map_content to: :value`
223
- * *Container elements* (`Math`, `Mrow`, `Mfrac`, `Mtable`, `Msubsup`): hold
224
- child elements via `mixed_content`
301
+ * *Leaf elements*: use `map_content to: :value` for text content
302
+ * *Container elements*: use `mixed_content` for child elements
225
303
 
226
304
  [source,ruby]
227
305
  ----
228
- # Leaf element — text content
229
- module Mml
230
- class Mi < Lutaml::Model::Serializable
231
- attribute :value, :string
232
-
233
- xml do
234
- namespace Namespace
235
- element "mi"
236
- map_content to: :value
237
- end
306
+ # Leaf — text content
307
+ class Mi < Lutaml::Model::Serializable
308
+ attribute :value, :string
309
+ xml do
310
+ element "mi"
311
+ map_content to: :value
238
312
  end
239
313
  end
240
314
 
241
- # Container element — child elements
242
- module Mml
243
- class Mrow < Lutaml::Model::Serializable
244
- xml do
245
- namespace Namespace
246
- element "mrow"
247
- mixed_content
248
- end
315
+ # Container — child elements
316
+ class Mrow < Lutaml::Model::Serializable
317
+ xml do
318
+ element "mrow"
319
+ mixed_content
249
320
  end
250
321
  end
251
322
  ----
252
323
 
253
324
  === CommonAttributes
254
325
 
255
- Container elements that can hold arbitrary MathML children use a shared
256
- `CommonAttributes` model.
257
-
258
- The classes that receive it are listed in
326
+ Container elements that accept arbitrary MathML children import `CommonAttributes`,
327
+ which defines `#{tag}_value` collection attributes for all supported child elements.
328
+ The list of classes that receive this mixin is in
259
329
  `Configuration::COMMON_ATTRIBUTES_CLASSES`.
260
330
 
261
331
  === Namespace
262
332
 
263
- All elements declare `namespace Namespace` using the MathML namespace URI
264
- (`http://www.w3.org/1998/Math/MathML`) with prefix `mml`. The `Mml.parse`
265
- method handles three input forms:
333
+ All elements use the MathML namespace URI (`http://www.w3.org/1998/Math/MathML`).
334
+ Three input forms are supported:
266
335
 
267
336
  * Default namespace: `<math xmlns="http://www.w3.org/1998/Math/MathML">`
268
337
  * Prefixed: `<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML">`
269
- * No namespace: handled by injecting the namespace before parsing
338
+ * No namespace: namespace is injected before parsing when `namespace_exist: false`
270
339
 
271
- === Configuration
340
+ == Configuration
272
341
 
273
342
  [source,ruby]
274
343
  ----
275
- # Switch XML adapter
344
+ # Switch XML adapter (default: :ox, also supports :nokogiri)
276
345
  Mml::Configuration.adapter = :nokogiri
277
346
 
278
347
  # Register custom model replacements
279
348
  Mml::Configuration.custom_models = { Mi => MyCustomMi }
280
349
  ----
281
350
 
282
- == Supported elements
283
-
284
- Mml supports the following MathML 3 presentation elements:
285
-
286
- `math`, `mi`, `mn`, `mo`, `ms`, `mtext`, `mspace`, `mglyph`
287
- `mrow`, `mfrac`, `msqrt`, `mroot`, `mstyle`, `merror`, `mpadded`,
288
- `mphantom`, `mfenced`, `menclose`, `maction`
289
- `msub`, `msup`, `msubsup`, `munder`, `mover`, `munderover`,
290
- `mmultiscripts`, `mprescripts`
291
- `mtable`, `mtr`, `mtd`, `mlabeledtr`
292
- `mstack`, `msrow`, `mscarries`, `mscarry`, `msline`, `msgroup`, `mlongdiv`
293
- `mfraction`, `semantics`, `none`
294
-
295
351
  == Development
296
352
 
297
- After checking out the repo, run `bin/setup` to install dependencies. Then run
298
- `rake` to run the tests and linter. You can also run `bin/console` for an
299
- interactive prompt.
300
-
301
353
  [source,bash]
302
354
  ----
303
355
  rake # Run specs + rubocop
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mml
4
+ module V3
5
+ Mml::V3::Configuration::SUPPORTED_TAGS.each do |tag|
6
+ autoload(tag.to_s.capitalize.to_sym, "mml/v3/#{tag}.rb")
7
+ end
8
+
9
+ class CommonAttributes < Lutaml::Model::Serializable
10
+ Mml::V3::Configuration::SUPPORTED_TAGS.each do |tag|
11
+ attribute :"#{tag}_value", Mml::V3.const_get(tag.to_s.capitalize),
12
+ collection: true
13
+ end
14
+
15
+ xml do
16
+ Mml::V3::Configuration::SUPPORTED_TAGS.each do |tag|
17
+ map_element tag, to: :"#{tag}_value"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end