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 foris_a?dispatch. -
Cml::Elements::ALLis the single source of truth for the CML element table. -
Cml::EnumsandCml::Patternsmirror XSD simpleTypes. -
Cml::CommonChildrenprovides the universalmetadataList,label,name,descriptionchild 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.