ChemML provides a Chemical Markup Language (CML) object model for Ruby, built on lutaml-model[https://github.com/lutaml/lutaml-model].

Features

  • Full Schema 3 and Schema 2.4 coverage — every CML element, attribute, and XSD enum/pattern modelled as Ruby classes.

  • Format-agnostic serialization — XML, JSON, YAML, and TOML round-trip via lutaml-model adapters.

  • Eight built-in conventions with 88+ registered constraints: molecular, compchem, dictionary, unit-dictionary, unitType-dictionary, spectroscopy, cascade, simpleUnit.

  • Built-in dictionaries (cml, compchem, cif, unit_si, unit_non_si, unit_type, cml_name, cml_formula) loaded from YAML.

  • CLI for command-line validation and inspection.

  • Ruby-native query API (doc.find_atom(id), doc.each_molecule).

Install

Add to your Gemfile:

gem "chemicalml"

Or install directly:

$ gem install chemicalml

Usage

Parse a CML document

require "chemicalml"

doc = Chemicalml.parse_file("ethanol.cml")
doc.molecules.first.atom_array.atoms.first.element_type # => "C"

Validate against a convention

report = Chemicalml.validate(doc)
puts report.summary
puts report.errors.map(&:message)

Build a document programmatically

atom = Chemicalml::Cml::Atom.new(id: "a1", element_type: "C")
mol  = Chemicalml::Cml::Molecule.new(
  id: "m1",
  atom_array: Chemicalml::Cml::AtomArray.new(atoms: [atom])
)
doc  = Chemicalml::Cml::Document.new(molecules: [mol])

doc.to_xml # => well-formed CML XML

Query the tree

doc.each_atom { |a| puts "#{a.id}: #{a.element_type}" }
doc.find_atom("a1")     # => Atom instance
doc.atom_count          # => recursive count
doc.find_molecule("m1") # => Molecule instance

Cross-format serialisation

doc.to_xml   # CML XML
doc.to_json  # JSON with CML wire names (elementType, atomArray, ...)
doc.to_yaml  # YAML

Resolve references

resolver = Chemicalml::Cml::ReferenceResolver.new(doc)
resolver.unresolved_refs  # => [{ node: <Bond>, attr: :atom_refs2, missing: ["a99"] }]

CLI

$ chemicalml validate file.cml    # auto-detect convention, print violations
$ chemicalml inspect file.cml     # tree-style summary
$ chemicalml conventions          # list 8 registered conventions
$ chemicalml dictionaries         # list built-in YAML dictionaries
$ chemicalml elements             # list all CML wire classes

Conventions

Eight conventions register 88+ constraints:

Convention Constraints Description

molecular

35

Atom/bond/molecule well-formedness, id patterns, element type validation, dictRef resolution, AtomParity parent check, references-resolve check.

compchem

20

JobList/Job/Initialization/Calculation/Finalization module structure, scalar/array/matrix value rules.

dictionary

8

Dictionary namespace, prefix, entry id/term/definition, unitType/units co-constraints.

unit-dictionary

8

UnitList namespace, unit id/title/symbol/parentSI/ multiplierToSI, definition child.

unitType-dictionary

5

UnitTypeList namespace, unitType id/name/definition.

spectroscopy

5

Spectrum convention/format/content, peakList, peak values.

cascade

4

ReactionScheme content, reactionStepList, reactiveCentre atomRefs.

simpleUnit

3

simpleUnit root, unit power, unit symbol.

Add a custom convention at runtime:

module MyConvention
  extend Chemicalml::Convention::Base

  def self.qname; "convention:my"; end
  def self.namespace_uri; "http://example.com/my"; end

  register MyConstraintClass
end

Chemicalml::Convention::Registry.register_custom(MyConvention)

Dictionaries

Eight built-in YAML dictionaries ship under data/dictionaries/:

Dictionary Prefix

cml

cml

cml_name

cmlName

cml_formula

cmlFormula

cif

cif

compchem

compchem

unit_si

si

unit_non_si

nonsi

unit_type

unitType

Load via:

dict = Chemicalml::Dictionary.load(:cml)
entry = Chemicalml::Dictionary::Registry.lookup("cml:bp")
entry.term        # => "Boiling Point"
entry.definition  # => "The temperature at which a substance boils..."

Design

Each CML element is a Lutaml::Model::Serializable subclass with declared attributes and parallel xml do …​ end and key_value do …​ end mapping blocks. Serialisation is fully framework-backed — no hand-rolled XML.

  • Cml::Base::* modules declare shared attributes (mixed into both Schema3 and Schema24 wire classes).

  • Cml::Role::* modules are type-markers used by the convention constraint walker for is_a? dispatch.

  • Cml::Elements::ALL is the single source of truth for the CML element table.

  • Cml::Enums and Cml::Patterns mirror XSD simpleTypes.

  • Cml::CommonChildren provides the universal metadataList, label, name, description child set as a DRY mixin.

Reference docs

reference-docs/ holds archival source material from xml-cml.org: the XSDs (schemas/schema3/schema.xsd, schemas/schema24/schema.xsd), the convention specifications, and the CMLLite paper. These are source material, not derived output — never delete or edit them.

Acknowledgements

CML is the work of the xml-cml.org community, edited by Sam Adams, Joe Townsend, Weerapong Phadungsukanan, and Jens Thomas, with originator Peter Murray-Rust and contributors Alex Wade, Nick England, Daniel Lowe, Hannah Barjat, and Egon Willighagen. The schema, conventions, dictionaries, and examples are licensed by xml-cml.org under CC-BY-3.0. See NOTICES.adoc for full attribution of upstream content redistributed in this gem.

License

BSD-2-Clause. See LICENSE.