mml 2.2.0 → 2.3.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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +14 -4
  3. data/CLAUDE.md +79 -0
  4. data/README.adoc +226 -83
  5. data/lib/mml/base/deprecated_font_attributes.rb +31 -0
  6. data/lib/mml/base/maction.rb +28 -0
  7. data/lib/mml/base/maligngroup.rb +26 -0
  8. data/lib/mml/base/malignmark.rb +26 -0
  9. data/lib/mml/base/math.rb +23 -0
  10. data/lib/mml/base/menclose.rb +27 -0
  11. data/lib/mml/base/merror.rb +25 -0
  12. data/lib/mml/base/mfenced.rb +34 -0
  13. data/lib/mml/base/mfrac.rb +33 -0
  14. data/lib/mml/base/mfraction.rb +33 -0
  15. data/lib/mml/base/mglyph.rb +42 -0
  16. data/lib/mml/base/mi.rb +32 -0
  17. data/lib/mml/base/mlabeledtr.rb +37 -0
  18. data/lib/mml/base/mlongdiv.rb +31 -0
  19. data/lib/mml/base/mmultiscripts.rb +31 -0
  20. data/lib/mml/base/mn.rb +32 -0
  21. data/lib/mml/base/mo.rb +76 -0
  22. data/lib/mml/base/mover.rb +29 -0
  23. data/lib/mml/base/mpadded.rb +35 -0
  24. data/lib/mml/base/mphantom.rb +25 -0
  25. data/lib/mml/base/mprescripts.rb +24 -0
  26. data/lib/mml/base/mroot.rb +25 -0
  27. data/lib/mml/base/mrow.rb +29 -0
  28. data/lib/mml/base/ms.rb +37 -0
  29. data/lib/mml/base/mscarries.rb +33 -0
  30. data/lib/mml/base/mscarry.rb +29 -0
  31. data/lib/mml/base/msgroup.rb +31 -0
  32. data/lib/mml/base/msline.rb +34 -0
  33. data/lib/mml/base/mspace.rb +52 -0
  34. data/lib/mml/base/msqrt.rb +25 -0
  35. data/lib/mml/base/msrow.rb +27 -0
  36. data/lib/mml/base/mstack.rb +33 -0
  37. data/lib/mml/base/mstyle.rb +189 -0
  38. data/lib/mml/base/msub.rb +27 -0
  39. data/lib/mml/base/msubsup.rb +29 -0
  40. data/lib/mml/base/msup.rb +27 -0
  41. data/lib/mml/base/mtable.rb +57 -0
  42. data/lib/mml/base/mtd.rb +29 -0
  43. data/lib/mml/base/mtext.rb +32 -0
  44. data/lib/mml/base/mtr.rb +35 -0
  45. data/lib/mml/base/munder.rb +31 -0
  46. data/lib/mml/base/munderover.rb +31 -0
  47. data/lib/mml/base/none.rb +24 -0
  48. data/lib/mml/base/semantics.rb +23 -0
  49. data/lib/mml/base/v3_only/operator_attrs.rb +24 -0
  50. data/lib/mml/base/v3_only/style_attrs.rb +31 -0
  51. data/lib/mml/base/v3_only/table_attrs.rb +28 -0
  52. data/lib/mml/base/v3_only.rb +11 -0
  53. data/lib/mml/base/v4_attributes.rb +30 -0
  54. data/lib/mml/base.rb +51 -0
  55. data/lib/mml/common_elements.rb +85 -0
  56. data/lib/mml/context_configuration.rb +118 -0
  57. data/lib/mml/context_options.rb +64 -0
  58. data/lib/mml/namespace.rb +10 -0
  59. data/lib/mml/v3/common_elements.rb +8 -0
  60. data/lib/mml/v3/configuration.rb +4 -96
  61. data/lib/mml/v3/maction.rb +2 -14
  62. data/lib/mml/v3/maligngroup.rb +2 -12
  63. data/lib/mml/v3/malignmark.rb +2 -12
  64. data/lib/mml/v3/math.rb +3 -10
  65. data/lib/mml/v3/menclose.rb +3 -14
  66. data/lib/mml/v3/merror.rb +3 -12
  67. data/lib/mml/v3/mfenced.rb +3 -21
  68. data/lib/mml/v3/mfrac.rb +3 -20
  69. data/lib/mml/v3/mfraction.rb +3 -20
  70. data/lib/mml/v3/mglyph.rb +3 -38
  71. data/lib/mml/v3/mi.rb +3 -30
  72. data/lib/mml/v3/mlabeledtr.rb +2 -23
  73. data/lib/mml/v3/mlongdiv.rb +3 -18
  74. data/lib/mml/v3/mmultiscripts.rb +3 -20
  75. data/lib/mml/v3/mn.rb +3 -30
  76. data/lib/mml/v3/mo.rb +4 -78
  77. data/lib/mml/v3/mover.rb +3 -16
  78. data/lib/mml/v3/mpadded.rb +3 -22
  79. data/lib/mml/v3/mphantom.rb +3 -12
  80. data/lib/mml/v3/mprescripts.rb +2 -10
  81. data/lib/mml/v3/mroot.rb +3 -12
  82. data/lib/mml/v3/mrow.rb +3 -18
  83. data/lib/mml/v3/ms.rb +4 -36
  84. data/lib/mml/v3/mscarries.rb +3 -20
  85. data/lib/mml/v3/mscarry.rb +3 -16
  86. data/lib/mml/v3/msgroup.rb +3 -18
  87. data/lib/mml/v3/msline.rb +2 -20
  88. data/lib/mml/v3/mspace.rb +3 -50
  89. data/lib/mml/v3/msqrt.rb +3 -12
  90. data/lib/mml/v3/msrow.rb +3 -14
  91. data/lib/mml/v3/mstack.rb +3 -20
  92. data/lib/mml/v3/mstyle.rb +5 -204
  93. data/lib/mml/v3/msub.rb +3 -14
  94. data/lib/mml/v3/msubsup.rb +3 -16
  95. data/lib/mml/v3/msup.rb +3 -14
  96. data/lib/mml/v3/mtable.rb +3 -53
  97. data/lib/mml/v3/mtd.rb +3 -16
  98. data/lib/mml/v3/mtext.rb +3 -30
  99. data/lib/mml/v3/mtr.rb +2 -21
  100. data/lib/mml/v3/munder.rb +3 -18
  101. data/lib/mml/v3/munderover.rb +3 -18
  102. data/lib/mml/v3/namespace.rb +1 -4
  103. data/lib/mml/v3/none.rb +2 -10
  104. data/lib/mml/v3/semantics.rb +3 -12
  105. data/lib/mml/v3.rb +51 -77
  106. data/lib/mml/v4/a.rb +4 -13
  107. data/lib/mml/v4/common_elements.rb +13 -0
  108. data/lib/mml/v4/configuration.rb +4 -97
  109. data/lib/mml/v4/maction.rb +4 -19
  110. data/lib/mml/v4/maligngroup.rb +4 -17
  111. data/lib/mml/v4/malignmark.rb +4 -17
  112. data/lib/mml/v4/math.rb +4 -14
  113. data/lib/mml/v4/menclose.rb +4 -18
  114. data/lib/mml/v4/merror.rb +4 -16
  115. data/lib/mml/v4/mfenced.rb +4 -25
  116. data/lib/mml/v4/mfrac.rb +4 -24
  117. data/lib/mml/v4/mfraction.rb +4 -20
  118. data/lib/mml/v4/mglyph.rb +4 -35
  119. data/lib/mml/v4/mi.rb +4 -30
  120. data/lib/mml/v4/mlabeledtr.rb +6 -23
  121. data/lib/mml/v4/mlongdiv.rb +4 -18
  122. data/lib/mml/v4/mmultiscripts.rb +4 -24
  123. data/lib/mml/v4/mn.rb +4 -29
  124. data/lib/mml/v4/mo.rb +4 -75
  125. data/lib/mml/v4/mover.rb +4 -20
  126. data/lib/mml/v4/mpadded.rb +4 -26
  127. data/lib/mml/v4/mphantom.rb +4 -16
  128. data/lib/mml/v4/mprescripts.rb +4 -11
  129. data/lib/mml/v4/mroot.rb +4 -16
  130. data/lib/mml/v4/mrow.rb +4 -20
  131. data/lib/mml/v4/ms.rb +4 -34
  132. data/lib/mml/v4/mscarries.rb +4 -20
  133. data/lib/mml/v4/mscarry.rb +4 -16
  134. data/lib/mml/v4/msgroup.rb +4 -18
  135. data/lib/mml/v4/msline.rb +4 -21
  136. data/lib/mml/v4/mspace.rb +4 -49
  137. data/lib/mml/v4/msqrt.rb +4 -16
  138. data/lib/mml/v4/msrow.rb +4 -18
  139. data/lib/mml/v4/mstack.rb +4 -24
  140. data/lib/mml/v4/mstyle.rb +4 -186
  141. data/lib/mml/v4/msub.rb +4 -18
  142. data/lib/mml/v4/msubsup.rb +4 -20
  143. data/lib/mml/v4/msup.rb +4 -18
  144. data/lib/mml/v4/mtable.rb +4 -55
  145. data/lib/mml/v4/mtd.rb +4 -20
  146. data/lib/mml/v4/mtext.rb +4 -29
  147. data/lib/mml/v4/mtr.rb +4 -25
  148. data/lib/mml/v4/munder.rb +4 -22
  149. data/lib/mml/v4/munderover.rb +4 -22
  150. data/lib/mml/v4/namespace.rb +1 -4
  151. data/lib/mml/v4/none.rb +6 -11
  152. data/lib/mml/v4/semantics.rb +4 -12
  153. data/lib/mml/v4.rb +52 -74
  154. data/lib/mml/version.rb +1 -1
  155. data/lib/mml/versioned_parser.rb +46 -0
  156. data/lib/mml.rb +33 -25
  157. metadata +60 -4
  158. data/lib/mml/v3/common_attributes.rb +0 -22
  159. data/lib/mml/v4/common_attributes.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efbc2e4b8156916d34e524d2f7b6456168f8ef4a0c0c2ade8fbfb2b02bdc2efb
