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.
Files changed (248) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rubocop.yml +20 -0
  4. data/CHANGELOG.md +21 -0
  5. data/CLAUDE.md +198 -0
  6. data/Gemfile +14 -0
  7. data/LICENSE +24 -0
  8. data/NOTICES.adoc +115 -0
  9. data/README.adoc +87 -0
  10. data/Rakefile +8 -0
  11. data/TODO.align/01-cml-base-mixins.md +122 -0
  12. data/TODO.align/02-real-schema-hierarchies.md +88 -0
  13. data/TODO.align/03-configuration.md +50 -0
  14. data/TODO.align/04-parse-entry-points.md +59 -0
  15. data/TODO.align/05-schema-aware-translator.md +33 -0
  16. data/TODO.align/06-no-respond-to.md +44 -0
  17. data/TODO.align/07-canon-xml-comparison.md +43 -0
  18. data/TODO.align/08-expand-fixtures.md +37 -0
  19. data/TODO.align/09-final-spec-lint.md +29 -0
  20. data/TODO.align/10-role-marker-modules.md +54 -0
  21. data/TODO.align/11-dry-configuration.md +63 -0
  22. data/TODO.align/12-wire-class-macro.md +51 -0
  23. data/TODO.align/13-wire-namespace.md +44 -0
  24. data/TODO.align/14-schema24-correctness.md +37 -0
  25. data/TODO.align/15-canon-fixture-roundtrip.md +38 -0
  26. data/TODO.align/16-schema24-convention-coverage.md +33 -0
  27. data/TODO.align/17-final-spec-lint.md +28 -0
  28. data/TODO.align/README.md +27 -0
  29. data/TODO.align/README.round2.md +24 -0
  30. data/TODO.cml-full/01-reference-docs.md +43 -0
  31. data/TODO.cml-full/02-schema-versioned-model.md +70 -0
  32. data/TODO.cml-full/03-fixtures-scrape.md +52 -0
  33. data/TODO.cml-full/04-convention-framework.md +69 -0
  34. data/TODO.cml-full/05-dictionary-layer.md +77 -0
  35. data/TODO.cml-full/06-extend-canonical-translator.md +60 -0
  36. data/TODO.cml-full/07-attribution-notices.md +42 -0
  37. data/TODO.cml-full/08-autoload-only.md +48 -0
  38. data/TODO.cml-full/09-spec-coverage.md +38 -0
  39. data/TODO.cml-full/README.md +23 -0
  40. data/chemicalml.gemspec +35 -0
  41. data/data/dictionaries/_index.yaml +14 -0
  42. data/data/dictionaries/compchem.yaml +481 -0
  43. data/data/dictionaries/unit_non_si.yaml +53 -0
  44. data/data/dictionaries/unit_si.yaml +87 -0
  45. data/data/dictionaries/unit_type.yaml +113 -0
  46. data/lib/chemicalml/cml/array.rb +10 -0
  47. data/lib/chemicalml/cml/atom.rb +10 -0
  48. data/lib/chemicalml/cml/atom_array.rb +10 -0
  49. data/lib/chemicalml/cml/atom_parity.rb +10 -0
  50. data/lib/chemicalml/cml/base/array.rb +36 -0
  51. data/lib/chemicalml/cml/base/atom.rb +38 -0
  52. data/lib/chemicalml/cml/base/atom_array.rb +22 -0
  53. data/lib/chemicalml/cml/base/atom_parity.rb +24 -0
  54. data/lib/chemicalml/cml/base/bond.rb +28 -0
  55. data/lib/chemicalml/cml/base/bond_array.rb +22 -0
  56. data/lib/chemicalml/cml/base/bond_stereo.rb +28 -0
  57. data/lib/chemicalml/cml/base/cml_module.rb +40 -0
  58. data/lib/chemicalml/cml/base/dictionary.rb +32 -0
  59. data/lib/chemicalml/cml/base/dictionary_entry.rb +34 -0
  60. data/lib/chemicalml/cml/base/document.rb +26 -0
  61. data/lib/chemicalml/cml/base/formula.rb +38 -0
  62. data/lib/chemicalml/cml/base/identifier.rb +26 -0
  63. data/lib/chemicalml/cml/base/label.rb +28 -0
  64. data/lib/chemicalml/cml/base/list.rb +36 -0
  65. data/lib/chemicalml/cml/base/matrix.rb +38 -0
  66. data/lib/chemicalml/cml/base/metadata.rb +30 -0
  67. data/lib/chemicalml/cml/base/metadata_list.rb +28 -0
  68. data/lib/chemicalml/cml/base/molecule.rb +40 -0
  69. data/lib/chemicalml/cml/base/name.rb +26 -0
  70. data/lib/chemicalml/cml/base/parameter.rb +34 -0
  71. data/lib/chemicalml/cml/base/parameter_list.rb +28 -0
  72. data/lib/chemicalml/cml/base/product.rb +22 -0
  73. data/lib/chemicalml/cml/base/product_list.rb +22 -0
  74. data/lib/chemicalml/cml/base/property.rb +34 -0
  75. data/lib/chemicalml/cml/base/property_list.rb +28 -0
  76. data/lib/chemicalml/cml/base/reactant.rb +22 -0
  77. data/lib/chemicalml/cml/base/reactant_list.rb +22 -0
  78. data/lib/chemicalml/cml/base/reaction.rb +32 -0
  79. data/lib/chemicalml/cml/base/reaction_list.rb +22 -0
  80. data/lib/chemicalml/cml/base/scalar.rb +32 -0
  81. data/lib/chemicalml/cml/base/substance.rb +30 -0
  82. data/lib/chemicalml/cml/base/unit.rb +38 -0
  83. data/lib/chemicalml/cml/base/unit_list.rb +30 -0
  84. data/lib/chemicalml/cml/base/unit_type.rb +28 -0
  85. data/lib/chemicalml/cml/base/unit_type_list.rb +30 -0
  86. data/lib/chemicalml/cml/base.rb +51 -0
  87. data/lib/chemicalml/cml/bond.rb +10 -0
  88. data/lib/chemicalml/cml/bond_array.rb +10 -0
  89. data/lib/chemicalml/cml/bond_stereo.rb +10 -0
  90. data/lib/chemicalml/cml/cml_module.rb +10 -0
  91. data/lib/chemicalml/cml/dictionary.rb +10 -0
  92. data/lib/chemicalml/cml/dictionary_entry.rb +10 -0
  93. data/lib/chemicalml/cml/document.rb +10 -0
  94. data/lib/chemicalml/cml/elements.rb +58 -0
  95. data/lib/chemicalml/cml/formula.rb +10 -0
  96. data/lib/chemicalml/cml/identifier.rb +10 -0
  97. data/lib/chemicalml/cml/label.rb +10 -0
  98. data/lib/chemicalml/cml/list.rb +10 -0
  99. data/lib/chemicalml/cml/matrix.rb +10 -0
  100. data/lib/chemicalml/cml/metadata.rb +10 -0
  101. data/lib/chemicalml/cml/metadata_list.rb +10 -0
  102. data/lib/chemicalml/cml/molecule.rb +10 -0
  103. data/lib/chemicalml/cml/name.rb +10 -0
  104. data/lib/chemicalml/cml/namespace.rb +17 -0
  105. data/lib/chemicalml/cml/parameter.rb +10 -0
  106. data/lib/chemicalml/cml/parameter_list.rb +10 -0
  107. data/lib/chemicalml/cml/product.rb +10 -0
  108. data/lib/chemicalml/cml/product_list.rb +10 -0
  109. data/lib/chemicalml/cml/property.rb +10 -0
  110. data/lib/chemicalml/cml/property_list.rb +10 -0
  111. data/lib/chemicalml/cml/reactant.rb +10 -0
  112. data/lib/chemicalml/cml/reactant_list.rb +10 -0
  113. data/lib/chemicalml/cml/reaction.rb +10 -0
  114. data/lib/chemicalml/cml/reaction_list.rb +10 -0
  115. data/lib/chemicalml/cml/role/array.rb +13 -0
  116. data/lib/chemicalml/cml/role/atom.rb +13 -0
  117. data/lib/chemicalml/cml/role/atom_array.rb +13 -0
  118. data/lib/chemicalml/cml/role/atom_parity.rb +13 -0
  119. data/lib/chemicalml/cml/role/bond.rb +13 -0
  120. data/lib/chemicalml/cml/role/bond_array.rb +13 -0
  121. data/lib/chemicalml/cml/role/bond_stereo.rb +13 -0
  122. data/lib/chemicalml/cml/role/cml_module.rb +14 -0
  123. data/lib/chemicalml/cml/role/dictionary.rb +13 -0
  124. data/lib/chemicalml/cml/role/dictionary_entry.rb +13 -0
  125. data/lib/chemicalml/cml/role/document.rb +13 -0
  126. data/lib/chemicalml/cml/role/formula.rb +13 -0
  127. data/lib/chemicalml/cml/role/identifier.rb +13 -0
  128. data/lib/chemicalml/cml/role/label.rb +13 -0
  129. data/lib/chemicalml/cml/role/list.rb +13 -0
  130. data/lib/chemicalml/cml/role/matrix.rb +13 -0
  131. data/lib/chemicalml/cml/role/metadata.rb +13 -0
  132. data/lib/chemicalml/cml/role/metadata_list.rb +13 -0
  133. data/lib/chemicalml/cml/role/molecule.rb +13 -0
  134. data/lib/chemicalml/cml/role/name.rb +13 -0
  135. data/lib/chemicalml/cml/role/parameter.rb +13 -0
  136. data/lib/chemicalml/cml/role/parameter_list.rb +13 -0
  137. data/lib/chemicalml/cml/role/product.rb +13 -0
  138. data/lib/chemicalml/cml/role/product_list.rb +13 -0
  139. data/lib/chemicalml/cml/role/property.rb +13 -0
  140. data/lib/chemicalml/cml/role/property_list.rb +13 -0
  141. data/lib/chemicalml/cml/role/reactant.rb +13 -0
  142. data/lib/chemicalml/cml/role/reactant_list.rb +13 -0
  143. data/lib/chemicalml/cml/role/reaction.rb +13 -0
  144. data/lib/chemicalml/cml/role/reaction_list.rb +13 -0
  145. data/lib/chemicalml/cml/role/scalar.rb +13 -0
  146. data/lib/chemicalml/cml/role/substance.rb +13 -0
  147. data/lib/chemicalml/cml/role/unit.rb +13 -0
  148. data/lib/chemicalml/cml/role/unit_list.rb +13 -0
  149. data/lib/chemicalml/cml/role/unit_type.rb +13 -0
  150. data/lib/chemicalml/cml/role/unit_type_list.rb +13 -0
  151. data/lib/chemicalml/cml/role.rb +54 -0
  152. data/lib/chemicalml/cml/scalar.rb +10 -0
  153. data/lib/chemicalml/cml/schema24/configuration.rb +19 -0
  154. data/lib/chemicalml/cml/schema24.rb +40 -0
  155. data/lib/chemicalml/cml/schema3/configuration.rb +17 -0
  156. data/lib/chemicalml/cml/schema3.rb +40 -0
  157. data/lib/chemicalml/cml/substance.rb +10 -0
  158. data/lib/chemicalml/cml/translator/value_translations.rb +265 -0
  159. data/lib/chemicalml/cml/translator.rb +233 -0
  160. data/lib/chemicalml/cml/unit.rb +10 -0
  161. data/lib/chemicalml/cml/unit_list.rb +10 -0
  162. data/lib/chemicalml/cml/unit_type.rb +10 -0
  163. data/lib/chemicalml/cml/unit_type_list.rb +10 -0
  164. data/lib/chemicalml/cml/visitable.rb +65 -0
  165. data/lib/chemicalml/cml/wire_class_macro.rb +45 -0
  166. data/lib/chemicalml/cml.rb +55 -0
  167. data/lib/chemicalml/context_configuration.rb +127 -0
  168. data/lib/chemicalml/convention/base.rb +48 -0
  169. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +33 -0
  170. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +38 -0
  171. data/lib/chemicalml/convention/compchem/constraints.rb +14 -0
  172. data/lib/chemicalml/convention/compchem.rb +28 -0
  173. data/lib/chemicalml/convention/constraint.rb +79 -0
  174. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +33 -0
  175. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +34 -0
  176. data/lib/chemicalml/convention/dictionary/constraints.rb +14 -0
  177. data/lib/chemicalml/convention/dictionary.rb +28 -0
  178. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +30 -0
  179. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +42 -0
  180. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +54 -0
  181. data/lib/chemicalml/convention/molecular/constraints.rb +19 -0
  182. data/lib/chemicalml/convention/molecular.rb +29 -0
  183. data/lib/chemicalml/convention/registry.rb +50 -0
  184. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +46 -0
  185. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +12 -0
  186. data/lib/chemicalml/convention/unit_dictionary.rb +27 -0
  187. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +34 -0
  188. data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +12 -0
  189. data/lib/chemicalml/convention/unit_type_dictionary.rb +27 -0
  190. data/lib/chemicalml/convention/violation.rb +36 -0
  191. data/lib/chemicalml/convention.rb +39 -0
  192. data/lib/chemicalml/dictionary/entry.rb +59 -0
  193. data/lib/chemicalml/dictionary/enum.rb +51 -0
  194. data/lib/chemicalml/dictionary/link.rb +32 -0
  195. data/lib/chemicalml/dictionary/loader.rb +60 -0
  196. data/lib/chemicalml/dictionary/model.rb +64 -0
  197. data/lib/chemicalml/dictionary/registry.rb +75 -0
  198. data/lib/chemicalml/dictionary.rb +26 -0
  199. data/lib/chemicalml/errors.rb +7 -0
  200. data/lib/chemicalml/model/atom.rb +40 -0
  201. data/lib/chemicalml/model/bond.rb +33 -0
  202. data/lib/chemicalml/model/cml_array.rb +27 -0
  203. data/lib/chemicalml/model/cml_module.rb +45 -0
  204. data/lib/chemicalml/model/document.rb +29 -0
  205. data/lib/chemicalml/model/formula.rb +34 -0
  206. data/lib/chemicalml/model/identifier.rb +21 -0
  207. data/lib/chemicalml/model/label.rb +21 -0
  208. data/lib/chemicalml/model/matrix.rb +29 -0
  209. data/lib/chemicalml/model/metadata.rb +23 -0
  210. data/lib/chemicalml/model/metadata_list.rb +25 -0
  211. data/lib/chemicalml/model/molecule.rb +38 -0
  212. data/lib/chemicalml/model/name.rb +21 -0
  213. data/lib/chemicalml/model/node.rb +45 -0
  214. data/lib/chemicalml/model/parameter.rb +27 -0
  215. data/lib/chemicalml/model/parameter_list.rb +25 -0
  216. data/lib/chemicalml/model/product.rb +21 -0
  217. data/lib/chemicalml/model/product_list.rb +21 -0
  218. data/lib/chemicalml/model/property.rb +28 -0
  219. data/lib/chemicalml/model/property_list.rb +25 -0
  220. data/lib/chemicalml/model/reactant.rb +21 -0
  221. data/lib/chemicalml/model/reactant_list.rb +21 -0
  222. data/lib/chemicalml/model/reaction.rb +41 -0
  223. data/lib/chemicalml/model/reaction_list.rb +23 -0
  224. data/lib/chemicalml/model/scalar.rb +25 -0
  225. data/lib/chemicalml/model/substance.rb +25 -0
  226. data/lib/chemicalml/model.rb +38 -0
  227. data/lib/chemicalml/schema/definition.rb +41 -0
  228. data/lib/chemicalml/schema/registry.rb +30 -0
  229. data/lib/chemicalml/schema.rb +35 -0
  230. data/lib/chemicalml/version.rb +5 -0
  231. data/lib/chemicalml/versioned_parser.rb +43 -0
  232. data/lib/chemicalml.rb +47 -0
  233. data/lib/tasks/reference_fetch.rake +105 -0
  234. data/lib/tasks/scrape_fixtures.rake +133 -0
  235. data/reference-docs/conventions/compchem.md +141 -0
  236. data/reference-docs/conventions/dictionary.md +119 -0
  237. data/reference-docs/conventions/index.md +56 -0
  238. data/reference-docs/conventions/molecular.md +115 -0
  239. data/reference-docs/conventions/unit-dictionary.md +92 -0
  240. data/reference-docs/conventions/unitType-dictionary.md +51 -0
  241. data/reference-docs/dictionaries/compchem.md +335 -0
  242. data/reference-docs/dictionaries/index.md +52 -0
  243. data/reference-docs/examples/schema24-index.md +50 -0
  244. data/reference-docs/examples/schema3-compchem-index.md +14 -0
  245. data/reference-docs/examples/schema3-molecular-index.md +28 -0
  246. data/reference-docs/schemas/schema24/schema.xsd +10624 -0
  247. data/reference-docs/schemas/schema3/schema.xsd +11459 -0
  248. 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ VERSION = "0.1.0"
5
+ 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