Skip to content

plurimath/omml

Repository files navigation

Omml: OMML parser and builder for Ruby

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 embedded w:rPr run properties

  • Opal support: Runs in the browser via Ruby-to-JavaScript compilation

Installation

gem 'omml'
$ bundle install
# or
$ gem install omml

Quick start

require "omml"

# Parse OMML XML
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>')
# => #<Omml::Models::OMath:...>

# Serialize back to XML
math.to_xml(prefix: 'm')
# => "<m:oMath xmlns:m=\"...\">...</m:oMath>"

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

Round-trip (parse, modify, serialize)

math = Omml.parse(xml_string)
# Access and modify the model tree
math.r.first.t.content = "new value"
math.to_xml(prefix: 'm')

Internal architecture

Parsing flow

Omml.parse(xml_string) delegates to Parser.parse, which:

  1. Configures the XML adapter (:ox on MRI, :oga on Opal)

  2. Parses the XML string into a document

  3. Resolves the root class from the root element name (OMath or OMathPara)

  4. Calls .of_xml to 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

Type substitutions

Many OMML simple types are aliases for built-in Lutaml types (e.g., STStringLutaml::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 val values (1/0) on CT_OnOff elements where the XSD specifies on/off enumeration

  • r elements directly under oMathPara (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 Ribose Inc.

About

OMML library of Plurimath

Resources

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors