Project

chemicalml

0.0
The project is in a healthy, maintained state
ChemML provides a lutaml-model-based Ruby object model for the Chemical Markup Language (CML). Parse, manipulate, and serialize CML XML with full round-trip fidelity. Designed as a sibling model to other chemistry model layers (e.g. AsciiChem).
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.8.0
 Project Readme

ChemML

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.