chemicalml 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +198 -0
- data/Gemfile +14 -0
- data/LICENSE +24 -0
- data/NOTICES.adoc +115 -0
- data/README.adoc +87 -0
- data/Rakefile +8 -0
- data/TODO.align/01-cml-base-mixins.md +122 -0
- data/TODO.align/02-real-schema-hierarchies.md +88 -0
- data/TODO.align/03-configuration.md +50 -0
- data/TODO.align/04-parse-entry-points.md +59 -0
- data/TODO.align/05-schema-aware-translator.md +33 -0
- data/TODO.align/06-no-respond-to.md +44 -0
- data/TODO.align/07-canon-xml-comparison.md +43 -0
- data/TODO.align/08-expand-fixtures.md +37 -0
- data/TODO.align/09-final-spec-lint.md +29 -0
- data/TODO.align/10-role-marker-modules.md +54 -0
- data/TODO.align/11-dry-configuration.md +63 -0
- data/TODO.align/12-wire-class-macro.md +51 -0
- data/TODO.align/13-wire-namespace.md +44 -0
- data/TODO.align/14-schema24-correctness.md +37 -0
- data/TODO.align/15-canon-fixture-roundtrip.md +38 -0
- data/TODO.align/16-schema24-convention-coverage.md +33 -0
- data/TODO.align/17-final-spec-lint.md +28 -0
- data/TODO.align/README.md +27 -0
- data/TODO.align/README.round2.md +24 -0
- data/TODO.cml-full/01-reference-docs.md +43 -0
- data/TODO.cml-full/02-schema-versioned-model.md +70 -0
- data/TODO.cml-full/03-fixtures-scrape.md +52 -0
- data/TODO.cml-full/04-convention-framework.md +69 -0
- data/TODO.cml-full/05-dictionary-layer.md +77 -0
- data/TODO.cml-full/06-extend-canonical-translator.md +60 -0
- data/TODO.cml-full/07-attribution-notices.md +42 -0
- data/TODO.cml-full/08-autoload-only.md +48 -0
- data/TODO.cml-full/09-spec-coverage.md +38 -0
- data/TODO.cml-full/README.md +23 -0
- data/chemicalml.gemspec +35 -0
- data/data/dictionaries/_index.yaml +14 -0
- data/data/dictionaries/compchem.yaml +481 -0
- data/data/dictionaries/unit_non_si.yaml +53 -0
- data/data/dictionaries/unit_si.yaml +87 -0
- data/data/dictionaries/unit_type.yaml +113 -0
- data/lib/chemicalml/cml/array.rb +10 -0
- data/lib/chemicalml/cml/atom.rb +10 -0
- data/lib/chemicalml/cml/atom_array.rb +10 -0
- data/lib/chemicalml/cml/atom_parity.rb +10 -0
- data/lib/chemicalml/cml/base/array.rb +36 -0
- data/lib/chemicalml/cml/base/atom.rb +38 -0
- data/lib/chemicalml/cml/base/atom_array.rb +22 -0
- data/lib/chemicalml/cml/base/atom_parity.rb +24 -0
- data/lib/chemicalml/cml/base/bond.rb +28 -0
- data/lib/chemicalml/cml/base/bond_array.rb +22 -0
- data/lib/chemicalml/cml/base/bond_stereo.rb +28 -0
- data/lib/chemicalml/cml/base/cml_module.rb +40 -0
- data/lib/chemicalml/cml/base/dictionary.rb +32 -0
- data/lib/chemicalml/cml/base/dictionary_entry.rb +34 -0
- data/lib/chemicalml/cml/base/document.rb +26 -0
- data/lib/chemicalml/cml/base/formula.rb +38 -0
- data/lib/chemicalml/cml/base/identifier.rb +26 -0
- data/lib/chemicalml/cml/base/label.rb +28 -0
- data/lib/chemicalml/cml/base/list.rb +36 -0
- data/lib/chemicalml/cml/base/matrix.rb +38 -0
- data/lib/chemicalml/cml/base/metadata.rb +30 -0
- data/lib/chemicalml/cml/base/metadata_list.rb +28 -0
- data/lib/chemicalml/cml/base/molecule.rb +40 -0
- data/lib/chemicalml/cml/base/name.rb +26 -0
- data/lib/chemicalml/cml/base/parameter.rb +34 -0
- data/lib/chemicalml/cml/base/parameter_list.rb +28 -0
- data/lib/chemicalml/cml/base/product.rb +22 -0
- data/lib/chemicalml/cml/base/product_list.rb +22 -0
- data/lib/chemicalml/cml/base/property.rb +34 -0
- data/lib/chemicalml/cml/base/property_list.rb +28 -0
- data/lib/chemicalml/cml/base/reactant.rb +22 -0
- data/lib/chemicalml/cml/base/reactant_list.rb +22 -0
- data/lib/chemicalml/cml/base/reaction.rb +32 -0
- data/lib/chemicalml/cml/base/reaction_list.rb +22 -0
- data/lib/chemicalml/cml/base/scalar.rb +32 -0
- data/lib/chemicalml/cml/base/substance.rb +30 -0
- data/lib/chemicalml/cml/base/unit.rb +38 -0
- data/lib/chemicalml/cml/base/unit_list.rb +30 -0
- data/lib/chemicalml/cml/base/unit_type.rb +28 -0
- data/lib/chemicalml/cml/base/unit_type_list.rb +30 -0
- data/lib/chemicalml/cml/base.rb +51 -0
- data/lib/chemicalml/cml/bond.rb +10 -0
- data/lib/chemicalml/cml/bond_array.rb +10 -0
- data/lib/chemicalml/cml/bond_stereo.rb +10 -0
- data/lib/chemicalml/cml/cml_module.rb +10 -0
- data/lib/chemicalml/cml/dictionary.rb +10 -0
- data/lib/chemicalml/cml/dictionary_entry.rb +10 -0
- data/lib/chemicalml/cml/document.rb +10 -0
- data/lib/chemicalml/cml/elements.rb +58 -0
- data/lib/chemicalml/cml/formula.rb +10 -0
- data/lib/chemicalml/cml/identifier.rb +10 -0
- data/lib/chemicalml/cml/label.rb +10 -0
- data/lib/chemicalml/cml/list.rb +10 -0
- data/lib/chemicalml/cml/matrix.rb +10 -0
- data/lib/chemicalml/cml/metadata.rb +10 -0
- data/lib/chemicalml/cml/metadata_list.rb +10 -0
- data/lib/chemicalml/cml/molecule.rb +10 -0
- data/lib/chemicalml/cml/name.rb +10 -0
- data/lib/chemicalml/cml/namespace.rb +17 -0
- data/lib/chemicalml/cml/parameter.rb +10 -0
- data/lib/chemicalml/cml/parameter_list.rb +10 -0
- data/lib/chemicalml/cml/product.rb +10 -0
- data/lib/chemicalml/cml/product_list.rb +10 -0
- data/lib/chemicalml/cml/property.rb +10 -0
- data/lib/chemicalml/cml/property_list.rb +10 -0
- data/lib/chemicalml/cml/reactant.rb +10 -0
- data/lib/chemicalml/cml/reactant_list.rb +10 -0
- data/lib/chemicalml/cml/reaction.rb +10 -0
- data/lib/chemicalml/cml/reaction_list.rb +10 -0
- data/lib/chemicalml/cml/role/array.rb +13 -0
- data/lib/chemicalml/cml/role/atom.rb +13 -0
- data/lib/chemicalml/cml/role/atom_array.rb +13 -0
- data/lib/chemicalml/cml/role/atom_parity.rb +13 -0
- data/lib/chemicalml/cml/role/bond.rb +13 -0
- data/lib/chemicalml/cml/role/bond_array.rb +13 -0
- data/lib/chemicalml/cml/role/bond_stereo.rb +13 -0
- data/lib/chemicalml/cml/role/cml_module.rb +14 -0
- data/lib/chemicalml/cml/role/dictionary.rb +13 -0
- data/lib/chemicalml/cml/role/dictionary_entry.rb +13 -0
- data/lib/chemicalml/cml/role/document.rb +13 -0
- data/lib/chemicalml/cml/role/formula.rb +13 -0
- data/lib/chemicalml/cml/role/identifier.rb +13 -0
- data/lib/chemicalml/cml/role/label.rb +13 -0
- data/lib/chemicalml/cml/role/list.rb +13 -0
- data/lib/chemicalml/cml/role/matrix.rb +13 -0
- data/lib/chemicalml/cml/role/metadata.rb +13 -0
- data/lib/chemicalml/cml/role/metadata_list.rb +13 -0
- data/lib/chemicalml/cml/role/molecule.rb +13 -0
- data/lib/chemicalml/cml/role/name.rb +13 -0
- data/lib/chemicalml/cml/role/parameter.rb +13 -0
- data/lib/chemicalml/cml/role/parameter_list.rb +13 -0
- data/lib/chemicalml/cml/role/product.rb +13 -0
- data/lib/chemicalml/cml/role/product_list.rb +13 -0
- data/lib/chemicalml/cml/role/property.rb +13 -0
- data/lib/chemicalml/cml/role/property_list.rb +13 -0
- data/lib/chemicalml/cml/role/reactant.rb +13 -0
- data/lib/chemicalml/cml/role/reactant_list.rb +13 -0
- data/lib/chemicalml/cml/role/reaction.rb +13 -0
- data/lib/chemicalml/cml/role/reaction_list.rb +13 -0
- data/lib/chemicalml/cml/role/scalar.rb +13 -0
- data/lib/chemicalml/cml/role/substance.rb +13 -0
- data/lib/chemicalml/cml/role/unit.rb +13 -0
- data/lib/chemicalml/cml/role/unit_list.rb +13 -0
- data/lib/chemicalml/cml/role/unit_type.rb +13 -0
- data/lib/chemicalml/cml/role/unit_type_list.rb +13 -0
- data/lib/chemicalml/cml/role.rb +54 -0
- data/lib/chemicalml/cml/scalar.rb +10 -0
- data/lib/chemicalml/cml/schema24/configuration.rb +19 -0
- data/lib/chemicalml/cml/schema24.rb +40 -0
- data/lib/chemicalml/cml/schema3/configuration.rb +17 -0
- data/lib/chemicalml/cml/schema3.rb +40 -0
- data/lib/chemicalml/cml/substance.rb +10 -0
- data/lib/chemicalml/cml/translator/value_translations.rb +265 -0
- data/lib/chemicalml/cml/translator.rb +233 -0
- data/lib/chemicalml/cml/unit.rb +10 -0
- data/lib/chemicalml/cml/unit_list.rb +10 -0
- data/lib/chemicalml/cml/unit_type.rb +10 -0
- data/lib/chemicalml/cml/unit_type_list.rb +10 -0
- data/lib/chemicalml/cml/visitable.rb +65 -0
- data/lib/chemicalml/cml/wire_class_macro.rb +45 -0
- data/lib/chemicalml/cml.rb +55 -0
- data/lib/chemicalml/context_configuration.rb +127 -0
- data/lib/chemicalml/convention/base.rb +48 -0
- data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +33 -0
- data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +38 -0
- data/lib/chemicalml/convention/compchem/constraints.rb +14 -0
- data/lib/chemicalml/convention/compchem.rb +28 -0
- data/lib/chemicalml/convention/constraint.rb +79 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +33 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +34 -0
- data/lib/chemicalml/convention/dictionary/constraints.rb +14 -0
- data/lib/chemicalml/convention/dictionary.rb +28 -0
- data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +30 -0
- data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +42 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +54 -0
- data/lib/chemicalml/convention/molecular/constraints.rb +19 -0
- data/lib/chemicalml/convention/molecular.rb +29 -0
- data/lib/chemicalml/convention/registry.rb +50 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +46 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints.rb +12 -0
- data/lib/chemicalml/convention/unit_dictionary.rb +27 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +34 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +12 -0
- data/lib/chemicalml/convention/unit_type_dictionary.rb +27 -0
- data/lib/chemicalml/convention/violation.rb +36 -0
- data/lib/chemicalml/convention.rb +39 -0
- data/lib/chemicalml/dictionary/entry.rb +59 -0
- data/lib/chemicalml/dictionary/enum.rb +51 -0
- data/lib/chemicalml/dictionary/link.rb +32 -0
- data/lib/chemicalml/dictionary/loader.rb +60 -0
- data/lib/chemicalml/dictionary/model.rb +64 -0
- data/lib/chemicalml/dictionary/registry.rb +75 -0
- data/lib/chemicalml/dictionary.rb +26 -0
- data/lib/chemicalml/errors.rb +7 -0
- data/lib/chemicalml/model/atom.rb +40 -0
- data/lib/chemicalml/model/bond.rb +33 -0
- data/lib/chemicalml/model/cml_array.rb +27 -0
- data/lib/chemicalml/model/cml_module.rb +45 -0
- data/lib/chemicalml/model/document.rb +29 -0
- data/lib/chemicalml/model/formula.rb +34 -0
- data/lib/chemicalml/model/identifier.rb +21 -0
- data/lib/chemicalml/model/label.rb +21 -0
- data/lib/chemicalml/model/matrix.rb +29 -0
- data/lib/chemicalml/model/metadata.rb +23 -0
- data/lib/chemicalml/model/metadata_list.rb +25 -0
- data/lib/chemicalml/model/molecule.rb +38 -0
- data/lib/chemicalml/model/name.rb +21 -0
- data/lib/chemicalml/model/node.rb +45 -0
- data/lib/chemicalml/model/parameter.rb +27 -0
- data/lib/chemicalml/model/parameter_list.rb +25 -0
- data/lib/chemicalml/model/product.rb +21 -0
- data/lib/chemicalml/model/product_list.rb +21 -0
- data/lib/chemicalml/model/property.rb +28 -0
- data/lib/chemicalml/model/property_list.rb +25 -0
- data/lib/chemicalml/model/reactant.rb +21 -0
- data/lib/chemicalml/model/reactant_list.rb +21 -0
- data/lib/chemicalml/model/reaction.rb +41 -0
- data/lib/chemicalml/model/reaction_list.rb +23 -0
- data/lib/chemicalml/model/scalar.rb +25 -0
- data/lib/chemicalml/model/substance.rb +25 -0
- data/lib/chemicalml/model.rb +38 -0
- data/lib/chemicalml/schema/definition.rb +41 -0
- data/lib/chemicalml/schema/registry.rb +30 -0
- data/lib/chemicalml/schema.rb +35 -0
- data/lib/chemicalml/version.rb +5 -0
- data/lib/chemicalml/versioned_parser.rb +43 -0
- data/lib/chemicalml.rb +47 -0
- data/lib/tasks/reference_fetch.rake +105 -0
- data/lib/tasks/scrape_fixtures.rake +133 -0
- data/reference-docs/conventions/compchem.md +141 -0
- data/reference-docs/conventions/dictionary.md +119 -0
- data/reference-docs/conventions/index.md +56 -0
- data/reference-docs/conventions/molecular.md +115 -0
- data/reference-docs/conventions/unit-dictionary.md +92 -0
- data/reference-docs/conventions/unitType-dictionary.md +51 -0
- data/reference-docs/dictionaries/compchem.md +335 -0
- data/reference-docs/dictionaries/index.md +52 -0
- data/reference-docs/examples/schema24-index.md +50 -0
- data/reference-docs/examples/schema3-compchem-index.md +14 -0
- data/reference-docs/examples/schema3-molecular-index.md +28 -0
- data/reference-docs/schemas/schema24/schema.xsd +10624 -0
- data/reference-docs/schemas/schema3/schema.xsd +11459 -0
- metadata +306 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Model
|
|
5
|
+
# A chemical reaction. Reactants, products, arrow kind, optional
|
|
6
|
+
# conditions (above / below the arrow).
|
|
7
|
+
class Reaction < Node
|
|
8
|
+
ARROWS = %i[forward reverse equilibrium resonance].freeze
|
|
9
|
+
|
|
10
|
+
attr_accessor :id, :reactant_list, :product_list, :arrow,
|
|
11
|
+
:conditions_above, :conditions_below, :title, :type
|
|
12
|
+
|
|
13
|
+
def initialize(id: nil, reactant_list:, product_list:,
|
|
14
|
+
arrow: :forward, conditions_above: nil,
|
|
15
|
+
conditions_below: nil, title: nil, type: nil)
|
|
16
|
+
@id = id
|
|
17
|
+
@reactant_list = reactant_list
|
|
18
|
+
@product_list = product_list
|
|
19
|
+
@arrow = arrow
|
|
20
|
+
@conditions_above = conditions_above
|
|
21
|
+
@conditions_below = conditions_below
|
|
22
|
+
@title = title
|
|
23
|
+
@type = type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def children
|
|
27
|
+
[reactant_list, product_list]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def value_attributes
|
|
31
|
+
{
|
|
32
|
+
id: id, reactant_list: reactant_list,
|
|
33
|
+
product_list: product_list, arrow: arrow,
|
|
34
|
+
conditions_above: conditions_above,
|
|
35
|
+
conditions_below: conditions_below,
|
|
36
|
+
title: title, type: type
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Model
|
|
5
|
+
# A reaction cascade: ordered list of `Reaction` instances where
|
|
6
|
+
# each step's products are the next step's reactants.
|
|
7
|
+
class ReactionList < Node
|
|
8
|
+
attr_accessor :reactions
|
|
9
|
+
|
|
10
|
+
def initialize(reactions: [])
|
|
11
|
+
@reactions = reactions
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def children
|
|
15
|
+
reactions
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def value_attributes
|
|
19
|
+
{ reactions: reactions }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Model
|
|
5
|
+
# A primitive scalar value with a data type and units.
|
|
6
|
+
class Scalar < Node
|
|
7
|
+
attr_accessor :id, :title, :dict_ref, :data_type, :units, :value
|
|
8
|
+
|
|
9
|
+
def initialize(value:, data_type: nil, units: nil, id: nil,
|
|
10
|
+
title: nil, dict_ref: nil)
|
|
11
|
+
@value = value
|
|
12
|
+
@data_type = data_type
|
|
13
|
+
@units = units
|
|
14
|
+
@id = id
|
|
15
|
+
@title = title
|
|
16
|
+
@dict_ref = dict_ref
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def value_attributes
|
|
20
|
+
{ value: value, data_type: data_type, units: units,
|
|
21
|
+
id: id, title: title, dict_ref: dict_ref }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Model
|
|
5
|
+
# A reaction participant. Wraps a `Molecule` with a `role`
|
|
6
|
+
# (e.g. `:reactant`, `:product`, `:catalyst`, `:solvent`).
|
|
7
|
+
class Substance < Node
|
|
8
|
+
attr_accessor :molecule, :role, :title
|
|
9
|
+
|
|
10
|
+
def initialize(molecule:, role: nil, title: nil)
|
|
11
|
+
@molecule = molecule
|
|
12
|
+
@role = role
|
|
13
|
+
@title = title
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def children
|
|
17
|
+
[molecule]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def value_attributes
|
|
21
|
+
{ molecule: molecule, role: role, title: title }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
# Canonical chemistry representation model. Format-agnostic plain
|
|
5
|
+
# Ruby classes; not tied to any wire format.
|
|
6
|
+
#
|
|
7
|
+
# Every adapter (CML XML, AsciiChem text, future SMILES/InChI/MOL)
|
|
8
|
+
# speaks the canonical model. Adapters never talk to each other
|
|
9
|
+
# directly — only through these classes.
|
|
10
|
+
module Model
|
|
11
|
+
autoload :Array, "chemicalml/model/cml_array"
|
|
12
|
+
autoload :Atom, "chemicalml/model/atom"
|
|
13
|
+
autoload :Bond, "chemicalml/model/bond"
|
|
14
|
+
autoload :Document, "chemicalml/model/document"
|
|
15
|
+
autoload :Formula, "chemicalml/model/formula"
|
|
16
|
+
autoload :Identifier, "chemicalml/model/identifier"
|
|
17
|
+
autoload :Label, "chemicalml/model/label"
|
|
18
|
+
autoload :Matrix, "chemicalml/model/matrix"
|
|
19
|
+
autoload :Metadata, "chemicalml/model/metadata"
|
|
20
|
+
autoload :MetadataList, "chemicalml/model/metadata_list"
|
|
21
|
+
autoload :Module, "chemicalml/model/cml_module"
|
|
22
|
+
autoload :Molecule, "chemicalml/model/molecule"
|
|
23
|
+
autoload :Name, "chemicalml/model/name"
|
|
24
|
+
autoload :Node, "chemicalml/model/node"
|
|
25
|
+
autoload :Parameter, "chemicalml/model/parameter"
|
|
26
|
+
autoload :ParameterList, "chemicalml/model/parameter_list"
|
|
27
|
+
autoload :Product, "chemicalml/model/product"
|
|
28
|
+
autoload :ProductList, "chemicalml/model/product_list"
|
|
29
|
+
autoload :Property, "chemicalml/model/property"
|
|
30
|
+
autoload :PropertyList, "chemicalml/model/property_list"
|
|
31
|
+
autoload :Reaction, "chemicalml/model/reaction"
|
|
32
|
+
autoload :ReactionList, "chemicalml/model/reaction_list"
|
|
33
|
+
autoload :Reactant, "chemicalml/model/reactant"
|
|
34
|
+
autoload :ReactantList, "chemicalml/model/reactant_list"
|
|
35
|
+
autoload :Scalar, "chemicalml/model/scalar"
|
|
36
|
+
autoload :Substance, "chemicalml/model/substance"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Schema
|
|
5
|
+
# Value object describing one CML schema version. Immutable.
|
|
6
|
+
class Definition
|
|
7
|
+
attr_reader :version, :xsd_path, :xml_namespace, :ruby_namespace
|
|
8
|
+
|
|
9
|
+
def initialize(version:, xsd_path:, xml_namespace:, ruby_namespace:)
|
|
10
|
+
@version = version
|
|
11
|
+
@xsd_path = xsd_path
|
|
12
|
+
@xml_namespace = xml_namespace
|
|
13
|
+
@ruby_namespace = ruby_namespace
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def xsd_full_path
|
|
18
|
+
File.expand_path(xsd_path, project_root)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def wire_namespace_constant
|
|
22
|
+
ruby_namespace.split("::").reduce(Object) { |ns, name| ns.const_get(name) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def eql?(other)
|
|
26
|
+
other.is_a?(Definition) && version == other.version
|
|
27
|
+
end
|
|
28
|
+
alias == eql?
|
|
29
|
+
|
|
30
|
+
def hash
|
|
31
|
+
version.hash
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def project_root
|
|
37
|
+
File.expand_path("../../..", __dir__)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Schema
|
|
5
|
+
# Lookup table for schema versions. Adding a new schema = adding
|
|
6
|
+
# one entry to `Chemicalml::Schema::BUILTIN`. No mutation of
|
|
7
|
+
# existing entries, no switch statements.
|
|
8
|
+
module Registry
|
|
9
|
+
LOOKUP = Schema::BUILTIN.each_with_object({}) do |(key, defn), h|
|
|
10
|
+
h[key] = defn
|
|
11
|
+
h[key.to_s] = defn
|
|
12
|
+
end.freeze
|
|
13
|
+
|
|
14
|
+
def self.lookup(name)
|
|
15
|
+
entry = LOOKUP[name] || LOOKUP[name.to_sym] || LOOKUP[name.to_s]
|
|
16
|
+
raise ArgumentError, "unknown schema version: #{name.inspect}" unless entry
|
|
17
|
+
|
|
18
|
+
entry
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.versions
|
|
22
|
+
Schema::BUILTIN.keys.freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.default
|
|
26
|
+
lookup(:schema3)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
# Schema version registry. Maps version symbols (e.g. `:schema3`,
|
|
5
|
+
# `:schema24`) to their canonical metadata: XSD path, default XML
|
|
6
|
+
# namespace, and the Ruby sub-namespace that holds the wire classes.
|
|
7
|
+
#
|
|
8
|
+
# The two XSDs are source-of-truth archival files under
|
|
9
|
+
# `reference-docs/schemas/`. They are never modified or regenerated
|
|
10
|
+
# from code.
|
|
11
|
+
module Schema
|
|
12
|
+
autoload :Definition, "chemicalml/schema/definition"
|
|
13
|
+
autoload :Registry, "chemicalml/schema/registry"
|
|
14
|
+
|
|
15
|
+
CML_XML_NAMESPACE = "http://www.xml-cml.org/schema"
|
|
16
|
+
|
|
17
|
+
# Built-in versions registered at load time. Adding a new CML schema
|
|
18
|
+
# version = appending an entry here and creating the matching
|
|
19
|
+
# `Chemicalml::Cml::SchemaXxx` namespace. No existing code changes.
|
|
20
|
+
BUILTIN = {
|
|
21
|
+
schema24: Definition.new(
|
|
22
|
+
version: :schema24,
|
|
23
|
+
xsd_path: "reference-docs/schemas/schema24/schema.xsd",
|
|
24
|
+
xml_namespace: CML_XML_NAMESPACE,
|
|
25
|
+
ruby_namespace: "Chemicalml::Cml::Schema24"
|
|
26
|
+
),
|
|
27
|
+
schema3: Definition.new(
|
|
28
|
+
version: :schema3,
|
|
29
|
+
xsd_path: "reference-docs/schemas/schema3/schema.xsd",
|
|
30
|
+
xml_namespace: CML_XML_NAMESPACE,
|
|
31
|
+
ruby_namespace: "Chemicalml::Cml::Schema3"
|
|
32
|
+
)
|
|
33
|
+
}.freeze
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
# Shared parse entrypoint for versioned schema modules. Each schema
|
|
5
|
+
# module (`Cml::Schema3`, `Cml::Schema24`) extends this; it provides
|
|
6
|
+
# `.parse(xml)` that ensures the schema's wire classes are
|
|
7
|
+
# registered with `Lutaml::Model::GlobalContext`, then delegates to
|
|
8
|
+
# `Document.from_xml(xml, register: context_id)`.
|
|
9
|
+
#
|
|
10
|
+
# Mirrors `Mml::VersionedParser`.
|
|
11
|
+
module VersionedParser
|
|
12
|
+
def parse(xml, namespace_exist: true)
|
|
13
|
+
configuration.ensure_registered!
|
|
14
|
+
document_class.from_xml(
|
|
15
|
+
xml_input(xml, namespace_exist),
|
|
16
|
+
register: configuration.context_id
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def document_class
|
|
21
|
+
const_get(:Document)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def configuration
|
|
25
|
+
const_get(:Configuration)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def xml_input(xml, namespace_exist)
|
|
31
|
+
return xml if namespace_exist
|
|
32
|
+
|
|
33
|
+
namespace_uri = Chemicalml::Cml::Namespace.uri
|
|
34
|
+
inject_namespace(xml, namespace_uri)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def inject_namespace(xml_string, namespace_uri)
|
|
38
|
+
xml_string.sub(/<cml([\s>])/) do
|
|
39
|
+
"<cml xmlns=\"#{namespace_uri}\"#{Regexp.last_match(1)}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/chemicalml.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
# Chemicalml provides a Ruby object model for the Chemical Markup Language
|
|
6
|
+
# (CML).
|
|
7
|
+
#
|
|
8
|
+
# Top-level entry points:
|
|
9
|
+
# Chemicalml::Cml::Document.from_xml(xml) # parse CML
|
|
10
|
+
# document.to_xml # serialize back to XML
|
|
11
|
+
#
|
|
12
|
+
# Each CML element is a Lutaml::Model::Serializable subclass with
|
|
13
|
+
# declared attributes and an XML mapping block. Serialization is
|
|
14
|
+
# framework-backed — no hand-rolled XML in this library.
|
|
15
|
+
module Chemicalml
|
|
16
|
+
autoload :Cml, "chemicalml/cml"
|
|
17
|
+
autoload :Configuration, "chemicalml/configuration"
|
|
18
|
+
autoload :ContextConfiguration, "chemicalml/context_configuration"
|
|
19
|
+
autoload :Convention, "chemicalml/convention"
|
|
20
|
+
autoload :Dictionary, "chemicalml/dictionary"
|
|
21
|
+
autoload :Error, "chemicalml/errors"
|
|
22
|
+
autoload :Model, "chemicalml/model"
|
|
23
|
+
autoload :ParseError, "chemicalml/errors"
|
|
24
|
+
autoload :Schema, "chemicalml/schema"
|
|
25
|
+
autoload :VERSION, "chemicalml/version"
|
|
26
|
+
autoload :VersionedParser, "chemicalml/versioned_parser"
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def parse(xml, schema: :schema3, namespace_exist: true)
|
|
31
|
+
parser_for(schema).parse(xml, namespace_exist: namespace_exist)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def serialize(document, **opts)
|
|
35
|
+
document.to_xml(**opts)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parser_for(schema)
|
|
39
|
+
case schema.to_sym
|
|
40
|
+
when :schema3 then Chemicalml::Cml::Schema3
|
|
41
|
+
when :schema24 then Chemicalml::Cml::Schema24
|
|
42
|
+
else
|
|
43
|
+
raise ArgumentError, "unsupported schema: #{schema.inspect} " \
|
|
44
|
+
"(supported: :schema3, :schema24)"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
# Idempotent fetcher for xml-cml.org reference material.
|
|
8
|
+
# Re-running this task only downloads files that are missing locally.
|
|
9
|
+
module ReferenceFetch
|
|
10
|
+
BASE = "http://www.xml-cml.org".freeze
|
|
11
|
+
LICENSE = "Source: <url>. Distributed by xml-cml.org under CC-BY-3.0.".freeze
|
|
12
|
+
|
|
13
|
+
# Map of remote URL => local path under reference-docs/.
|
|
14
|
+
SOURCES = {
|
|
15
|
+
# Conventions
|
|
16
|
+
"#{BASE}/convention/" => "reference-docs/conventions/index.md",
|
|
17
|
+
"#{BASE}/convention/molecular" => "reference-docs/conventions/molecular.md",
|
|
18
|
+
"#{BASE}/convention/compchem" => "reference-docs/conventions/compchem.md",
|
|
19
|
+
"#{BASE}/convention/dictionary" => "reference-docs/conventions/dictionary.md",
|
|
20
|
+
"#{BASE}/convention/unit-dictionary" => "reference-docs/conventions/unit-dictionary.md",
|
|
21
|
+
"#{BASE}/convention/unitType-dictionary" => "reference-docs/conventions/unitType-dictionary.md",
|
|
22
|
+
# Dictionaries
|
|
23
|
+
"#{BASE}/dictionary/" => "reference-docs/dictionaries/index.md",
|
|
24
|
+
"#{BASE}/dictionary/compchem/" => "reference-docs/dictionaries/compchem.md",
|
|
25
|
+
"#{BASE}/dictionary/cml/" => "reference-docs/dictionaries/cml.md",
|
|
26
|
+
"#{BASE}/dictionary/cml/name/" => "reference-docs/dictionaries/cml-name.md",
|
|
27
|
+
"#{BASE}/dictionary/cml/formula/" => "reference-docs/dictionaries/cml-formula.md",
|
|
28
|
+
"#{BASE}/dictionary/cif/" => "reference-docs/dictionaries/cif.md",
|
|
29
|
+
# Unit dictionaries
|
|
30
|
+
"#{BASE}/unit/si/" => "reference-docs/dictionaries/unit-si.md",
|
|
31
|
+
"#{BASE}/unit/nonSi/" => "reference-docs/dictionaries/unit-nonSi.md",
|
|
32
|
+
"#{BASE}/unit/unitType/" => "reference-docs/dictionaries/unit-type.md",
|
|
33
|
+
# Schema / spec
|
|
34
|
+
"https://www.xml-cml.org/schema/cmllite.html" => "reference-docs/cmllite.html",
|
|
35
|
+
# Examples
|
|
36
|
+
"#{BASE}/examples/schema3/molecular/" => "reference-docs/examples/schema3-molecular-index.md",
|
|
37
|
+
"#{BASE}/examples/schema3/compchem/" => "reference-docs/examples/schema3-compchem-index.md",
|
|
38
|
+
"#{BASE}/examples/schema24/" => "reference-docs/examples/schema24-index.md"
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
def self.fetch_all
|
|
42
|
+
SOURCES.each do |url, path|
|
|
43
|
+
fetch_one(url, path)
|
|
44
|
+
sleep 0.5
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.fetch_one(url, path)
|
|
49
|
+
full = File.expand_path(path, project_root)
|
|
50
|
+
if File.exist?(full)
|
|
51
|
+
puts "skip #{path} (exists)"
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
FileUtils.mkdir_p(File.dirname(full))
|
|
56
|
+
body = http_get(url)
|
|
57
|
+
if body.nil?
|
|
58
|
+
File.write("#{full}.failed", "GET #{url} -> failed\n")
|
|
59
|
+
warn "FAILED #{url}"
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if path.end_with?(".html")
|
|
64
|
+
File.write(full, body)
|
|
65
|
+
else
|
|
66
|
+
File.write(full, header(url) + body)
|
|
67
|
+
end
|
|
68
|
+
puts "saved #{path}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.header(url)
|
|
72
|
+
"<!-- Source: #{url} -->\n<!-- License: CC-BY-3.0 (http://creativecommons.org/licenses/by/3.0/) -->\n\n"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.http_get(url, limit = 5)
|
|
76
|
+
raise "too many redirects" if limit.zero?
|
|
77
|
+
|
|
78
|
+
uri = URI(url)
|
|
79
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
80
|
+
http.use_ssl = true if uri.is_a?(URI::HTTPS)
|
|
81
|
+
http.read_timeout = 30
|
|
82
|
+
req = Net::HTTP::Get.new(uri, "User-Agent" => "chemicalml-gem-reference-fetch")
|
|
83
|
+
res = http.request(req)
|
|
84
|
+
|
|
85
|
+
case res
|
|
86
|
+
when Net::HTTPSuccess then res.body
|
|
87
|
+
when Net::HTTPRedirection then http_get(res["location"], limit - 1)
|
|
88
|
+
else nil
|
|
89
|
+
end
|
|
90
|
+
rescue StandardError => e
|
|
91
|
+
warn " error fetching #{url}: #{e.message}"
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.project_root
|
|
96
|
+
File.expand_path("..", __dir__)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
namespace :reference do
|
|
101
|
+
desc "Download all xml-cml.org reference material into reference-docs/. Idempotent."
|
|
102
|
+
task :fetch do
|
|
103
|
+
ReferenceFetch.fetch_all
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "cgi"
|
|
7
|
+
|
|
8
|
+
# Idempotent scraper for xml-cml.org example CML files. Walks the
|
|
9
|
+
# HTML index pages, extracts the embedded CML, and writes one `.cml`
|
|
10
|
+
# file per example under `spec/fixtures/`.
|
|
11
|
+
module FixturesScrape
|
|
12
|
+
BASE = "http://www.xml-cml.org".freeze
|
|
13
|
+
|
|
14
|
+
# Map of remote index URL => local target directory.
|
|
15
|
+
INDEXES = {
|
|
16
|
+
"#{BASE}/examples/schema3/molecular/" => "spec/fixtures/schema3/molecular",
|
|
17
|
+
"#{BASE}/examples/schema3/compchem/" => "spec/fixtures/schema3/compchem",
|
|
18
|
+
"#{BASE}/examples/schema24/" => "spec/fixtures/schema24"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def self.scrape_all
|
|
22
|
+
INDEXES.each { |url, dir| scrape_index(url, dir) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.scrape_index(index_url, target_dir)
|
|
26
|
+
full_target = File.expand_path(target_dir, project_root)
|
|
27
|
+
FileUtils.mkdir_p(full_target)
|
|
28
|
+
|
|
29
|
+
body = http_get(index_url)
|
|
30
|
+
unless body
|
|
31
|
+
warn "FAILED to fetch #{index_url}"
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
example_links = extract_example_links(body, index_url)
|
|
36
|
+
puts "#{index_url} -> #{example_links.length} examples"
|
|
37
|
+
example_links.each do |href, title|
|
|
38
|
+
save_example(href, title, full_target)
|
|
39
|
+
sleep 0.5
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.extract_example_links(html, base_url)
|
|
44
|
+
hrefs = html.scan(/<a[^>]+href=["']([^"']+\.html?)["'][^>]*>([^<]+)<\/a>/i)
|
|
45
|
+
hrefs.map do |raw_href, raw_title|
|
|
46
|
+
href = URI.join(base_url, raw_href).to_s
|
|
47
|
+
title = CGI.unescapeHTML(raw_title.strip)
|
|
48
|
+
next nil if title.empty?
|
|
49
|
+
next nil unless title.match?(/\A[a-z]/i)
|
|
50
|
+
|
|
51
|
+
[href, title]
|
|
52
|
+
end.compact
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.save_example(url, title, target_dir)
|
|
56
|
+
slug = slugify(title)
|
|
57
|
+
return if slug.empty?
|
|
58
|
+
|
|
59
|
+
path = File.join(target_dir, "#{slug}.cml")
|
|
60
|
+
if File.exist?(path)
|
|
61
|
+
puts " skip #{slug}.cml (exists)"
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
body = http_get(url)
|
|
66
|
+
unless body
|
|
67
|
+
log_failure(url, target_dir)
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
cml = extract_cml(body)
|
|
72
|
+
unless cml
|
|
73
|
+
log_failure(url, target_dir, reason: "no <cml> block found")
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
File.write(path, cml.strip + "\n")
|
|
78
|
+
puts " saved #{slug}.cml"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.extract_cml(html)
|
|
82
|
+
match = html.match(/<cml[^>]*>.*<\/cml>/m)
|
|
83
|
+
return match[0] if match
|
|
84
|
+
|
|
85
|
+
match = html.match(/<(?:cml|module|dictionary|molecule|reaction|unitList|unitTypeList)[^>]*>.*<\/(?:cml|module|dictionary|molecule|reaction|unitList|unitTypeList)>/m)
|
|
86
|
+
match && match[0]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.slugify(title)
|
|
90
|
+
title.downcase
|
|
91
|
+
.gsub(/[^a-z0-9]+/, "_")
|
|
92
|
+
.gsub(/^_+|_+$/, "")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.http_get(url, limit = 5)
|
|
96
|
+
raise "too many redirects" if limit.zero?
|
|
97
|
+
|
|
98
|
+
uri = URI(url)
|
|
99
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
100
|
+
http.use_ssl = true if uri.is_a?(URI::HTTPS)
|
|
101
|
+
http.read_timeout = 30
|
|
102
|
+
req = Net::HTTP::Get.new(uri, "User-Agent" => "chemicalml-gem-fixtures-scrape")
|
|
103
|
+
res = http.request(req)
|
|
104
|
+
|
|
105
|
+
case res
|
|
106
|
+
when Net::HTTPSuccess then res.body
|
|
107
|
+
when Net::HTTPRedirection then http_get(res["location"], limit - 1)
|
|
108
|
+
else nil
|
|
109
|
+
end
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
warn " error fetching #{url}: #{e.message}"
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.log_failure(url, target_dir, reason: "fetch failed")
|
|
116
|
+
File.open(File.join(target_dir, ".scrape-log"), "a") do |f|
|
|
117
|
+
f.puts "#{Time.now.utc.iso8601}\t#{url}\t#{reason}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.project_root
|
|
122
|
+
File.expand_path("../..", __dir__)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
require "time"
|
|
127
|
+
|
|
128
|
+
namespace :fixtures do
|
|
129
|
+
desc "Scrape every xml-cml.org example CML into spec/fixtures/. Idempotent."
|
|
130
|
+
task :scrape do
|
|
131
|
+
FixturesScrape.scrape_all
|
|
132
|
+
end
|
|
133
|
+
end
|