Purpose
Omml provides OMML (Office Math Markup Language) XML parsing and serialization for Ruby. It maps the full OMML element set into Ruby model classes using the lutaml-model framework and is used by Plurimath for mathematical formula representation.
Key features:
-
Round-trip fidelity: Parse XML to an object graph, modify, and serialize back
-
Full schema coverage: 172 complex types, 53 simple types, and 17 group models generated from the OOXML Shared Math schema (
shared-math.xsd) -
Namespace handling: OMML namespace with
m:prefix, plus Word ML namespace for embeddedw:rPrrun properties -
Opal support: Runs in the browser via Ruby-to-JavaScript compilation
Installation
gem 'omml'
$ bundle install
# or
$ gem install omml
Quick start
Parsing and serialization
Parsing
Omml.parse accepts an XML string and returns a model tree. The root element
determines the wrapper class:
-
<m:oMath>→Omml::Models::OMath -
<m:oMathPara>→Omml::Models::OMathPara
# oMath root
math = Omml.parse('<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"><m:r><m:t>x</m:t></m:r></m:oMath>')
math.class # => Omml::Models::OMath
# oMathPara root
para = Omml.parse('<m:oMathPara xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"><m:oMath><m:r><m:t>y</m:t></m:r></m:oMath></m:oMathPara>')
para.class # => Omml::Models::OMathPara
Serialization
math.to_xml # Default serialization
math.to_xml(prefix: 'm') # With m: prefix on all elements
Internal architecture
Parsing flow
Omml.parse(xml_string) delegates to Parser.parse, which:
-
Configures the XML adapter (
:oxon MRI,:ogaon Opal) -
Parses the XML string into a document
-
Resolves the root class from the root element name (
OMathorOMathPara) -
Calls
.of_xmlto deserialize into a Lutaml model tree
Type context system
Omml::Configuration manages a Lutaml::Model::GlobalContext (context ID :omml)
that holds a registry of all model classes. Models register themselves at load
time via Omml::Configuration.register_model. The context is populated lazily on
first parse.
Custom contexts can be created for downstream libraries (e.g., Plurimath) that fall back to the OMML context:
# Create a custom context with OMML fallback
Omml::Configuration.create_context(id: :custom_omml)
# Parse using the custom context
Omml.parse(xml, context: :custom_omml)
Model IDs are derived from class names by stripping the CT/EG/ST prefix
and snake-casing with the prefix (e.g., CTOMath → :ct_o_math,
EGOMathElements → :eg_o_math_elements).
Model layer (lib/omml/models/)
Three categories of models, all extending Lutaml::Model::Serializable:
Complex types (ct_*.rb): Represent OMML schema complex types. Each defines
attributes and XML element mappings, and self-registers via
Omml::Configuration.register_model.
Simple types (simple_types/st_*.rb): Enum/value types like STJc, STOnOff.
These wrap string values with validation.
Group models (groups/eg_*.rb): Shared compositional groups (e.g.,
EGOMathElements). These use import_model_attributes /
import_model_mappings to compose attributes from other groups, implementing
the OMML schema’s choice/sequence patterns.
Root element wrappers (o_math.rb, o_math_para.rb) extend
CommonCode::RootModel and use omml_root_element to declare themselves as XML
root elements with the OMML namespace.
Namespaces
-
Omml::Namespace— OMML namespace (http://schemas.openxmlformats.org/officeDocument/2006/math, prefixm) -
Omml::WordprocessingNamespace— Word ML namespace (prefixw), used for elements likew:rPrthat appear inside OMMLctrlPrelements
Type substitutions
Many OMML simple types are aliases for built-in Lutaml types (e.g., STString
→ Lutaml::Model::Type::String). These are registered as aliases in the
context rather than as separate model classes, via Omml::TypeSubstitutions.
Configuration
# XML adapter (default: :ox, :oga on Opal)
Omml.configure_adapter!
Omml::Configuration.adapter = :oga
# Access the built-in context
Omml::Configuration.context_id # => :omml
Omml::Configuration.context
# Rebuild after a reset
Omml::Configuration.populate_context!
# Resolve a model class by its context ID
Omml::Configuration.resolve_type(:ct_o_math)
# => Omml::Models::CTOMath
Test Suite and Fixtures
The gem uses 279 OMML fixture files from spec/fixtures/omml/ for round-trip
validation. Each fixture is parsed, serialized, and the output is compared
structurally against the original.
Running Tests
bundle exec rake # Run all specs + rubocop
bundle exec rspec # Run all tests
bundle exec rspec spec/omml_spec.rb # Run specific test file
bundle exec rspec spec/omml_spec.rb:77 # Run single test by line
bundle exec rspec --only-failures # Re-run failures
XSD Validation
The fixture round-trips are validated against the OOXML shared-math.xsd
schema. Some fixtures contain Word-specific deviations from the strict XSD:
-
Element ordering differences (Word may output children in a different order than the schema sequence)
-
Numeric
valvalues (1/0) onCT_OnOffelements where the XSD specifieson/offenumeration -
relements directly underoMathPara(Word output, not in the XSD sequence)
These are well-known Word compatibilities and do not represent parser bugs.
Development
bundle install # Install dependencies
bundle exec rake # Run specs + rubocop
bundle exec rspec # Run tests
bundle exec rubocop # Lint
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/plurimath/omml.
Copyright and license
Copyright Ribose Inc.