4
- data.tar.gz: 8f826d13adfc7f0b556aa5ab6f32f8e8390bf06002a76570845605af7282058a
3
+ metadata.gz: 2c1d691b0a249ad8e7c35c7eb2daeaa315202df3bd5ce479cca4ff73ef8bd98e
4
+ data.tar.gz: 1c200909f2749d9b62136a3d23e9a5462454e46e3591587a463114abd7d201f8
5
5
  SHA512:
6
- metadata.gz: 694df3fcc6b3611d6bac5b5c380b420b91823e697b219ba93a5d9ec22c70d910f8ecef6fef6c419b1acf80660d726259056717380faf8e77d235879b056e3c85
7
- data.tar.gz: 72b8f2d1960adec6a3777e43c792cf3d1d3aa4658be47e5bdbf9f7e5cd428ac1dbd3990d7c1685ae4f6ed1a90566b80932046c15424bf4b6a94efa4e39360a29
6
+ metadata.gz: 6328fec56b6a4bb170ba7085a0a27ae8264d16922119c63e255c72059d442f80ba80bae26179bf8f098ef53a47d25dfd69cacff8056f64f68de95f7219fcfa3f
7
+ data.tar.gz: c9c9ac8e92274886ea12c9c09d32e899583cbe1b99d753f890465e1ec06822b7b80ad34dd168b246fd0494bdeba588bb8bf8144bdb9ae18ec6c6f6450911a19b
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-03-31 03:44:08 UTC using RuboCop version 1.86.0.
3
+ # on 2026-04-05 08:05:31 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
@@ -16,13 +16,23 @@ Layout/LineLength:
16
16
  - 'spec/mml_spec.rb'
