mml 2.2.1 → 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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +14 -4
- data/CLAUDE.md +79 -0
- data/README.adoc +166 -43
- data/lib/mml/base/deprecated_font_attributes.rb +31 -0
- data/lib/mml/base/maction.rb +28 -0
- data/lib/mml/base/maligngroup.rb +26 -0
- data/lib/mml/base/malignmark.rb +26 -0
- data/lib/mml/base/math.rb +23 -0
- data/lib/mml/base/menclose.rb +27 -0
- data/lib/mml/base/merror.rb +25 -0
- data/lib/mml/base/mfenced.rb +34 -0
- data/lib/mml/base/mfrac.rb +33 -0
- data/lib/mml/base/mfraction.rb +33 -0
- data/lib/mml/base/mglyph.rb +42 -0
- data/lib/mml/base/mi.rb +32 -0
- data/lib/mml/base/mlabeledtr.rb +37 -0
- data/lib/mml/base/mlongdiv.rb +31 -0
- data/lib/mml/base/mmultiscripts.rb +31 -0
- data/lib/mml/base/mn.rb +32 -0
- data/lib/mml/base/mo.rb +76 -0
- data/lib/mml/base/mover.rb +29 -0
- data/lib/mml/base/mpadded.rb +35 -0
- data/lib/mml/base/mphantom.rb +25 -0
- data/lib/mml/base/mprescripts.rb +24 -0
- data/lib/mml/base/mroot.rb +25 -0
- data/lib/mml/base/mrow.rb +29 -0
- data/lib/mml/base/ms.rb +37 -0
- data/lib/mml/base/mscarries.rb +33 -0
- data/lib/mml/base/mscarry.rb +29 -0
- data/lib/mml/base/msgroup.rb +31 -0
- data/lib/mml/base/msline.rb +34 -0
- data/lib/mml/base/mspace.rb +52 -0
- data/lib/mml/base/msqrt.rb +25 -0
- data/lib/mml/base/msrow.rb +27 -0
- data/lib/mml/base/mstack.rb +33 -0
- data/lib/mml/base/mstyle.rb +189 -0
- data/lib/mml/base/msub.rb +27 -0
- data/lib/mml/base/msubsup.rb +29 -0
- data/lib/mml/base/msup.rb +27 -0
- data/lib/mml/base/mtable.rb +57 -0
- data/lib/mml/base/mtd.rb +29 -0
- data/lib/mml/base/mtext.rb +32 -0
- data/lib/mml/base/mtr.rb +35 -0
- data/lib/mml/base/munder.rb +31 -0
- data/lib/mml/base/munderover.rb +31 -0
- data/lib/mml/base/none.rb +24 -0
- data/lib/mml/base/semantics.rb +23 -0
- data/lib/mml/base/v3_only/operator_attrs.rb +24 -0
- data/lib/mml/base/v3_only/style_attrs.rb +31 -0
- data/lib/mml/base/v3_only/table_attrs.rb +28 -0
- data/lib/mml/base/v3_only.rb +11 -0
- data/lib/mml/base/v4_attributes.rb +30 -0
- data/lib/mml/base.rb +51 -0
- data/lib/mml/common_elements.rb +85 -0
- data/lib/mml/context_configuration.rb +118 -0
- data/lib/mml/context_options.rb +64 -0
- data/lib/mml/namespace.rb +10 -0
- data/lib/mml/v3/common_elements.rb +8 -0
- data/lib/mml/v3/configuration.rb +4 -96
- data/lib/mml/v3/maction.rb +2 -14
- data/lib/mml/v3/maligngroup.rb +2 -12
- data/lib/mml/v3/malignmark.rb +2 -12
- data/lib/mml/v3/math.rb +3 -10
- data/lib/mml/v3/menclose.rb +3 -14
- data/lib/mml/v3/merror.rb +3 -12
- data/lib/mml/v3/mfenced.rb +3 -21
- data/lib/mml/v3/mfrac.rb +3 -20
- data/lib/mml/v3/mfraction.rb +3 -20
- data/lib/mml/v3/mglyph.rb +3 -38
- data/lib/mml/v3/mi.rb +3 -30
- data/lib/mml/v3/mlabeledtr.rb +2 -23
- data/lib/mml/v3/mlongdiv.rb +3 -18
- data/lib/mml/v3/mmultiscripts.rb +3 -20
- data/lib/mml/v3/mn.rb +3 -30
- data/lib/mml/v3/mo.rb +4 -78
- data/lib/mml/v3/mover.rb +3 -16
- data/lib/mml/v3/mpadded.rb +3 -22
- data/lib/mml/v3/mphantom.rb +3 -12
- data/lib/mml/v3/mprescripts.rb +2 -10
- data/lib/mml/v3/mroot.rb +3 -12
- data/lib/mml/v3/mrow.rb +3 -18
- data/lib/mml/v3/ms.rb +4 -36
- data/lib/mml/v3/mscarries.rb +3 -20
- data/lib/mml/v3/mscarry.rb +3 -16
- data/lib/mml/v3/msgroup.rb +3 -18
- data/lib/mml/v3/msline.rb +2 -20
- data/lib/mml/v3/mspace.rb +3 -50
- data/lib/mml/v3/msqrt.rb +3 -12
- data/lib/mml/v3/msrow.rb +3 -14
- data/lib/mml/v3/mstack.rb +3 -20
- data/lib/mml/v3/mstyle.rb +5 -204
- data/lib/mml/v3/msub.rb +3 -14
- data/lib/mml/v3/msubsup.rb +3 -16
- data/lib/mml/v3/msup.rb +3 -14
- data/lib/mml/v3/mtable.rb +3 -53
- data/lib/mml/v3/mtd.rb +3 -16
- data/lib/mml/v3/mtext.rb +3 -30
- data/lib/mml/v3/mtr.rb +2 -21
- data/lib/mml/v3/munder.rb +3 -18
- data/lib/mml/v3/munderover.rb +3 -18
- data/lib/mml/v3/namespace.rb +1 -4
- data/lib/mml/v3/none.rb +2 -10
- data/lib/mml/v3/semantics.rb +3 -12
- data/lib/mml/v3.rb +51 -77
- data/lib/mml/v4/a.rb +4 -13
- data/lib/mml/v4/common_elements.rb +13 -0
- data/lib/mml/v4/configuration.rb +4 -97
- data/lib/mml/v4/maction.rb +4 -19
- data/lib/mml/v4/maligngroup.rb +4 -17
- data/lib/mml/v4/malignmark.rb +4 -17
- data/lib/mml/v4/math.rb +4 -14
- data/lib/mml/v4/menclose.rb +4 -18
- data/lib/mml/v4/merror.rb +4 -16
- data/lib/mml/v4/mfenced.rb +4 -25
- data/lib/mml/v4/mfrac.rb +4 -24
- data/lib/mml/v4/mfraction.rb +4 -20
- data/lib/mml/v4/mglyph.rb +4 -35
- data/lib/mml/v4/mi.rb +4 -30
- data/lib/mml/v4/mlabeledtr.rb +6 -23
- data/lib/mml/v4/mlongdiv.rb +4 -18
- data/lib/mml/v4/mmultiscripts.rb +4 -24
- data/lib/mml/v4/mn.rb +4 -29
- data/lib/mml/v4/mo.rb +4 -75
- data/lib/mml/v4/mover.rb +4 -20
- data/lib/mml/v4/mpadded.rb +4 -26
- data/lib/mml/v4/mphantom.rb +4 -16
- data/lib/mml/v4/mprescripts.rb +4 -11
- data/lib/mml/v4/mroot.rb +4 -16
- data/lib/mml/v4/mrow.rb +4 -20
- data/lib/mml/v4/ms.rb +4 -34
- data/lib/mml/v4/mscarries.rb +4 -20
- data/lib/mml/v4/mscarry.rb +4 -16
- data/lib/mml/v4/msgroup.rb +4 -18
- data/lib/mml/v4/msline.rb +4 -21
- data/lib/mml/v4/mspace.rb +4 -49
- data/lib/mml/v4/msqrt.rb +4 -16
- data/lib/mml/v4/msrow.rb +4 -18
- data/lib/mml/v4/mstack.rb +4 -24
- data/lib/mml/v4/mstyle.rb +4 -186
- data/lib/mml/v4/msub.rb +4 -18
- data/lib/mml/v4/msubsup.rb +4 -20
- data/lib/mml/v4/msup.rb +4 -18
- data/lib/mml/v4/mtable.rb +4 -55
- data/lib/mml/v4/mtd.rb +4 -20
- data/lib/mml/v4/mtext.rb +4 -29
- data/lib/mml/v4/mtr.rb +4 -25
- data/lib/mml/v4/munder.rb +4 -22
- data/lib/mml/v4/munderover.rb +4 -22
- data/lib/mml/v4/namespace.rb +1 -4
- data/lib/mml/v4/none.rb +6 -11
- data/lib/mml/v4/semantics.rb +4 -12
- data/lib/mml/v4.rb +52 -74
- data/lib/mml/version.rb +1 -1
- data/lib/mml/versioned_parser.rb +46 -0
- data/lib/mml.rb +34 -6
- metadata +60 -4
- data/lib/mml/v3/common_attributes.rb +0 -22
- data/lib/mml/v4/common_attributes.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c1d691b0a249ad8e7c35c7eb2daeaa315202df3bd5ce479cca4ff73ef8bd98e
|
|
4
|
+
data.tar.gz: 1c200909f2749d9b62136a3d23e9a5462454e46e3591587a463114abd7d201f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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-
|
|
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:
|
|
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:
|
|
28
|
+
Max: 172
|
|
24
29
|
|
|
25
|
-
# Offense count:
|
|
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
|
@@ -47,8 +47,8 @@ math4 = Mml.parse(input, version: 4)
|
|
|
47
47
|
math.to_xml
|
|
48
48
|
|
|
49
49
|
# Or use versioned modules directly
|
|
50
|
-
Mml::V3
|
|
51
|
-
Mml::V4
|
|
50
|
+
Mml::V3.parse(input)
|
|
51
|
+
Mml::V4.parse(input)
|
|
52
52
|
----
|
|
53
53
|
|
|
54
54
|
== MathML version architecture
|
|
@@ -80,7 +80,7 @@ for backward compatibility.
|
|
|
80
80
|
│ └────────────┬───────────────┘ │ │ │ + <a> element │ │
|
|
81
81
|
│ │ │ │ └────────────┬────────────┘ │
|
|
82
82
|
│ ┌────────────┴────────────┐ │ │ ┌────────────┴────────────┐ │
|
|
83
|
-
│ │
|
|
83
|
+
│ │ CommonElements │ │ │ │ CommonElements │ │
|
|
84
84
|
│ │ (child element mixin) │ │ │ │ (v4 version) │ │
|
|
85
85
|
│ └─────────────────────────┘ │ | └─────────────────────────┘ |
|
|
86
86
|
└─────────────────────────────────────┘ └───────────────────────────────┘
|
|
@@ -109,11 +109,13 @@ Mml::V4.parse(input) # Direct v4 parsing
|
|
|
109
109
|
|Feature |MathML 4 additions
|
|
110
110
|
|Universal attributes |`intent`, `arg`, `displaystyle`, `scriptlevel` available on all presentation elements
|
|
111
111
|
|New element |`<a>` hyperlink element with `href`, `hreflang`
|
|
112
|
-
|Deprecated (
|
|
113
|
-
* `fontfamily`, `fontweight`, `fontstyle`, `fontsize`, `color`, `background` on `mstyle`, `mglyph`, `mspace`
|
|
114
|
-
* `
|
|
115
|
-
* `
|
|
116
|
-
|
|
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.
|
|
117
119
|
|===
|
|
118
120
|
|
|
119
121
|
=== Migration from previous versions
|
|
@@ -128,19 +130,29 @@ namespace:
|
|
|
128
130
|
# Before (no longer supported)
|
|
129
131
|
require "mml/configuration"
|
|
130
132
|
Mml::Configuration.adapter = :nokogiri
|
|
131
|
-
Mml::Configuration.
|
|
133
|
+
Mml::Configuration.create_context(id: :custom_v3)
|
|
132
134
|
Mml::Math.new(...)
|
|
133
135
|
|
|
134
136
|
# After — explicit version
|
|
135
137
|
require "mml"
|
|
136
138
|
Mml::V3::Configuration.adapter = :nokogiri
|
|
137
|
-
Mml::V3::Configuration.
|
|
138
|
-
|
|
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)
|
|
139
146
|
|
|
140
147
|
# Or for MathML 4
|
|
141
148
|
Mml::V4::Configuration.adapter = :nokogiri
|
|
142
|
-
Mml::V4::Configuration.
|
|
143
|
-
|
|
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)
|
|
144
156
|
----
|
|
145
157
|
|
|
146
158
|
==== Element class references
|
|
@@ -233,7 +245,7 @@ math.to_xml
|
|
|
233
245
|
|
|
234
246
|
*v4 only*: `a` (hyperlink)
|
|
235
247
|
|
|
236
|
-
*Deprecated*: `mlabeledtr`, `none` (classes exist but hidden from
|
|
248
|
+
*Deprecated*: `mlabeledtr`, `none` (classes exist but hidden from CommonElements in v4)
|
|
237
249
|
|
|
238
250
|
=== Token elements (leaf nodes)
|
|
239
251
|
|
|
@@ -257,6 +269,7 @@ Container elements hold child elements via `#{tag}_value` collection attributes:
|
|
|
257
269
|
[source,ruby]
|
|
258
270
|
----
|
|
259
271
|
Mml::V3::Mrow.new(
|
|
272
|
+
lutaml_register: Mml::V3::Configuration.context_id,
|
|
260
273
|
mi_value: [Mml::V3::Mi.new(value: "x")],
|
|
261
274
|
mo_value: [Mml::V3::Mo.new(value: "+")],
|
|
262
275
|
mn_value: [Mml::V3::Mn.new(value: "1")],
|
|
@@ -271,8 +284,10 @@ Build an expression tree by nesting elements:
|
|
|
271
284
|
[source,ruby]
|
|
272
285
|
----
|
|
273
286
|
Mml::V3::Math.new(
|
|
287
|
+
lutaml_register: Mml::V3::Configuration.context_id,
|
|
274
288
|
mfrac_value: [
|
|
275
289
|
Mml::V3::Mfrac.new(
|
|
290
|
+
lutaml_register: Mml::V3::Configuration.context_id,
|
|
276
291
|
mi_value: [Mml::V3::Mi.new(value: "a"), Mml::V3::Mi.new(value: "b")],
|
|
277
292
|
),
|
|
278
293
|
],
|
|
@@ -285,11 +300,19 @@ Mml::V3::Math.new(
|
|
|
285
300
|
[source,ruby]
|
|
286
301
|
----
|
|
287
302
|
Mml::V3::Mtable.new(
|
|
303
|
+
lutaml_register: Mml::V3::Configuration.context_id,
|
|
288
304
|
mtr_value: [
|
|
289
305
|
Mml::V3::Mtr.new(
|
|
306
|
+
lutaml_register: Mml::V3::Configuration.context_id,
|
|
290
307
|
mtd_value: [
|
|
291
|
-
Mml::V3::Mtd.new(
|
|
292
|
-
|
|
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
|
+
),
|
|
293
316
|
],
|
|
294
317
|
),
|
|
295
318
|
],
|
|
@@ -301,6 +324,7 @@ Mml::V3::Mtable.new(
|
|
|
301
324
|
[source,ruby]
|
|
302
325
|
----
|
|
303
326
|
Mml::V4::A.new(
|
|
327
|
+
lutaml_register: Mml::V4::Configuration.context_id,
|
|
304
328
|
href: "https://example.com",
|
|
305
329
|
hreflang: "en",
|
|
306
330
|
mi_value: [Mml::V4::Mi.new(value: "click")]
|
|
@@ -308,41 +332,116 @@ Mml::V4::A.new(
|
|
|
308
332
|
# => <a href="https://example.com" hreflang="en"><mi>click</mi></a>
|
|
309
333
|
----
|
|
310
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
|
+
|
|
311
364
|
== Internal architecture
|
|
312
365
|
|
|
313
366
|
=== Element class patterns
|
|
314
367
|
|
|
315
|
-
|
|
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.
|
|
316
370
|
|
|
317
|
-
* *Leaf elements*:
|
|
318
|
-
* *Container 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.
|
|
319
375
|
|
|
320
376
|
[source,ruby]
|
|
321
377
|
----
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
328
388
|
end
|
|
329
389
|
end
|
|
330
390
|
|
|
331
|
-
#
|
|
332
|
-
class
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# V3 container
|
|
403
|
+
class V3::Mrow < CommonElements
|
|
404
|
+
include Base::Mrow
|
|
337
405
|
end
|
|
338
406
|
----
|
|
339
407
|
|
|
340
|
-
===
|
|
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.
|
|
341
415
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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)
|
|
428
|
+
----
|
|
429
|
+
|
|
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
|
+
)
|
|
442
|
+
|
|
443
|
+
math.to_xml
|
|
444
|
+
----
|
|
346
445
|
|
|
347
446
|
=== Namespace
|
|
348
447
|
|
|
@@ -355,19 +454,43 @@ Three input forms are supported:
|
|
|
355
454
|
|
|
356
455
|
== Configuration
|
|
357
456
|
|
|
358
|
-
Configuration is version-specific. Use the namespace matching your target version:
|
|
359
|
-
|
|
360
457
|
[source,ruby]
|
|
361
458
|
----
|
|
362
|
-
# Switch XML adapter (default: :ox,
|
|
459
|
+
# Switch XML adapter (default: :ox, :oga on Opal)
|
|
363
460
|
Mml::V3::Configuration.adapter = :nokogiri
|
|
364
|
-
Mml::V4::Configuration.adapter = :nokogiri
|
|
365
461
|
|
|
366
|
-
#
|
|
367
|
-
Mml::V3::Configuration.
|
|
368
|
-
Mml::V4::Configuration.
|
|
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
|
+
)
|
|
479
|
+
|
|
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)
|
|
369
485
|
----
|
|
370
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
|
+
|
|
371
494
|
== Development
|
|
372
495
|
|
|
373
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
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mml
|
|
4
|
+
module Base
|
|
5
|
+
module Malignmark
|
|
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 :edge, :string
|
|
13
|
+
|
|
14
|
+
xml do
|
|
15
|
+
namespace Mml::Namespace
|
|
16
|
+
element "malignmark"
|
|
17
|
+
|
|
18
|
+
map_attribute "mathcolor", to: :mathcolor
|
|
19
|
+
map_attribute "mathbackground", to: :mathbackground
|
|
20
|
+
map_attribute "edge", to: :edge
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mml
|
|
4
|
+
module Base
|
|
5
|
+
module Math
|
|
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 :display, :string
|
|
11
|
+
|
|
12
|
+
xml do
|
|
13
|
+
namespace Mml::Namespace
|
|
14
|
+
element "math"
|
|
15
|
+
mixed_content
|
|
16
|
+
|
|
17
|
+
map_attribute :display, to: :display
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mml
|
|
4
|
+
module Base
|
|
5
|
+
module Menclose
|
|
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 :notation, :string
|
|
13
|
+
|
|
14
|
+
xml do
|
|
15
|
+
namespace Mml::Namespace
|
|
16
|
+
element "menclose"
|
|
17
|
+
mixed_content
|
|
18
|
+
|
|
19
|
+
map_attribute "mathcolor", to: :mathcolor
|
|
20
|
+
map_attribute "mathbackground", to: :mathbackground
|
|
21
|
+
map_attribute "notation", to: :notation
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mml
|
|
4
|
+
module Base
|
|
5
|
+
module Merror
|
|
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 :mathbackground, :string
|
|
11
|
+
attribute :mathcolor, :string
|
|
12
|
+
|
|
13
|
+
xml do
|
|
14
|
+
namespace Mml::Namespace
|
|
15
|
+
element "merror"
|
|
16
|
+
mixed_content
|
|
17
|
+
|
|
18
|
+
map_attribute "mathcolor", to: :mathcolor
|
|
19
|
+
map_attribute "mathbackground", to: :mathbackground
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mml
|
|
4
|
+
module Base
|
|
5
|
+
module Mfenced
|
|
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 :mathbackground, :string
|
|
11
|
+
attribute :separators, :string
|
|
12
|
+
attribute :mathcolor, :string
|
|
13
|
+
attribute :content, :string
|
|
14
|
+
attribute :close, :string
|
|
15
|
+
attribute :open, :string
|
|
16
|
+
|
|
17
|
+
xml do
|
|
18
|
+
namespace Mml::Namespace
|
|
19
|
+
element "mfenced"
|
|
20
|
+
mixed_content
|
|
21
|
+
|
|
22
|
+
map_content to: :content
|
|
23
|
+
|
|
24
|
+
map_attribute "mathbackground", to: :mathbackground
|
|
25
|
+
map_attribute "separators", to: :separators
|
|
26
|
+
map_attribute "mathcolor", to: :mathcolor
|
|
27
|
+
map_attribute "close", to: :close
|
|
28
|
+
map_attribute "open", to: :open
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|