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,88 @@
|
|
|
1
|
+
# 02 — Real Schema3 / Schema24 class hierarchies
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 01
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Today `Chemicalml::Cml::Schema3::Document` is just an alias for
|
|
9
|
+
`Chemicalml::Cml::Document` — they are literally the same class. This
|
|
10
|
+
means schema-version-specific behavior is impossible to add without
|
|
11
|
+
modifying the shared class (OCP violation).
|
|
12
|
+
|
|
13
|
+
Promote both namespaces to genuine class hierarchies. Each
|
|
14
|
+
schema-versioned class is a `Lutaml::Model::Serializable` subclass that
|
|
15
|
+
includes the corresponding `Cml::Base::*` mixin (from task 01) and may
|
|
16
|
+
add version-specific declarations.
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
The CML Schema 2.4 and Schema 3 XSDs differ in content models —
|
|
21
|
+
Schema 3 loosened many of Schema 2.4's restrictions and added the
|
|
22
|
+
generic `<module>` element. Today's aliasing hides this. Real class
|
|
23
|
+
hierarchies let each version evolve independently.
|
|
24
|
+
|
|
25
|
+
## Deliverables
|
|
26
|
+
|
|
27
|
+
- [ ] `lib/chemicalml/cml/schema3/` directory with one file per
|
|
28
|
+
element, each `class Foo < Lutaml::Model::Serializable;
|
|
29
|
+
include Base::Foo; end`
|
|
30
|
+
- [ ] `lib/chemicalml/cml/schema24/` directory mirroring the same set
|
|
31
|
+
- [ ] `lib/chemicalml/cml/schema3.rb` — namespace file with autoloads
|
|
32
|
+
(replaces the current alias module)
|
|
33
|
+
- [ ] `lib/chemicalml/cml/schema24.rb` — same
|
|
34
|
+
- [ ] Backward compatibility: `Chemicalml::Cml::Atom`,
|
|
35
|
+
`Cml::Molecule`, etc. at the top level become aliases for
|
|
36
|
+
`Cml::Schema3::*` (so existing user code and specs still load).
|
|
37
|
+
Document this in `CLAUDE.md` and `README.adoc`.
|
|
38
|
+
|
|
39
|
+
## Schema-specific deviations to honor
|
|
40
|
+
|
|
41
|
+
- Schema 2.4 has stricter content models; some elements (e.g.
|
|
42
|
+
`<module>`) do not exist. `Schema24::Module` therefore does not
|
|
43
|
+
exist.
|
|
44
|
+
- Schema 3 added `<module>` and loosened `<cml>` content; that
|
|
45
|
+
behavior is the default in Schema3 wire classes.
|
|
46
|
+
- Future Schema 2.4-specific deviations are added in
|
|
47
|
+
`Base::Schema24Only::*` modules included only by Schema24 classes
|
|
48
|
+
(OCP-friendly extension point).
|
|
49
|
+
|
|
50
|
+
## Pattern
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# lib/chemicalml/cml/schema3/molecule.rb
|
|
54
|
+
module Chemicalml
|
|
55
|
+
module Cml
|
|
56
|
+
module Schema3
|
|
57
|
+
class Molecule < Lutaml::Model::Serializable
|
|
58
|
+
include Base::Molecule
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
# lib/chemicalml/cml/schema24/molecule.rb (deviates: no <module> child)
|
|
67
|
+
module Chemicalml
|
|
68
|
+
module Cml
|
|
69
|
+
module Schema24
|
|
70
|
+
class Molecule < Lutaml::Model::Serializable
|
|
71
|
+
include Base::Molecule
|
|
72
|
+
# Schema 2.4 doesn't allow child <module> elements;
|
|
73
|
+
# the Base::Molecule mixin doesn't declare one, so this class
|
|
74
|
+
# is already correct. If we needed to *remove* an attribute
|
|
75
|
+
# the Base declares, we'd use `undef_attribute` here.
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Acceptance
|
|
83
|
+
|
|
84
|
+
- `Chemicalml::Cml::Schema3::Atom` and `Chemicalml::Cml::Schema24::Atom`
|
|
85
|
+
are different classes (different `object_id`).
|
|
86
|
+
- `Chemicalml::Cml::Atom` is an alias for `Chemicalml::Cml::Schema3::Atom`.
|
|
87
|
+
- All existing specs continue to pass.
|
|
88
|
+
- Round-trip through either schema works on the existing fixtures.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 03 — Configuration + lutaml_default_register
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 02
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Wire `lutaml-model`'s `GlobalContext` type-resolution machinery into
|
|
9
|
+
the schema-versioned classes. Each schema gets its own context id
|
|
10
|
+
(`:chemicalml_schema3`, `:chemicalml_schema24`); each wire class
|
|
11
|
+
declares which context it belongs to via `lutaml_default_register`.
|
|
12
|
+
|
|
13
|
+
This is the same pattern `mml` uses to let `from_xml` resolve child
|
|
14
|
+
element types based on the active context.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
Without context-aware type resolution, parsing `<atom>` inside a Schema3
|
|
19
|
+
document and inside a Schema24 document both yield the same Ruby class.
|
|
20
|
+
Once task 02 introduces real hierarchies, the framework needs to know
|
|
21
|
+
which `Atom` class to instantiate based on which schema's `Document`
|
|
22
|
+
is being parsed.
|
|
23
|
+
|
|
24
|
+
## Deliverables
|
|
25
|
+
|
|
26
|
+
- [ ] `lib/chemicalml/cml/schema3/configuration.rb` —
|
|
27
|
+
`module Schema3::Configuration; extend Chemicalml::ContextConfiguration;
|
|
28
|
+
CONTEXT_ID = :chemicalml_schema3; end`
|
|
29
|
+
- [ ] `lib/chemicalml/cml/schema24/configuration.rb` — same shape
|
|
30
|
+
with `CONTEXT_ID = :chemicalml_schema24`
|
|
31
|
+
- [ ] `lib/chemicalml/context_configuration.rb` — shared mixin
|
|
32
|
+
providing `register_model`, `populate_context!`, `context`,
|
|
33
|
+
`default_context_id` (modeled on `mml`'s
|
|
34
|
+
`lib/mml/context_configuration.rb`)
|
|
35
|
+
- [ ] Each `Schema3::*` and `Schema24::*` class defines
|
|
36
|
+
`def self.lutaml_default_register; :chemicalml_schema3; end`
|
|
37
|
+
(or `:chemicalml_schema24`)
|
|
38
|
+
- [ ] `lib/chemicalml/versioned_parser.rb` — `extend`-able mixin with
|
|
39
|
+
`parse(xml, namespace_exist:, context:)` that calls
|
|
40
|
+
`ensure_registered!` then `GlobalContext.resolve_type(:cml, ctx)`
|
|
41
|
+
then `root_class.from_xml(xml, register: ctx)`
|
|
42
|
+
- [ ] Schema3 and Schema24 modules `extend VersionedParser`
|
|
43
|
+
|
|
44
|
+
## Acceptance
|
|
45
|
+
|
|
46
|
+
- `Chemicalml::Cml::Schema3.parse(xml)` returns a Schema3 document.
|
|
47
|
+
- `Chemicalml::Cml::Schema24.parse(xml)` returns a Schema24 document.
|
|
48
|
+
- `Chemicalml.parse(xml, schema: :schema24)` dispatches correctly.
|
|
49
|
+
- `Lutaml::Model::GlobalContext.context(:chemicalml_schema3)` is
|
|
50
|
+
populated after first parse.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 04 — Top-level parse / serialize entry points
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 02, 03
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Provide a single, obvious entry point for users: `Chemicalml.parse(xml,
|
|
9
|
+
schema: :schema3)` returns the right wire document. Symmetrically,
|
|
10
|
+
`Chemicalml.serialize(doc)` produces XML. This mirrors `Mml.parse(input,
|
|
11
|
+
version:)`.
|
|
12
|
+
|
|
13
|
+
## Why
|
|
14
|
+
|
|
15
|
+
Today users must know which class to call `.from_xml` on:
|
|
16
|
+
`Chemicalml::Cml::Schema3::Document.from_xml(xml)`. After task 02 this
|
|
17
|
+
gets worse — there are now two real hierarchies. A single dispatch
|
|
18
|
+
function is the obvious UX.
|
|
19
|
+
|
|
20
|
+
## Deliverables
|
|
21
|
+
|
|
22
|
+
- [ ] `Chemicalml.parse(xml, schema: :schema3, namespace_exist: true)`
|
|
23
|
+
→ dispatches via `Schema::Registry` to the right VersionedParser.
|
|
24
|
+
- [ ] `Chemicalml.serialize(document)` → just calls `document.to_xml`.
|
|
25
|
+
Exists for API symmetry.
|
|
26
|
+
- [ ] `Chemicalml.parser_for(schema)` → returns the Schema3 or Schema24
|
|
27
|
+
module (raises `ArgumentError` on unknown).
|
|
28
|
+
- [ ] Document in `README.adoc` and `CLAUDE.md`.
|
|
29
|
+
|
|
30
|
+
## Pattern
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# lib/chemicalml.rb (additions)
|
|
34
|
+
def parse(xml, schema: :schema3, **opts)
|
|
35
|
+
parser_for(schema).parse(xml, **opts)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def serialize(document, **opts)
|
|
39
|
+
document.to_xml(**opts)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parser_for(schema)
|
|
43
|
+
case schema
|
|
44
|
+
when :schema3 then Chemicalml::Cml::Schema3
|
|
45
|
+
when :schema24 then Chemicalml::Cml::Schema24
|
|
46
|
+
else
|
|
47
|
+
raise ArgumentError, "unsupported schema: #{schema.inspect} " \
|
|
48
|
+
"(supported: :schema3, :schema24)"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
module_function :parse, :serialize, :parser_for
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Acceptance
|
|
55
|
+
|
|
56
|
+
- `Chemicalml.parse("<cml>...</cml>")` works (defaults to Schema3).
|
|
57
|
+
- `Chemicalml.parse("<cml>...</cml>", schema: :schema24)` returns a
|
|
58
|
+
`Schema24::Document`.
|
|
59
|
+
- Unknown schema raises `ArgumentError`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 05 — Schema-aware Translator
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 02
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Update `Chemicalml::Cml::Translator` so it can serialize the canonical
|
|
9
|
+
model into either Schema3 or Schema24 wire format, and so it can parse
|
|
10
|
+
either schema's wire format back into the canonical model.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Today the translator only knows about `Cml::Document` (now an alias for
|
|
15
|
+
Schema3). Once Schema3 and Schema24 are real class hierarchies, the
|
|
16
|
+
translator needs to know which one to instantiate when going from
|
|
17
|
+
canonical → wire.
|
|
18
|
+
|
|
19
|
+
## Deliverables
|
|
20
|
+
|
|
21
|
+
- [ ] `Translator.from_canonical(model_doc, schema: :schema3)` →
|
|
22
|
+
returns a `Schema3::Document` or `Schema24::Document`
|
|
23
|
+
- [ ] `Translator.to_canonical(wire_doc)` → unchanged: detects the
|
|
24
|
+
wire class via `is_a?` and dispatches correctly
|
|
25
|
+
- [ ] Existing two-method public API (`to_canonical`,
|
|
26
|
+
`from_canonical`) preserved; new behavior is opt-in via the
|
|
27
|
+
`schema:` keyword (defaults to `:schema3`)
|
|
28
|
+
|
|
29
|
+
## Acceptance
|
|
30
|
+
|
|
31
|
+
- Round-trip canonical → Schema3 wire → canonical yields an equal doc.
|
|
32
|
+
- Round-trip canonical → Schema24 wire → canonical yields an equal doc.
|
|
33
|
+
- Specs cover both schemas.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 06 — Replace respond_to? with is_a?
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 02
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
The user's global rule forbids `respond_to?` for type checking. Today
|
|
9
|
+
the convention constraint walker uses `respond_to?` heavily to ask
|
|
10
|
+
heterogeneous wire nodes whether they expose a particular attribute.
|
|
11
|
+
Replace every occurrence with `is_a?` checks against the concrete wire
|
|
12
|
+
classes (or a marker module that those classes include).
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
`respond_to?` is duck-typing that hides type errors until runtime. The
|
|
17
|
+
right pattern is either:
|
|
18
|
+
|
|
19
|
+
- Use `is_a?(SpecificClass)` when the constraint genuinely only applies
|
|
20
|
+
to one class.
|
|
21
|
+
- Introduce a marker module (e.g. `Cml::HasAtomArray`) that the
|
|
22
|
+
relevant classes include, then check `node.is_a?(HasAtomArray)`. This
|
|
23
|
+
is OCP-friendly — new classes opt in by including the marker, not by
|
|
24
|
+
editing a switch.
|
|
25
|
+
|
|
26
|
+
## Deliverables
|
|
27
|
+
|
|
28
|
+
- [ ] Introduce `Chemicalml::Cml::Visitable` mixin included by every
|
|
29
|
+
wire class. Provides a uniform `accept_constraint(visitor)`
|
|
30
|
+
interface that delegates to `visitor.visit_<class_short_name>`.
|
|
31
|
+
- [ ] Add marker modules where duck-typing is currently used:
|
|
32
|
+
`Cml::HasAtomArray`, `Cml::HasBondArray`, `Cml::HasEntries`,
|
|
33
|
+
`Cml::HasUnits`, etc. — included by the relevant wire classes.
|
|
34
|
+
- [ ] Rewrite `Convention::Constraint#wire_children` to walk via
|
|
35
|
+
`is_a?` checks against marker modules, not `respond_to?`.
|
|
36
|
+
- [ ] Audit `grep -rn 'respond_to?' lib/` — should return zero hits
|
|
37
|
+
outside spec_helper boilerplate.
|
|
38
|
+
|
|
39
|
+
## Acceptance
|
|
40
|
+
|
|
41
|
+
- `grep -rn 'respond_to?' lib/` returns nothing.
|
|
42
|
+
- All convention specs still pass.
|
|
43
|
+
- Constraint walker behavior unchanged (still detects the same
|
|
44
|
+
violations on the same fixtures).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 07 — Canon-based semantic XML comparison
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** —
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Replace brittle string-equality assertions in round-trip specs with
|
|
9
|
+
semantic XML comparison via the
|
|
10
|
+
[`canon`](https://github.com/plurimath/canon) gem. This is what `mml`
|
|
11
|
+
uses for its `be_xml_equivalent_to` matcher.
|
|
12
|
+
|
|
13
|
+
## Why
|
|
14
|
+
|
|
15
|
+
Plain string equality breaks on:
|
|
16
|
+
|
|
17
|
+
- attribute order (`<a x="1" y="2"/>` vs `<a y="2" x="1"/>`)
|
|
18
|
+
- whitespace differences inside tags
|
|
19
|
+
- namespace prefix aliases
|
|
20
|
+
- self-closing vs empty-tag forms
|
|
21
|
+
|
|
22
|
+
Semantic comparison treats all of these as equivalent, which is what
|
|
23
|
+
round-trip tests actually want to verify.
|
|
24
|
+
|
|
25
|
+
## Deliverables
|
|
26
|
+
|
|
27
|
+
- [ ] Add `gem "canon"` to the development group in `Gemfile`
|
|
28
|
+
- [ ] Configure `Canon::Config` in `spec/spec_helper.rb`:
|
|
29
|
+
```ruby
|
|
30
|
+
Canon::Config.configure do |config|
|
|
31
|
+
config.xml.match.profile = :spec_friendly
|
|
32
|
+
config.xml.diff.algorithm = :semantic
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
- [ ] Existing round-trip specs use `expect(out).to be_xml_equivalent_to(in)`
|
|
36
|
+
instead of `expect(out).to eq(in)` or attribute-by-attribute
|
|
37
|
+
equality.
|
|
38
|
+
|
|
39
|
+
## Acceptance
|
|
40
|
+
|
|
41
|
+
- `bundle exec rspec` is green with the new matcher.
|
|
42
|
+
- Round-trip specs catch real regressions (e.g. dropped attributes)
|
|
43
|
+
rather than formatting noise.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 08 — Expand fixtures corpus
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 02
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Grow the fixture corpus so it covers the breadth of CML: stereo,
|
|
9
|
+
parity, properties, parameters, modules, dictionaries, units,
|
|
10
|
+
crystals, lattices. Each fixture exercises one feature so a regression
|
|
11
|
+
is easy to localize.
|
|
12
|
+
|
|
13
|
+
## Deliverables
|
|
14
|
+
|
|
15
|
+
Add the following seed fixtures under `spec/fixtures/`:
|
|
16
|
+
|
|
17
|
+
- [ ] `schema3/molecular/ethene_with_bond_stereo.cml` — cis/trans
|
|
18
|
+
- [ ] `schema3/molecular/chiral_center.cml` — atomParity
|
|
19
|
+
- [ ] `schema3/molecular/with_property.cml` — property + scalar
|
|
20
|
+
- [ ] `schema3/molecular/nested_molecules.cml` — parent/child
|
|
21
|
+
- [ ] `schema3/molecular/with_formula_inline.cml` — LaTeX-style inline
|
|
22
|
+
- [ ] `schema3/compchem/full_nwchem.cml` — full jobList with
|
|
23
|
+
initialization / calculation / finalization
|
|
24
|
+
- [ ] `schema3/compchem/with_basis_set.cml` — basisSet list
|
|
25
|
+
- [ ] `schema3/dictionary/example.cml` — dictionary with entries
|
|
26
|
+
- [ ] `schema3/unit/si_units.cml` — unitList with SI units
|
|
27
|
+
- [ ] `schema24/crystal.cml` — crystal + lattice
|
|
28
|
+
- [ ] `schema24/spectrum.cml` — peakList
|
|
29
|
+
- [ ] `spec/fixtures/README.md` — documents provenance (synthetic vs
|
|
30
|
+
scraped) and licensing
|
|
31
|
+
|
|
32
|
+
## Acceptance
|
|
33
|
+
|
|
34
|
+
- All new fixtures round-trip through both Schema3 and Schema24 wire
|
|
35
|
+
classes.
|
|
36
|
+
- `bundle exec rspec spec/chemicalml/fixtures_round_trip_spec.rb` is
|
|
37
|
+
green.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 09 — Final spec + lint pass
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** all
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Run the full suite, fix any regressions, run rubocop, document any
|
|
9
|
+
remaining gaps in this file.
|
|
10
|
+
|
|
11
|
+
## Deliverables
|
|
12
|
+
|
|
13
|
+
- [ ] `bundle exec rspec` green
|
|
14
|
+
- [ ] `bundle exec rubocop` clean (or only pre-existing offenses,
|
|
15
|
+
documented)
|
|
16
|
+
- [ ] `grep -rn 'require_relative' lib/` returns nothing (except the
|
|
17
|
+
gemspec line which is legitimate)
|
|
18
|
+
- [ ] `grep -rn 'respond_to?' lib/` returns nothing
|
|
19
|
+
- [ ] `grep -rn 'instance_variable_set\|instance_variable_get' lib/`
|
|
20
|
+
returns nothing
|
|
21
|
+
- [ ] `grep -rn '\.send(' lib/` reviewed — no private-method bypasses
|
|
22
|
+
- [ ] This file updated with final status
|
|
23
|
+
|
|
24
|
+
## Acceptance
|
|
25
|
+
|
|
26
|
+
- All four grep checks return clean.
|
|
27
|
+
- Full spec suite green.
|
|
28
|
+
- The TODO.align index (`README.md`) shows every workstream as
|
|
29
|
+
`complete`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 10 — Role marker modules
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** —
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Every constraint currently does:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
def molecule?(node)
|
|
12
|
+
node.is_a?(Chemicalml::Cml::Schema3::Molecule) ||
|
|
13
|
+
node.is_a?(Chemicalml::Cml::Schema24::Molecule)
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This is an OCP violation: adding `Schema5` later means editing every
|
|
18
|
+
constraint. Replace with a single marker module per role:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
def molecule?(node)
|
|
22
|
+
node.is_a?(Chemicalml::Cml::Role::Molecule)
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Both `Schema3::Molecule` and `Schema24::Molecule` (and any future
|
|
27
|
+
version) include `Cml::Role::Molecule`. Constraints never change when
|
|
28
|
+
new schema versions are added.
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
Type checks against concrete classes couple constraints to the set of
|
|
33
|
+
supported schema versions. Marker modules decouple them: the constraint
|
|
34
|
+
asks "is this a molecule?" not "is this one of the known molecule
|
|
35
|
+
classes?".
|
|
36
|
+
|
|
37
|
+
## Deliverables
|
|
38
|
+
|
|
39
|
+
- [ ] `lib/chemicalml/cml/role.rb` — namespace file with autoloads
|
|
40
|
+
- [ ] One module per CML element role under `lib/chemicalml/cml/role/`:
|
|
41
|
+
`molecule.rb`, `atom.rb`, `atom_array.rb`, `bond.rb`,
|
|
42
|
+
`bond_array.rb`, `module.rb`, `dictionary.rb`,
|
|
43
|
+
`dictionary_entry.rb`, `unit.rb`, `unit_type.rb`, etc.
|
|
44
|
+
- [ ] Each `Base::*` module's `included` hook adds `include
|
|
45
|
+
Chemicalml::Cml::Role::Foo` to the including class.
|
|
46
|
+
- [ ] Every constraint file uses `is_a?(Cml::Role::Foo)` instead of
|
|
47
|
+
`is_a?(Schema3::Foo) || is_a?(Schema24::Foo)`.
|
|
48
|
+
|
|
49
|
+
## Acceptance
|
|
50
|
+
|
|
51
|
+
- `grep -rn 'Schema3.*||.*Schema24' lib/` returns nothing.
|
|
52
|
+
- `grep -rn 'is_a?(Chemicalml::Cml::Schema' lib/chemicalml/convention/`
|
|
53
|
+
returns nothing.
|
|
54
|
+
- All convention specs pass.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# 11 — DRY Configuration registration
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** —
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
`Schema3::Configuration.register_models!` and
|
|
9
|
+
`Schema24::Configuration.register_models!` are 99% identical — only
|
|
10
|
+
the parent module differs. Both list every CML element class with the
|
|
11
|
+
same id. This is a DRY / SSOT violation.
|
|
12
|
+
|
|
13
|
+
Extract the registration loop into `ContextConfiguration` so each
|
|
14
|
+
schema's Configuration is just a few lines:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
module Schema3
|
|
18
|
+
module Configuration
|
|
19
|
+
extend Chemicalml::ContextConfiguration
|
|
20
|
+
|
|
21
|
+
CONTEXT_ID = :chemicalml_schema3
|
|
22
|
+
REGISTERED_ELEMENTS = %i[
|
|
23
|
+
atom atomArray atomParity bond bondArray bondStereo
|
|
24
|
+
document formula identifier label list matrix metadata
|
|
25
|
+
...
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
register_element_classes!
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The shared `register_element_classes!` walks the list, looks up each
|
|
34
|
+
class on the parent module, and calls `register_model`.
|
|
35
|
+
|
|
36
|
+
## Why
|
|
37
|
+
|
|
38
|
+
Today, adding a new CML element means:
|
|
39
|
+
|
|
40
|
+
1. Creating `Base::Foo`
|
|
41
|
+
2. Creating `Schema3::Foo` and `Schema24::Foo`
|
|
42
|
+
3. **Adding `register_model Foo, id: :foo` to BOTH Configuration files**
|
|
43
|
+
4. Adding translator rules
|
|
44
|
+
5. Adding specs
|
|
45
|
+
|
|
46
|
+
Step 3 is duplicated work. After this fix, the element name goes in one
|
|
47
|
+
list (`REGISTERED_ELEMENTS`) and both schemas pick it up automatically.
|
|
48
|
+
|
|
49
|
+
## Deliverables
|
|
50
|
+
|
|
51
|
+
- [ ] `Chemicalml::ContextConfiguration#register_element_classes!`
|
|
52
|
+
iterates `REGISTERED_ELEMENTS`, looks up each class on
|
|
53
|
+
`version_parent_module`, and registers it.
|
|
54
|
+
- [ ] `Schema3::Configuration` and `Schema24::Configuration` reduced
|
|
55
|
+
to ~10 lines each: `extend`, `CONTEXT_ID =`, `REGISTERED_ELEMENTS =`,
|
|
56
|
+
`register_element_classes!`.
|
|
57
|
+
|
|
58
|
+
## Acceptance
|
|
59
|
+
|
|
60
|
+
- Both Configuration files under 20 lines.
|
|
61
|
+
- Adding a new element requires touching exactly ONE place
|
|
62
|
+
(the `REGISTERED_ELEMENTS` array).
|
|
63
|
+
- All specs still pass.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 12 — Wire class definition macro
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 11
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Each `lib/chemicalml/cml/schema3/foo.rb` and `schema24/foo.rb` file is
|
|
9
|
+
12 lines of boilerplate differing only by the class name and context
|
|
10
|
+
id. That's 36 × 2 = 72 files of nearly-identical code.
|
|
11
|
+
|
|
12
|
+
Reduce to a single declarative call per schema:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# lib/chemicalml/cml/schema3.rb
|
|
16
|
+
Chemicalml::Cml.define_wire_classes(
|
|
17
|
+
in: Schema3,
|
|
18
|
+
base: Base,
|
|
19
|
+
context_id: :chemicalml_schema3,
|
|
20
|
+
elements: REGISTERED_ELEMENTS
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The macro creates one class per element name, includes the
|
|
25
|
+
corresponding Base and Visitable mixins, and defines
|
|
26
|
+
`lutaml_default_register`.
|
|
27
|
+
|
|
28
|
+
## Why
|
|
29
|
+
|
|
30
|
+
- DRY: one place to change the wire-class shape (e.g. adding another
|
|
31
|
+
mixin, changing the parent class).
|
|
32
|
+
- SSOT: the per-element declaration lives in `REGISTERED_ELEMENTS` (from
|
|
33
|
+
task 11), not duplicated across 72 files.
|
|
34
|
+
- New schema version: just add `define_wire_classes(in: Schema5, ...)`
|
|
35
|
+
with no per-class boilerplate.
|
|
36
|
+
|
|
37
|
+
## Deliverables
|
|
38
|
+
|
|
39
|
+
- [ ] `Chemicalml::Cml.define_wire_classes` class method on the `Cml`
|
|
40
|
+
module. Takes `in:`, `base:`, `context_id:`, `elements:` keyword
|
|
41
|
+
args.
|
|
42
|
+
- [ ] Delete the 72 per-class files.
|
|
43
|
+
- [ ] Update `schema3.rb` / `schema24.rb` to call the macro.
|
|
44
|
+
|
|
45
|
+
## Acceptance
|
|
46
|
+
|
|
47
|
+
- The 72 thin class files are gone.
|
|
48
|
+
- `lib/chemicalml/cml/schema3/*.rb` and `schema24/*.rb` directories
|
|
49
|
+
contain only `configuration.rb` (and any future schema-specific
|
|
50
|
+
overrides).
|
|
51
|
+
- All specs still pass.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 13 — Wire XML namespace setup
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** —
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Today, round-tripping a namespaced CML document produces inner
|
|
9
|
+
elements with spurious `xmlns=""` attributes:
|
|
10
|
+
|
|
11
|
+
```xml
|
|
12
|
+
<molecule xmlns="http://www.xml-cml.org/schema" id="m1">
|
|
13
|
+
<atomArray xmlns=""> <!-- ← bug -->
|
|
14
|
+
<atom id="a1" elementType="C"/>
|
|
15
|
+
</atomArray>
|
|
16
|
+
</molecule>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This is because the wire classes don't declare a namespace on their
|
|
20
|
+
`xml do ... end` block, so the framework assumes the empty namespace.
|
|
21
|
+
|
|
22
|
+
Add `namespace Chemicalml::Cml::Namespace` to each Base module's xml
|
|
23
|
+
block so output preserves the CML namespace.
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
- Real bug — the round-trip isn't faithful.
|
|
28
|
+
- `canon` semantic comparison will catch this once we switch the
|
|
29
|
+
fixture spec to it (task 15).
|
|
30
|
+
|
|
31
|
+
## Deliverables
|
|
32
|
+
|
|
33
|
+
- [ ] Verify `Chemicalml::Cml::Namespace` is a proper lutaml namespace
|
|
34
|
+
declaration.
|
|
35
|
+
- [ ] Add `namespace Chemicalml::Cml::Namespace` to the `xml do` block
|
|
36
|
+
in every `lib/chemicalml/cml/base/*.rb` mixin.
|
|
37
|
+
- [ ] Round-trip a fixture with namespace declaration and confirm no
|
|
38
|
+
`xmlns=""` appears on inner elements.
|
|
39
|
+
|
|
40
|
+
## Acceptance
|
|
41
|
+
|
|
42
|
+
- A round-tripped `<cml xmlns="..."><molecule>...</molecule></cml>` is
|
|
43
|
+
byte-equivalent in namespace structure to the input.
|
|
44
|
+
- Canon matcher specs pass on full-document round-trip.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 14 — Schema 2.4 correctness
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 11
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Schema 2.4 does NOT have the generic `<module>` element — it was
|
|
9
|
+
introduced in Schema 3. Currently `Chemicalml::Cml::Schema24::Module`
|
|
10
|
+
exists anyway, which is incorrect.
|
|
11
|
+
|
|
12
|
+
Drop `Schema24::Module` and document the divergence in the schema24.rb
|
|
13
|
+
file's header comment.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
- SSOT — the set of valid Schema 2.4 elements is defined by the XSD,
|
|
18
|
+
not by Ruby convenience. Including extra elements makes the wire
|
|
19
|
+
classes lie about what schema they implement.
|
|
20
|
+
- Compchem-convention documents (which use `<module>` heavily) MUST be
|
|
21
|
+
parsed as Schema 3, not Schema 2.4. This makes that explicit.
|
|
22
|
+
|
|
23
|
+
## Deliverables
|
|
24
|
+
|
|
25
|
+
- [ ] Remove `lib/chemicalml/cml/schema24/cml_module.rb`.
|
|
26
|
+
- [ ] Remove `Schema24::Module` autoload from `schema24.rb`.
|
|
27
|
+
- [ ] Ensure `Schema24::Configuration.register_models!` does NOT
|
|
28
|
+
register `:module`.
|
|
29
|
+
- [ ] Document in `schema24.rb` header that Schema 2.4 lacks `<module>`.
|
|
30
|
+
- [ ] Add a spec verifying `Chemicalml::Cml::Schema24` does not have
|
|
31
|
+
`Module` defined.
|
|
32
|
+
|
|
33
|
+
## Acceptance
|
|
34
|
+
|
|
35
|
+
- `defined?(Chemicalml::Cml::Schema24::Module)` returns `nil`.
|
|
36
|
+
- All other Schema24 classes load and round-trip normally.
|
|
37
|
+
- Specs pass.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 15 — Canon-based fixture round-trip
|
|
2
|
+
|
|
3
|
+
**Status:** complete
|
|
4
|
+
**Depends on:** 13
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
The current `spec/chemicalml/fixtures_round_trip_spec.rb` only checks
|
|
9
|
+
that `.molecules.length` is preserved across a round-trip. It misses
|
|
10
|
+
attribute-level regressions (dropped attributes, wrong order, changed
|
|
11
|
+
values).
|
|
12
|
+
|
|
13
|
+
Switch to `be_xml_equivalent_to` (from the `canon` gem) for semantic
|
|
14
|
+
XML comparison. Depends on task 13 (namespace fix) so the comparison
|
|
15
|
+
isn't tripped up by spurious `xmlns=""` attributes.
|
|
16
|
+
|
|
17
|
+
## Why
|
|
18
|
+
|
|
19
|
+
- Catches real regressions: a dropped `elementType` attribute or a
|
|
20
|
+
reordered atom would slip past today's check.
|
|
21
|
+
- Tolerates formatting differences (attribute order, whitespace) that
|
|
22
|
+
round-trip serialization legitimately introduces.
|
|
23
|
+
- Matches the spec idioms used in the sibling `mml` gem.
|
|
24
|
+
|
|
25
|
+
## Deliverables
|
|
26
|
+
|
|
27
|
+
- [ ] `fixtures_round_trip_spec.rb` rewritten to use
|
|
28
|
+
`expect(round_tripped_xml).to be_xml_equivalent_to(input_xml)`.
|
|
29
|
+
- [ ] One example per fixture file.
|
|
30
|
+
- [ ] Document any fixtures that can't yet round-trip cleanly (known
|
|
31
|
+
limitations) with `skip "reason"` rather than removing the test.
|
|
32
|
+
|
|
33
|
+
## Acceptance
|
|
34
|
+
|
|
35
|
+
- `bundle exec rspec spec/chemicalml/fixtures_round_trip_spec.rb` is
|
|
36
|
+
green.
|
|
37
|
+
- Manually introducing a regression (e.g. dropping an attribute from
|
|
38
|
+
a Base module) causes a spec failure.
|