17
17
  - 'spec/spec_helper.rb'
18
18
 
19
- # Offense count: 1
19
+ # Offense count: 18
20
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
21
+ Metrics/AbcSize:
22
+ Enabled: false
23
+
24
+ # Offense count: 5
20
25
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
21
26
  # AllowedMethods: refine
22
27
  Metrics/BlockLength:
23
- Max: 27
28
+ Max: 172
24
29
 
25
- # Offense count: 3
30
+ # Offense count: 43
31
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
32
+ Metrics/MethodLength:
33
+ Max: 175
34
+
35
+ # Offense count: 2
26
36
  # Configuration parameters: Prefixes, AllowedPatterns.
27
37
  # Prefixes: when, with, without
28
38
  RSpec/ContextWording:
data/CLAUDE.md ADDED
@@ -0,0 +1,79 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ `mml` is a Ruby gem that provides MathML 3 and MathML 4 XML parsing and serialization. It maps MathML elements into Ruby model classes using the `lutaml-model` framework. Part of the [Plurimath](https://github.com/plurimath/mml) ecosystem.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ rake # Run specs + rubocop (default task)
13
+ bundle exec rspec # Run tests
14
+ bundle exec rspec spec/mml_spec.rb:42 # Run single test by line
15
+ bundle exec rspec --only-failures # Run only previously failing tests
16
+ bundle exec rubocop # Lint
17
+ bundle exec rubocop -a # Auto-fix lint issues
18
+ bin/console # IRB with gem loaded
19
+ ```
20
+
21
+ ## Versioned Architecture
22
+
23
+ **MathML 3 vs MathML 4:** The gem maintains separate class hierarchies for MathML 3 (`Mml::V3::`) and MathML 4 (`Mml::V4::`). Users must reference the versioned namespace explicitly — no backward-compat aliases.
24
+
25
+ ```ruby
26
+ Mml.parse(input) # Default: MathML 3
27
+ Mml.parse(input, version: 3) # Explicit MathML 3
28
+ Mml.parse(input, version: 4) # MathML 4 with intent/arg attributes
29
+ ```
30
+
31
+ **Directory structure:**
32
+ - `lib/mml/v3/` — MathML 3 element classes (original)
33
+ - `lib/mml/v4/` — MathML 4 element classes (intent/arg added, deprecated attrs removed)
34
+
35
+ **Key difference:** MathML 4 adds `intent`, `arg`, `displaystyle`, and `scriptlevel` attributes as universal presentation attributes for accessibility markup.
36
+
37
+ **No hidden delegation:** The `Mml` module does not alias or delegate constants. Use `Mml::V3::Math`, `Mml::V4::Mi`, etc. directly.
38
+
39
+ ## Entry Points
40
+
41
+ - `Mml.parse(input, version: N)` — parse XML, returns `Mml::V3::Math` or `Mml::V4::Math` object graph
42
+ - `Mml::V3.parse(input)` / `Mml::V4.parse(input)` — version-specific parsing
43
+ - `Mml::V4::Math.from_xml(input)` — directly parse with v4 classes
44
+ - Call `to_xml` on any element to serialize back
45
+
46
+ ## Pattern
47
+
48
+ Each MathML element is a `Mml::V3::` or `Mml::V4::` class inheriting from `Lutaml::Model::Serializable` with an `xml do...end` DSL block. Two element types:
49
+ - **Leaf elements** (e.g., `Mi`, `Mn`, `Mo`): use `map_content to: :value` for text content
50
+ - **Container elements** (e.g., `Math`, `Mrow`, `Mfrac`): use `mixed_content` to accept arbitrary child elements
51
+
52
+ **CommonAttributes:** A `no_root` Lutaml model imported into container elements via `import_model`. It dynamically creates `#{tag}_value` attributes for each tag in `Configuration::SUPPORTED_TAGS`. Classes that receive it are listed in `Configuration::COMMON_ATTRIBUTES_CLASSES`.
53
+
54
+ **Autoloading:** Each version (`lib/mml/v3.rb`, `lib/mml/v4.rb`) autoloads its element classes. `CommonAttributes` is required after all classes exist, then `update_attributes` mixes it into the configured classes.
55
+
56
+ **Namespace:** Both versions use the same URI (`http://www.w3.org/1998/Math/MathML`) — MathML 4 chose backward compatibility over a new namespace.
57
+
58
+ ## Spec Structure
59
+
60
+ - `spec/mml_spec.rb` — tests `Mml::V3` and `Mml::V4` with separate fixture directories
61
+ - `spec/fixtures/with_namespace/` — v3 fixtures (MathML 3)
62
+ - `spec/fixtures/with_namespace_prefix/` — v3 fixtures with namespace prefix
63
+ - `spec/fixtures/v4/` — v4 fixtures (MathML 4 with intent attributes)
64
+
65
+ Specs use the `:nokogiri` Lutaml adapter (configured in `spec_helper.rb`). Runtime uses `:ox` adapter by default.
66
+
67
+ ## Key Dependencies
68
+
69
+ - `lutaml-model` (~ 0.8.0) — data mapper framework; all element classes inherit from `Lutaml::Model::Serializable`
70
+ - `moxml` — XML parsing library (uses `:ox` adapter by default)
71
+ - `canon` — XML comparison for specs
72
+
73
+ ## Conventions
74
+
75
+ - `frozen_string_literal: true` in all files
76
+ - RuboCop targets Ruby 3.0; inherits from Ribose OSS guides
77
+ - CI workflows are auto-generated by Cimas — do not edit manually
78
+ - `Gemfile.lock` is gitignored; dependencies come from the gemspec
79
+ - Type signatures exist in `sig/mml.rbs`
data/README.adoc CHANGED
@@ -39,15 +39,16 @@ $ gem install mml
39
39
  ----
40
40
  require "mml"
41
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>')
42
+ # Parse MathML with explicit version
43
+ math = Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>', version: 3)
44
+ math4 = Mml.parse(input, version: 4)
44
45
 
45
46
  # Serialize back to XML
46
47
  math.to_xml
47
- # => "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>x</mi></math>"
48
48
 
49
- # Parse with explicit version
50
- math4 = Mml.parse(input, version: 4)
49
+ # Or use versioned modules directly
50
+ Mml::V3.parse(input)
51
+ Mml::V4.parse(input)
51
52
  ----
52
53
 
53
54
  == MathML version architecture
@@ -60,7 +61,7 @@ for backward compatibility.
60
61
  ----
61
62
  ┌───────────────────────────────────────────┐
62
63
  │ Mml │
63
- │ (aliases Mml::V3 for backward compat)
64
+ parse() delegates to V3 or V4
64
65
  └────────────────────┬──────────────────────┘
65
66
 
66
67
  ┌───────────────────┴────────────────────┐
@@ -79,7 +80,7 @@ for backward compatibility.
79
80
  │ └────────────┬───────────────┘ │ │ │ + <a> element │ │
80
81
  │ │ │ │ └────────────┬────────────┘ │
81
82
  │ ┌────────────┴────────────┐ │ │ ┌────────────┴────────────┐ │
82
- │ │ CommonAttributes │ │ │ │ CommonAttributes │ │
83
+ │ │ CommonElements │ │ │ │ CommonElements │ │
83
84
  │ │ (child element mixin) │ │ │ │ (v4 version) │ │
84
85
  │ └─────────────────────────┘ │ | └─────────────────────────┘ |
85
86
  └─────────────────────────────────────┘ └───────────────────────────────┘
@@ -97,7 +98,8 @@ for backward compatibility.
97
98
  Mml.parse(input) # Default: MathML 3 (Mml::V3)
98
99
  Mml.parse(input, version: 3) # Explicit MathML 3
99
100
  Mml.parse(input, version: 4) # MathML 4 with intent/arg attributes
100
- Mml::V4.parse(input) # Direct v4 parsing (no default fallback)
101
+ Mml::V3.parse(input) # Direct v3 parsing
102
+ Mml::V4.parse(input) # Direct v4 parsing
101
103
  ----
102
104
 
103
105
  === Key differences between MathML 3 and MathML 4
@@ -107,56 +109,79 @@ Mml::V4.parse(input) # Direct v4 parsing (no default fallback)
107
109
  |Feature |MathML 4 additions
108
110
  |Universal attributes |`intent`, `arg`, `displaystyle`, `scriptlevel` available on all presentation elements
109
111
  |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
112
+ |Deprecated in MathML 4 (still recognized for backward compatibility) a|
113
+ * `fontfamily`, `fontweight`, `fontstyle`, `fontsize`, `color`, `background` on token elements, `mstyle`, `mglyph`, `mspace`
114
+ * `mode`, `macros` on `math`
115
+ * `index` on `mglyph`
116
+
117
+ See https://github.com/w3c/mathml/issues/5[w3c/mathml#5] and
118
+ https://github.com/w3c/mathml/issues/303[w3c/mathml#303] for details.
115
119
  |===
116
120
 
117
- === Migration from MathML 3 to MathML 4
121
+ === Migration from previous versions
118
122
 
119
- ==== For gem users (upgrading parsing)
123
+ ==== require and configuration
120
124
 
121
- If you currently parse MathML and want to use MathML 4 features:
125
+ The `Mml` module no longer aliases versioned constants. Use the explicit version
126
+ namespace:
122
127
 
123
128
  [source,ruby]
124
129
  ----
125
- # Change this:
126
- math = Mml.parse(input)
130
+ # Before (no longer supported)
131
+ require "mml/configuration"
132
+ Mml::Configuration.adapter = :nokogiri
133
+ Mml::Configuration.create_context(id: :custom_v3)
134
+ Mml::Math.new(...)
127
135
 
128
- # To this:
129
- math = Mml.parse(input, version: 4)
136
+ # After — explicit version
137
+ require "mml"
138
+ Mml::V3::Configuration.adapter = :nokogiri
139
+ Mml::V3::Configuration.create_context(
140
+ id: :custom_v3,
141
+ substitutions: [
142
+ { from_type: Mml::V3::Mi, to_type: MyCustomMi }
143
+ ]
144
+ )
145
+ Mml::V3.parse(input, context: :custom_v3)
146
+
147
+ # Or for MathML 4
148
+ Mml::V4::Configuration.adapter = :nokogiri
149
+ Mml::V4::Configuration.create_context(
150
+ id: :custom_v4,
151
+ substitutions: [
152
+ { from_type: Mml::V4::Mi, to_type: MyCustomMi }
153
+ ]
154
+ )
155
+ Mml::V4.parse(input, context: :custom_v4)
130
156
  ----
131
157
 
132
- ==== For gem users (upgrading serialized output)
158
+ ==== Element class references
133
159
 
134
- If you serialize MathML 4 documents and want to ensure compatibility:
160
+ All element classes live under their version namespace:
135
161
 
136
162
  [source,ruby]
137
163
  ----
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)
164
+ # Before (no longer supported)
165
+ Mml::Mi.new(value: "x")
166
+ Mml::Mrow.new(mi_value: [...])
144
167
 
