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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +14 -4
- data/CLAUDE.md +79 -0
- data/README.adoc +226 -83
- 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 +33 -25
- 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
|
@@ -39,15 +39,16 @@ $ gem install mml
|
|
|
39
39
|
----
|
|
40
40
|
require "mml"
|
|
41
41
|
|
|
42
|
-
# Parse MathML
|
|
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
|
-
#
|
|
50
|
-
|
|
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
|
-
│ (
|
|
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
|
-
│ │
|
|
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::
|
|
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 (
|
|
111
|
-
* `fontfamily`, `fontweight`, `fontstyle`, `fontsize`, `color`, `background` on `mstyle`, `mglyph`, `mspace`
|
|
112
|
-
* `
|
|
113
|
-
* `
|
|
114
|
-
|
|
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
|
|
121
|
+
=== Migration from previous versions
|
|
118
122
|
|
|
119
|
-
====
|
|
123
|
+
==== require and configuration
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
The `Mml` module no longer aliases versioned constants. Use the explicit version
|
|
126
|
+
namespace:
|
|
122
127
|
|
|
123
128
|
[source,ruby]
|
|
124
129
|
----
|
|
125
|
-
#
|
|
126
|
-
|
|
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
|
-
#
|
|
129
|
-
|
|
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
|
-
====
|
|
158
|
+
==== Element class references
|
|
133
159
|
|
|
134
|
-
|
|
160
|
+
All element classes live under their version namespace:
|
|
135
161
|
|
|
136
162
|
[source,ruby]
|
|
137
163
|
----
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
168
|
+
# After
|
|
169
|
+
Mml::V3::Mi.new(value: "x")
|
|
170
|
+
Mml::V3::Mrow.new(mi_value: [...])
|
|
146
171
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
#
|
|
159
|
-
Mml
|
|
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
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
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(
|
|
276
|
-
|
|
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
|
-
|
|
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*:
|
|
302
|
-
* *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.
|
|
303
375
|
|
|
304
376
|
[source,ruby]
|
|
305
377
|
----
|
|
306
|
-
#
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
#
|
|
316
|
-
class
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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,
|
|
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
|
-
#
|
|
348
|
-
Mml::
|
|
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
|