145
- Custom elements registered via `Configuration.custom_models` are version-specific:
168
+ # After
169
+ Mml::V3::Mi.new(value: "x")
170
+ Mml::V3::Mrow.new(mi_value: [...])
146
171
 
147
- [source,ruby]
148
- ----
149
- # Register a custom element for MathML 4 only
150
- Mml::V4::Configuration.custom_models = { Mi => MyCustomMiV4 }
172
+ # Or for MathML 4
173
+ Mml::V4::Mi.new(value: "x", intent: "$x")
174
+ Mml::V4::Mrow.new(mi_value: [...])
151
175
  ----
152
176
 
153
- CommonAttributes is version-specific. Elements that exist only in v4 (like `<a>`)
154
- are automatically handled:
177
+ ==== Parsing
178
+
179
+ `Mml.parse` still works with a `version:` keyword, defaulting to version 3:
155
180
 
156
181
  [source,ruby]
157
182
  ----
158
- # The <a> element is only in v4 CommonAttributes
159
- Mml::V4::Mrow.new(a_value: [Mml::V4::A.new(href: "https://...")])
183
+ Mml.parse(input) # unchanged defaults to MathML 3
184
+ Mml.parse(input, version: 4) # use MathML 4
160
185
  ----
161
186
 
162
187
  == Parsing and serialization
@@ -166,13 +191,16 @@ Mml::V4::Mrow.new(a_value: [Mml::V4::A.new(href: "https://...")])
166
191
  [source,ruby]
167
192
  ----
168
193
  # Default namespace
169
- Mml.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
194
+ Mml::V3.parse('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>')
170
195
 
171
196
  # Prefixed namespace
172
- Mml.parse('<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>x</mml:mi></mml:math>')
197
+ Mml::V3.parse('<mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>x</mml:mi></mml:math>')
173
198
 
174
199
  # No namespace (namespace injected internally)
175
- Mml.parse("<math><mi>x</mi></math>", namespace_exist: false)
200
+ Mml::V3.parse("<math><mi>x</mi></math>", namespace_exist: false)
201
+
202
+ # MathML 4
203
+ Mml::V4.parse(input)
176
204
  ----
177
205
 
178
206
  === Serialization
@@ -217,7 +245,7 @@ math.to_xml
217
245
 
218
246
  *v4 only*: `a` (hyperlink)
219
247
 
220
- *Deprecated*: `mlabeledtr`, `none` (classes exist but hidden from CommonAttributes in v4)
248
+ *Deprecated*: `mlabeledtr`, `none` (classes exist but hidden from CommonElements in v4)
221
249
 
222
250
  === Token elements (leaf nodes)
223
251
 
@@ -225,13 +253,13 @@ Token elements hold text content in the `value` attribute:
225
253
 
226
254
  [source,ruby]
227
255
  ----
228
- Mml::Mi.new(value: "x")
229
- Mml::Mn.new(value: "42")
230
- Mml::Mo.new(value: "+")
231
- Mml::Ms.new(value: "text")
232
- Mml::Mtext.new(value: "label")
233
- Mml::Mspace.new(width: "1em")
234
- Mml::Mglyph.new(alt: "symbol")
256
+ Mml::V3::Mi.new(value: "x")
257
+ Mml::V3::Mn.new(value: "42")
258
+ Mml::V3::Mo.new(value: "+")
259
+ Mml::V3::Ms.new(value: "text")
260
+ Mml::V3::Mtext.new(value: "label")
261
+ Mml::V3::Mspace.new(width: "1em")
262
+ Mml::V3::Mglyph.new(alt: "symbol")
235
263
  ----
236
264
 
237
265
  === Container elements
@@ -240,10 +268,11 @@ Container elements hold child elements via `#{tag}_value` collection attributes:
240
268
 
241
269
  [source,ruby]
242
270
  ----
243
- Mml::Mrow.new(
244
- mi_value: [Mml::Mi.new(value: "x")],
245
- mo_value: [Mml::Mo.new(value: "+")],
246
- mn_value: [Mml::Mn.new(value: "1")],
271
+ Mml::V3::Mrow.new(
272
+ lutaml_register: Mml::V3::Configuration.context_id,
273
+ mi_value: [Mml::V3::Mi.new(value: "x")],
274
+ mo_value: [Mml::V3::Mo.new(value: "+")],
275
+ mn_value: [Mml::V3::Mn.new(value: "1")],
247
276
  )
248
277
  # => <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow>
249
278
  ----
@@ -254,10 +283,12 @@ Build an expression tree by nesting elements:
254
283
 
255
284
  [source,ruby]
256
285
  ----
257
- Mml::Math.new(
286
+ Mml::V3::Math.new(
287
+ lutaml_register: Mml::V3::Configuration.context_id,
258
288
  mfrac_value: [
259
- Mml::Mfrac.new(
260
- mi_value: [Mml::Mi.new(value: "a"), Mml::Mi.new(value: "b")],
289
+ Mml::V3::Mfrac.new(
290
+ lutaml_register: Mml::V3::Configuration.context_id,
291
+ mi_value: [Mml::V3::Mi.new(value: "a"), Mml::V3::Mi.new(value: "b")],
261
292
  ),
262
293
  ],
263
294
  )
@@ -268,12 +299,20 @@ Mml::Math.new(
268
299
 
269
300
  [source,ruby]
270
301
  ----
271
- Mml::Mtable.new(
302
+ Mml::V3::Mtable.new(
303
+ lutaml_register: Mml::V3::Configuration.context_id,
272
304
  mtr_value: [
273
- Mml::Mtr.new(
305
+ Mml::V3::Mtr.new(
306
+ lutaml_register: Mml::V3::Configuration.context_id,
274
307
  mtd_value: [
275
- Mml::Mtd.new(mi_value: [Mml::Mi.new(value: "a")]),
276
- Mml::Mtd.new(mi_value: [Mml::Mi.new(value: "b")]),
308
+ Mml::V3::Mtd.new(
309
+ lutaml_register: Mml::V3::Configuration.context_id,
310
+ mi_value: [Mml::V3::Mi.new(value: "a")]
311
+ ),
312
+ Mml::V3::Mtd.new(
313
+ lutaml_register: Mml::V3::Configuration.context_id,
314
+ mi_value: [Mml::V3::Mi.new(value: "b")]
315
+ ),
277
316
  ],
278
317
  ),
279
318
  ],
@@ -285,6 +324,7 @@ Mml::Mtable.new(
285
324
  [source,ruby]
286
325
  ----
287
326
  Mml::V4::A.new(
327
+ lutaml_register: Mml::V4::Configuration.context_id,
288
328
  href: "https://example.com",
289
329
  hreflang: "en",
290
330
  mi_value: [Mml::V4::Mi.new(value: "click")]
@@ -292,41 +332,116 @@ Mml::V4::A.new(
292
332
  # => <a href="https://example.com" hreflang="en"><mi>click</mi></a>
293
333
  ----
294
334
 
335
+ == MathML V4 Compliance
336
+
337
+ This implementation has been audited against the
338
+ https://www.w3.org/TR/MathML4/[MathML 4 W3C Recommendation].
339
+
340
+ === Universal V4 Attributes
341
+
342
+ All MathML 4 presentation elements include `intent`, `arg`, `displaystyle`,
343
+ `scriptlevel`, `mathcolor`, and `mathbackground` via the shared
344
+ `Base::V4Attributes` module.
345
+
346
+ === Legacy Schema Support
347
+
348
+ For backwards compatibility with existing MathML content, this gem supports
349
+ both the strict V4 schema and the legacy schema:
350
+
351
+ [cols="1,2",options="header"]
352
+ |===
353
+ |Feature |Status
354
+ |Universal V4 attributes (`intent`, `arg`, `displaystyle`, `scriptlevel`) |Full support
355
+ |`mathcolor`, `mathbackground` on all presentation elements |Full support
356
+ |`<a>` hyperlink element |Full support
357
+ |Deprecated font attributes (`fontfamily`, `fontweight`, etc.) |Legacy support (V3 + V4 legacy schema)
358
+ |`fence`, `separator` on `<mo>` |Legacy support (removed from default V4 schema)
359
+ |`none` element |Deprecated in V4 (empty `<mrow>` recommended)
360
+ |`mlabeledtr` element |Legacy support (removed from default V4 schema)
361
+ |===
362
+
363
+
295
364
  == Internal architecture
296
365
 
297
366
  === Element class patterns
298
367
 
299
- All element classes inherit from `Lutaml::Model::Serializable`:
368
+ Shared attributes and mappings live in `Base::` modules (`lib/mml/base/`).
369
+ V3 and V4 classes include these modules independently — no cross-version inheritance.
300
370
 
301
- * *Leaf elements*: use `map_content to: :value` for text content
302
- * *Container elements*: use `mixed_content` for child elements
371
+ * *Leaf elements*: inherit `Lutaml::Model::Serializable`, include `Base::ElementName`
372
+ * *Container elements*: inherit `CommonElements`, include `Base::ElementName`
373
+
374
+ Each element self-registers in its version's built-in `GlobalContext` context.
303
375
 
304
376
  [source,ruby]
305
377
  ----
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
378
+ # Shared attributes (lib/mml/base/mi.rb)
379
+ module Base::Mi
380
+ def self.included(klass)
381
+ klass.class_eval do
382
+ attribute :value, :string
383
+ xml do
384
+ element "mi"
385
+ map_content to: :value
386
+ end
387
+ end
312
388
  end
313
389
  end
314
390
 
315
- # Container — child elements
316
- class Mrow < Lutaml::Model::Serializable
317
- xml do
318
- element "mrow"
319
- mixed_content
320
- end
391
+ # V3 leaf
392
+ class V3::Mi < Lutaml::Model::Serializable
393
+ include Base::Mi
394
+ end
395
+
396
+ # V4 leaf — adds V4-only attributes
397
+ class V4::Mi < Lutaml::Model::Serializable
398
+ include Base::Mi
399
+ attribute :intent, :string
321
400
  end
401
+
402
+ # V3 container
403
+ class V3::Mrow < CommonElements
404
+ include Base::Mrow
405
+ end
406
+ ----
407
+
408
+ === CommonElements
409
+
410
+ Container elements inherit `CommonElements`, which defines `#{tag}_value` collection
411
+ attributes for all supported child elements. Attribute types use symbols (e.g., `:mi`,
412
+ `:mfrac`) resolved through `Lutaml::Model::GlobalContext`.
413
+
414
+ V4's `CommonElements` extends the base with the `<a>` hyperlink element.
415
+
416
+ === Context and type resolution
417
+
418
+ When calling `from_xml` directly (outside of `Mml.parse` or `Mml::V3.parse`), pass
419
+ the version-specific context id for correct type resolution.
420
+
421
+ NOTE: `lutaml-model` still uses the keyword name `register:` in low-level APIs.
422
+ In MML, the value passed to that keyword should be a context id.
423
+
424
+ [source,ruby]
425
+ ----
426
+ Mml::V3::Math.from_xml(input, register: Mml::V3::Configuration.context_id)
427
+ Mml::V4::Math.from_xml(input, register: Mml::V4::Configuration.context_id)
322
428
  ----
323
429
 
324
- === CommonAttributes
430
+ The `parse` methods handle this automatically.
431
+
432
+ When constructing container elements directly, also pass the context id on the
433
+ instance via `lutaml_register:` so symbolic child types resolve in the right
434
+ versioned context:
435
+
436
+ [source,ruby]
437
+ ----
438
+ math = Mml::V3::Math.new(
439
+ lutaml_register: Mml::V3::Configuration.context_id,
440
+ mi_value: [Mml::V3::Mi.new(value: "x")]
441
+ )
325
442
 
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
329
- `Configuration::COMMON_ATTRIBUTES_CLASSES`.
443
+ math.to_xml
444
+ ----
330
445
 
331
446
  === Namespace
332
447
 
@@ -341,13 +456,41 @@ Three input forms are supported:
341
456
 
342
457
  [source,ruby]
343
458
  ----
344
- # Switch XML adapter (default: :ox, also supports :nokogiri)
345
- Mml::Configuration.adapter = :nokogiri
459
+ # Switch XML adapter (default: :ox, :oga on Opal)
460
+ Mml::V3::Configuration.adapter = :nokogiri
461
+
462
+ # Access the built-in version-specific contexts
463
+ Mml::V3::Configuration.context_id # => :mml_v3
464
+ Mml::V4::Configuration.context_id # => :mml_v4
465
+ Mml::V3::Configuration.context
466
+ Mml::V4::Configuration.context
467
+
468
+ # Rebuild a built-in context after an explicit GlobalContext.reset!
469
+ Mml::V3::Configuration.populate_context!
470
+ Mml::V4::Configuration.populate_context!
471
+
472
+ # Create a derived context with substitutions
473
+ Mml::V3::Configuration.create_context(
474
+ id: :custom_v3,
475
+ substitutions: [
476
+ { from_type: Mml::V3::Mi, to_type: MyCustomMi }
477
+ ]
478
+ )
346
479
 
347
- # Register custom model replacements
348
- Mml::Configuration.custom_models = { Mi => MyCustomMi }
480
+ # Parse using the custom context
481
+ Mml::V3.parse(input, context: :custom_v3)
482
+
483
+ # Low-level APIs still use the upstream keyword name `register:`
484
+ Mml::V3::Math.from_xml(input, register: :custom_v3)
349
485
  ----
350
486
 
487
+ The `context:` keyword is the preferred MML API. The legacy `register:` keyword is
488
+ still accepted temporarily in MML parse methods, but it emits a deprecation warning
489
+ and is normalized to a context id internally.
490
+
491
+ If you reset global contexts and need the built-in MML contexts restored
492
+ explicitly, call `populate_context!` for the version(s) you want to restore.
493
+
351
494
  == Development
352
495
 
353
496
  [source,bash]
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mml
4
+ module Base
5
+ # Deprecated MathML 1 font attributes: fontfamily, fontweight, fontstyle,
6
+ # fontsize, color, background.
7
+ # These are removed in MathML 4 default schema but valid in mathml4-legacy.
8
+ module DeprecatedFontAttributes
9
+ def self.included(klass)
10
+ klass.class_eval do
11
+ attribute :fontfamily, :string
12
+ attribute :fontweight, :string
13
+ attribute :fontstyle, :string
14
+ attribute :fontsize, :string
15
+ attribute :color, :string
16
+ attribute :background, :string
17
+
18
+ xml do
19
+ namespace Mml::Namespace
20
+ map_attribute "fontfamily", to: :fontfamily
21
+ map_attribute "fontweight", to: :fontweight
22
+ map_attribute "fontstyle", to: :fontstyle
23
+ map_attribute "fontsize", to: :fontsize
24
+ map_attribute "color", to: :color
25
+ map_attribute "background", to: :background
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mml
4
+ module Base
5
+ module Maction
6
+ # NOTE: class_eval resolves constants in module's lexical scope.
7
+ # Use fully qualified names (e.g., Mml::Namespace).
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ attribute :mathcolor, :string
11
+ attribute :mathbackground, :string
12
+ attribute :actiontype, :string
13
+ attribute :selection, :string
14
+
15
+ xml do
16
+ namespace Mml::Namespace
17
+ element "maction"
18
+
19
+ map_attribute "mathcolor", to: :mathcolor
20
+ map_attribute "mathbackground", to: :mathbackground
21
+ map_attribute "actiontype", to: :actiontype
22
+ map_attribute "selection", to: :selection
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mml
4
+ module Base
5
+ module Maligngroup
6
+ # NOTE: class_eval resolves constants in module's lexical scope.
7
+ # Use fully qualified names (e.g., Mml::Namespace).
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ attribute :mathcolor, :string
11
+ attribute :mathbackground, :string
12
+ attribute :groupalign, :string
13
+
14
+ xml do
15
+ namespace Mml::Namespace
16
+ element "maligngroup"
17
+
18
+ map_attribute "mathcolor", to: :mathcolor
19
+ map_attribute "mathbackground", to: :mathbackground
20
+ map_attribute "groupalign", to: :groupalign
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end