chemicalml 0.2.1 → 0.4.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 (352) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +21 -14
  3. data/.rubocop.yml +23 -0
  4. data/CHANGELOG.md +32 -0
  5. data/CLAUDE.md +49 -1
  6. data/README.adoc +154 -26
  7. data/TODO.align/61-parallel-array-attrs.md +54 -0
  8. data/TODO.align/62-per-element-attribute-gaps.md +49 -0
  9. data/TODO.align/63-universal-children-mixin.md +42 -0
  10. data/TODO.align/64-per-element-child-gaps.md +81 -0
  11. data/TODO.align/65-simple-unit-convention.md +40 -0
  12. data/TODO.align/66-schema24-legacy-children.md +32 -0
  13. data/TODO.align/67-final-verification.md +32 -0
  14. data/TODO.align/68-detection-coverage.md +33 -0
  15. data/TODO.align/69-json-yaml-roundtrip.md +27 -0
  16. data/TODO.align/70-iterative-walker.md +40 -0
  17. data/TODO.align/71-violation-value.md +25 -0
  18. data/TODO.align/72-close-child-gaps.md +66 -0
  19. data/TODO.align/73-schema24-parse-limitation.md +64 -0
  20. data/TODO.align/74-final-verification-round18.md +15 -0
  21. data/TODO.align/75-key-value-mappings.md +43 -0
  22. data/TODO.align/76-schema24-workaround.md +57 -0
  23. data/TODO.align/77-convention-coverage-matrix.md +31 -0
  24. data/TODO.align/78-fixture-audit.md +23 -0
  25. data/TODO.align/79-final-verification-round19.md +15 -0
  26. data/TODO.align/80-xsd-enum-constants.md +35 -0
  27. data/TODO.align/81-enum-validation-constraints.md +29 -0
  28. data/TODO.align/82-reference-resolver.md +33 -0
  29. data/TODO.align/83-toml-roundtrip.md +20 -0
  30. data/TODO.align/84-final-verification-round21.md +16 -0
  31. data/TODO.align/85-bond-distinct-atoms.md +25 -0
  32. data/TODO.align/86-references-should-resolve.md +20 -0
  33. data/TODO.align/87-cli-utility.md +26 -0
  34. data/TODO.align/88-diverse-fixtures.md +26 -0
  35. data/TODO.align/89-final-verification-round22.md +15 -0
  36. data/TODO.align/90-bond-stereo-completeness.md +31 -0
  37. data/TODO.align/91-atomparity-distinct.md +21 -0
  38. data/TODO.align/92-unit-dictionary-completeness.md +28 -0
  39. data/TODO.align/93-yard-docs.md +23 -0
  40. data/TODO.align/94-final-verification-round23.md +15 -0
  41. data/TODO.align/README.round17.md +28 -0
  42. data/TODO.align/README.round18.md +37 -0
  43. data/TODO.align/README.round19.md +40 -0
  44. data/TODO.align/README.round20.md +49 -0
  45. data/TODO.align/README.round21.md +35 -0
  46. data/TODO.align/README.round22.md +48 -0
  47. data/TODO.align/README.round23.md +46 -0
  48. data/TODO.align/README.round24.md +53 -0
  49. data/TODO.align/README.round25.md +66 -0
  50. data/TODO.align/README.round26.md +55 -0
  51. data/TODO.align/README.round27.md +51 -0
  52. data/TODO.align/README.round28.md +61 -0
  53. data/TODO.align/README.round29.md +64 -0
  54. data/TODO.align/README.round30.md +50 -0
  55. data/TODO.align/README.round31.md +54 -0
  56. data/TODO.align/README.round32.md +67 -0
  57. data/TODO.align/README.round33.md +62 -0
  58. data/TODO.align/README.round34.md +70 -0
  59. data/chemicalml.gemspec +1 -1
  60. data/docs/constraints.md +161 -0
  61. data/docs/generate_constraint_docs.rb +44 -0
  62. data/exe/chemicalml +6 -0
  63. data/lib/chemicalml/cli/base.rb +68 -0
  64. data/lib/chemicalml/cli/command.rb +43 -0
  65. data/lib/chemicalml/cli/constraints_command.rb +27 -0
  66. data/lib/chemicalml/cli/conventions_command.rb +14 -0
  67. data/lib/chemicalml/cli/dictionaries_command.rb +14 -0
  68. data/lib/chemicalml/cli/elements_command.rb +17 -0
  69. data/lib/chemicalml/cli/enums_command.rb +17 -0
  70. data/lib/chemicalml/cli/info_command.rb +77 -0
  71. data/lib/chemicalml/cli/inspect_command.rb +39 -0
  72. data/lib/chemicalml/cli/validate_command.rb +54 -0
  73. data/lib/chemicalml/cli.rb +43 -0
  74. data/lib/chemicalml/cml/base/abundance.rb +18 -8
  75. data/lib/chemicalml/cml/base/action.rb +34 -16
  76. data/lib/chemicalml/cml/base/action_list.rb +35 -17
  77. data/lib/chemicalml/cml/base/alternative.rb +13 -4
  78. data/lib/chemicalml/cml/base/amount.rb +14 -6
  79. data/lib/chemicalml/cml/base/angle.rb +28 -13
  80. data/lib/chemicalml/cml/base/annotation.rb +14 -6
  81. data/lib/chemicalml/cml/base/any_cml.rb +12 -5
  82. data/lib/chemicalml/cml/base/appinfo.rb +14 -6
  83. data/lib/chemicalml/cml/base/arg.rb +47 -13
  84. data/lib/chemicalml/cml/base/array.rb +40 -19
  85. data/lib/chemicalml/cml/base/array_list.rb +14 -6
  86. data/lib/chemicalml/cml/base/atom.rb +84 -28
  87. data/lib/chemicalml/cml/base/atom_array.rb +60 -23
  88. data/lib/chemicalml/cml/base/atom_parity.rb +14 -6
  89. data/lib/chemicalml/cml/base/atom_set.rb +14 -6
  90. data/lib/chemicalml/cml/base/atom_type.rb +25 -9
  91. data/lib/chemicalml/cml/base/atom_type_list.rb +22 -8
  92. data/lib/chemicalml/cml/base/atomic_basis_function.rb +33 -13
  93. data/lib/chemicalml/cml/base/band.rb +21 -8
  94. data/lib/chemicalml/cml/base/band_list.rb +15 -7
  95. data/lib/chemicalml/cml/base/basis_set.rb +24 -9
  96. data/lib/chemicalml/cml/base/bond.rb +36 -10
  97. data/lib/chemicalml/cml/base/bond_array.rb +34 -11
  98. data/lib/chemicalml/cml/base/bond_set.rb +14 -6
  99. data/lib/chemicalml/cml/base/bond_stereo.rb +20 -9
  100. data/lib/chemicalml/cml/base/bond_type.rb +23 -8
  101. data/lib/chemicalml/cml/base/bond_type_list.rb +22 -8
  102. data/lib/chemicalml/cml/base/cell_parameter.rb +28 -10
  103. data/lib/chemicalml/cml/base/cml_module.rb +42 -11
  104. data/lib/chemicalml/cml/base/common_children.rb +35 -0
  105. data/lib/chemicalml/cml/base/complex_object.rb +12 -5
  106. data/lib/chemicalml/cml/base/condition_list.rb +26 -10
  107. data/lib/chemicalml/cml/base/crystal.rb +26 -10
  108. data/lib/chemicalml/cml/base/definition.rb +14 -5
  109. data/lib/chemicalml/cml/base/description.rb +16 -6
  110. data/lib/chemicalml/cml/base/dictionary.rb +28 -11
  111. data/lib/chemicalml/cml/base/dictionary_entry.rb +69 -10
  112. data/lib/chemicalml/cml/base/dimension.rb +26 -12
  113. data/lib/chemicalml/cml/base/document.rb +24 -8
  114. data/lib/chemicalml/cml/base/documentation.rb +12 -5
  115. data/lib/chemicalml/cml/base/eigen.rb +32 -8
  116. data/lib/chemicalml/cml/base/electron.rb +26 -12
  117. data/lib/chemicalml/cml/base/enumeration.rb +16 -7
  118. data/lib/chemicalml/cml/base/expression.rb +21 -6
  119. data/lib/chemicalml/cml/base/float.rb +22 -10
  120. data/lib/chemicalml/cml/base/float_array.rb +24 -11
  121. data/lib/chemicalml/cml/base/formula.rb +31 -10
  122. data/lib/chemicalml/cml/base/fragment.rb +30 -12
  123. data/lib/chemicalml/cml/base/fragment_list.rb +24 -9
  124. data/lib/chemicalml/cml/base/gradient.rb +19 -6
  125. data/lib/chemicalml/cml/base/identifier.rb +18 -8
  126. data/lib/chemicalml/cml/base/integer.rb +22 -10
  127. data/lib/chemicalml/cml/base/integer_array.rb +24 -11
  128. data/lib/chemicalml/cml/base/isotope.rb +28 -9
  129. data/lib/chemicalml/cml/base/isotope_list.rb +17 -8
  130. data/lib/chemicalml/cml/base/join.rb +25 -9
  131. data/lib/chemicalml/cml/base/kpoint.rb +16 -7
  132. data/lib/chemicalml/cml/base/kpoint_list.rb +15 -7
  133. data/lib/chemicalml/cml/base/label.rb +14 -6
  134. data/lib/chemicalml/cml/base/lattice.rb +35 -10
  135. data/lib/chemicalml/cml/base/lattice_vector.rb +16 -7
  136. data/lib/chemicalml/cml/base/length.rb +26 -12
  137. data/lib/chemicalml/cml/base/line3.rb +18 -8
  138. data/lib/chemicalml/cml/base/link.rb +41 -20
  139. data/lib/chemicalml/cml/base/list.rb +22 -10
  140. data/lib/chemicalml/cml/base/map.rb +32 -11
  141. data/lib/chemicalml/cml/base/matrix.rb +32 -15
  142. data/lib/chemicalml/cml/base/mechanism.rb +20 -7
  143. data/lib/chemicalml/cml/base/mechanism_component.rb +14 -6
  144. data/lib/chemicalml/cml/base/metadata.rb +18 -8
  145. data/lib/chemicalml/cml/base/metadata_list.rb +27 -8
  146. data/lib/chemicalml/cml/base/molecule.rb +92 -26
  147. data/lib/chemicalml/cml/base/molecule_list.rb +26 -8
  148. data/lib/chemicalml/cml/base/name.rb +10 -4
  149. data/lib/chemicalml/cml/base/object.rb +18 -8
  150. data/lib/chemicalml/cml/base/observation.rb +16 -7
  151. data/lib/chemicalml/cml/base/operator.rb +14 -6
  152. data/lib/chemicalml/cml/base/parameter.rb +33 -13
  153. data/lib/chemicalml/cml/base/parameter_list.rb +27 -8
  154. data/lib/chemicalml/cml/base/particle.rb +20 -9
  155. data/lib/chemicalml/cml/base/peak.rb +73 -26
  156. data/lib/chemicalml/cml/base/peak_group.rb +62 -26
  157. data/lib/chemicalml/cml/base/peak_list.rb +24 -9
  158. data/lib/chemicalml/cml/base/peak_structure.rb +40 -13
  159. data/lib/chemicalml/cml/base/plane3.rb +14 -6
  160. data/lib/chemicalml/cml/base/point3.rb +14 -6
  161. data/lib/chemicalml/cml/base/potential.rb +17 -6
  162. data/lib/chemicalml/cml/base/potential_form.rb +30 -7
  163. data/lib/chemicalml/cml/base/potential_list.rb +15 -7
  164. data/lib/chemicalml/cml/base/product.rb +45 -10
  165. data/lib/chemicalml/cml/base/product_list.rb +29 -9
  166. data/lib/chemicalml/cml/base/property.rb +29 -11
  167. data/lib/chemicalml/cml/base/property_list.rb +23 -8
  168. data/lib/chemicalml/cml/base/reactant.rb +45 -10
  169. data/lib/chemicalml/cml/base/reactant_list.rb +29 -9
  170. data/lib/chemicalml/cml/base/reaction.rb +74 -20
  171. data/lib/chemicalml/cml/base/reaction_list.rb +23 -8
  172. data/lib/chemicalml/cml/base/reaction_scheme.rb +43 -13
  173. data/lib/chemicalml/cml/base/reaction_step.rb +30 -12
  174. data/lib/chemicalml/cml/base/reaction_step_list.rb +30 -11
  175. data/lib/chemicalml/cml/base/reactive_centre.rb +31 -6
  176. data/lib/chemicalml/cml/base/region.rb +20 -9
  177. data/lib/chemicalml/cml/base/related_entry.rb +11 -3
  178. data/lib/chemicalml/cml/base/sample.rb +30 -7
  179. data/lib/chemicalml/cml/base/scalar.rb +32 -15
  180. data/lib/chemicalml/cml/base/spectator.rb +25 -6
  181. data/lib/chemicalml/cml/base/spectator_list.rb +20 -7
  182. data/lib/chemicalml/cml/base/spectrum.rb +57 -18
  183. data/lib/chemicalml/cml/base/spectrum_data.rb +19 -9
  184. data/lib/chemicalml/cml/base/spectrum_list.rb +28 -9
  185. data/lib/chemicalml/cml/base/sphere3.rb +14 -6
  186. data/lib/chemicalml/cml/base/stmml.rb +12 -5
  187. data/lib/chemicalml/cml/base/string.rb +14 -6
  188. data/lib/chemicalml/cml/base/string_array.rb +22 -10
  189. data/lib/chemicalml/cml/base/substance.rb +36 -14
  190. data/lib/chemicalml/cml/base/substance_list.rb +29 -10
  191. data/lib/chemicalml/cml/base/symmetry.rb +31 -9
  192. data/lib/chemicalml/cml/base/system.rb +21 -10
  193. data/lib/chemicalml/cml/base/table.rb +38 -13
  194. data/lib/chemicalml/cml/base/table_cell.rb +12 -5
  195. data/lib/chemicalml/cml/base/table_content.rb +19 -9
  196. data/lib/chemicalml/cml/base/table_header.rb +15 -7
  197. data/lib/chemicalml/cml/base/table_header_cell.rb +22 -10
  198. data/lib/chemicalml/cml/base/table_row.rb +15 -7
  199. data/lib/chemicalml/cml/base/table_row_list.rb +15 -7
  200. data/lib/chemicalml/cml/base/tcell.rb +16 -7
  201. data/lib/chemicalml/cml/base/torsion.rb +28 -13
  202. data/lib/chemicalml/cml/base/transform3.rb +12 -5
  203. data/lib/chemicalml/cml/base/transition_state.rb +23 -5
  204. data/lib/chemicalml/cml/base/trow.rb +22 -6
  205. data/lib/chemicalml/cml/base/unit.rb +39 -16
  206. data/lib/chemicalml/cml/base/unit_list.rb +34 -12
  207. data/lib/chemicalml/cml/base/unit_type.rb +30 -10
  208. data/lib/chemicalml/cml/base/unit_type_list.rb +29 -11
  209. data/lib/chemicalml/cml/base/vector3.rb +14 -6
  210. data/lib/chemicalml/cml/base/xaxis.rb +32 -10
  211. data/lib/chemicalml/cml/base/yaxis.rb +30 -10
  212. data/lib/chemicalml/cml/base/z_matrix.rb +26 -5
  213. data/lib/chemicalml/cml/base.rb +139 -138
  214. data/lib/chemicalml/cml/canonical_comparison.rb +54 -0
  215. data/lib/chemicalml/cml/elements.rb +145 -136
  216. data/lib/chemicalml/cml/enums.rb +55 -0
  217. data/lib/chemicalml/cml/namespace.rb +2 -3
  218. data/lib/chemicalml/cml/patterns.rb +33 -0
  219. data/lib/chemicalml/cml/reference_resolver.rb +137 -0
  220. data/lib/chemicalml/cml/role.rb +138 -138
  221. data/lib/chemicalml/cml/schema24/configuration.rb +11 -1
  222. data/lib/chemicalml/cml/schema24.rb +139 -139
  223. data/lib/chemicalml/cml/schema3.rb +123 -123
  224. data/lib/chemicalml/cml/visitable.rb +77 -3
  225. data/lib/chemicalml/cml/wire_class_registry.rb +3 -3
  226. data/lib/chemicalml/cml/xhtml/p.rb +21 -0
  227. data/lib/chemicalml/cml/xhtml.rb +20 -0
  228. data/lib/chemicalml/cml.rb +164 -129
  229. data/lib/chemicalml/context_configuration.rb +4 -2
  230. data/lib/chemicalml/convention/cascade/constraints/reaction_scheme_must_have_content.rb +33 -0
  231. data/lib/chemicalml/convention/cascade/constraints/reaction_step_list_must_contain_steps.rb +31 -0
  232. data/lib/chemicalml/convention/cascade/constraints/reaction_step_must_have_reaction_or_lists.rb +33 -0
  233. data/lib/chemicalml/convention/cascade/constraints/reactive_centre_atom_refs_should_be_present.rb +34 -0
  234. data/lib/chemicalml/convention/cascade/constraints.rb +18 -0
  235. data/lib/chemicalml/convention/cascade.rb +43 -0
  236. data/lib/chemicalml/convention/compchem/constraints/array_rules.rb +1 -1
  237. data/lib/chemicalml/convention/compchem/constraints/calculation_requires_finalization.rb +1 -1
  238. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +2 -1
  239. data/lib/chemicalml/convention/compchem/constraints/environment_at_most_one_property_list.rb +1 -1
  240. data/lib/chemicalml/convention/compchem/constraints/environment_must_not_contain_parameter.rb +1 -1
  241. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_molecule.rb +1 -1
  242. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_property_list.rb +1 -1
  243. data/lib/chemicalml/convention/compchem/constraints/finalization_must_have_content.rb +44 -0
  244. data/lib/chemicalml/convention/compchem/constraints/finalization_must_not_contain_parameter.rb +1 -1
  245. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_molecule.rb +1 -1
  246. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_parameter_list.rb +1 -1
  247. data/lib/chemicalml/convention/compchem/constraints/initialization_must_have_content.rb +54 -0
  248. data/lib/chemicalml/convention/compchem/constraints/initialization_must_not_contain_property.rb +1 -1
  249. data/lib/chemicalml/convention/compchem/constraints/job_list_module_must_have_id.rb +1 -0
  250. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_environment.rb +1 -1
  251. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_finalization.rb +1 -1
  252. data/lib/chemicalml/convention/compchem/constraints/job_module_must_have_id.rb +1 -0
  253. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +1 -0
  254. data/lib/chemicalml/convention/compchem/constraints/matrix_rules.rb +1 -1
  255. data/lib/chemicalml/convention/compchem/constraints/scalar_units.rb +1 -1
  256. data/lib/chemicalml/convention/compchem/constraints.rb +4 -0
  257. data/lib/chemicalml/convention/compchem.rb +4 -2
  258. data/lib/chemicalml/convention/constraint.rb +40 -9
  259. data/lib/chemicalml/convention/coordinator.rb +4 -3
  260. data/lib/chemicalml/convention/detection.rb +10 -4
  261. data/lib/chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace.rb +1 -0
  262. data/lib/chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash.rb +1 -1
  263. data/lib/chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern.rb +1 -1
  264. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +1 -1
  265. data/lib/chemicalml/convention/dictionary/constraints/entry_must_contain_definition.rb +15 -1
  266. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +1 -1
  267. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_unit_type.rb +1 -0
  268. data/lib/chemicalml/convention/dictionary/constraints/entry_units_co_constraints.rb +1 -1
  269. data/lib/chemicalml/convention/dictionary/constraints.rb +8 -8
  270. data/lib/chemicalml/convention/dictionary.rb +2 -2
  271. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula.rb +1 -0
  272. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +15 -6
  273. data/lib/chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired.rb +1 -1
  274. data/lib/chemicalml/convention/molecular/constraints/atom_element_type_should_be_in_periodic_table.rb +39 -0
  275. data/lib/chemicalml/convention/molecular/constraints/atom_id_must_match_pattern.rb +3 -2
  276. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +1 -0
  277. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_element_type.rb +1 -1
  278. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_id.rb +2 -1
  279. data/lib/chemicalml/convention/molecular/constraints/atom_parity_atom_refs4_should_be_distinct.rb +44 -0
  280. data/lib/chemicalml/convention/molecular/constraints/atom_parity_should_include_parent_atom.rb +65 -0
  281. data/lib/chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule.rb +1 -0
  282. data/lib/chemicalml/convention/molecular/constraints/bond_atom_refs2_should_be_distinct.rb +44 -0
  283. data/lib/chemicalml/convention/molecular/constraints/bond_id_should_match_pattern.rb +38 -0
  284. data/lib/chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule.rb +1 -0
  285. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2.rb +1 -1
  286. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_order.rb +2 -1
  287. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +1 -0
  288. data/lib/chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref.rb +1 -0
  289. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_be_in_enum.rb +39 -0
  290. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric.rb +1 -1
  291. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_atom_refs4_should_be_distinct.rb +43 -0
  292. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4.rb +1 -1
  293. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref.rb +1 -0
  294. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_should_be_in_enum.rb +38 -0
  295. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2.rb +1 -1
  296. data/lib/chemicalml/convention/molecular/constraints/dict_ref_should_resolve.rb +59 -0
  297. data/lib/chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children.rb +1 -0
  298. data/lib/chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children.rb +1 -0
  299. data/lib/chemicalml/convention/molecular/constraints/molecule_chirality_should_be_in_enum.rb +38 -0
  300. data/lib/chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level.rb +1 -0
  301. data/lib/chemicalml/convention/molecular/constraints/molecule_id_should_match_pattern.rb +38 -0
  302. data/lib/chemicalml/convention/molecular/constraints/molecule_must_have_id.rb +1 -1
  303. data/lib/chemicalml/convention/molecular/constraints/property_must_have_dict_ref.rb +1 -1
  304. data/lib/chemicalml/convention/molecular/constraints/property_scalar_data_type_matches_dictionary.rb +58 -0
  305. data/lib/chemicalml/convention/molecular/constraints/references_should_resolve.rb +37 -0
  306. data/lib/chemicalml/convention/molecular/constraints/scalar_must_have_data_type.rb +1 -1
  307. data/lib/chemicalml/convention/molecular/constraints.rb +49 -23
  308. data/lib/chemicalml/convention/molecular.rb +15 -2
  309. data/lib/chemicalml/convention/registry.rb +79 -11
  310. data/lib/chemicalml/convention/simple_unit/constraints/root_must_be_unit_list.rb +24 -0
  311. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_power.rb +32 -0
  312. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_symbol.rb +31 -0
  313. data/lib/chemicalml/convention/simple_unit/constraints.rb +16 -0
  314. data/lib/chemicalml/convention/simple_unit.rb +37 -0
  315. data/lib/chemicalml/convention/spectroscopy/constraints/peak_list_must_contain_peaks.rb +31 -0
  316. data/lib/chemicalml/convention/spectroscopy/constraints/peak_should_have_values.rb +34 -0
  317. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_content.rb +31 -0
  318. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_convention.rb +32 -0
  319. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_format.rb +31 -0
  320. data/lib/chemicalml/convention/spectroscopy/constraints.rb +20 -0
  321. data/lib/chemicalml/convention/spectroscopy.rb +41 -0
  322. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_contain_at_least_one_unit.rb +1 -0
  323. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_have_namespace.rb +1 -1
  324. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_contain_definition.rb +1 -0
  325. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_id.rb +1 -0
  326. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_multiplier_or_constant_to_si.rb +34 -0
  327. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_parent_si.rb +32 -0
  328. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +1 -1
  329. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_title.rb +32 -0
  330. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_parent_si_should_resolve.rb +45 -0
  331. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_unit_type_should_resolve.rb +46 -0
  332. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +10 -0
  333. data/lib/chemicalml/convention/unit_dictionary.rb +7 -2
  334. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_id_must_match_pattern.rb +1 -1
  335. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_contain_at_least_one_unit_type.rb +1 -0
  336. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_have_namespace.rb +1 -1
  337. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_contain_definition.rb +1 -0
  338. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +1 -1
  339. data/lib/chemicalml/convention/unit_type_dictionary.rb +2 -2
  340. data/lib/chemicalml/convention/validation_report.rb +30 -3
  341. data/lib/chemicalml/convention/violation.rb +9 -3
  342. data/lib/chemicalml/convention.rb +41 -14
  343. data/lib/chemicalml/dictionary.rb +13 -9
  344. data/lib/chemicalml/logger.rb +47 -0
  345. data/lib/chemicalml/schema/definition.rb +2 -2
  346. data/lib/chemicalml/schema.rb +7 -7
  347. data/lib/chemicalml/version.rb +1 -1
  348. data/lib/chemicalml/versioned_parser.rb +10 -10
  349. data/lib/chemicalml.rb +78 -15
  350. data/lib/tasks/reference_fetch.rake +30 -31
  351. data/lib/tasks/scrape_fixtures.rake +23 -24
  352. metadata +118 -5
@@ -7,9 +7,9 @@ module Chemicalml
7
7
  # Every unitType MUST have an `id` and a `name` attribute per
8
8
  # the unitType-dictionary convention.
9
9
  class UnitTypeMustHaveIdAndName < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Every unitType MUST have an `id` and a `name` attribute per the unitType-dictionary convention.'
10
11
  applies_to Chemicalml::Cml::Role::UnitType
11
12
  def check_node(node, path)
12
-
13
13
  violations = []
14
14
  if node.id.to_s.empty?
15
15
  violations << violation(path: path.join('/'),
@@ -8,9 +8,9 @@ module Chemicalml
8
8
  module UnitTypeDictionary
9
9
  extend Base
10
10
 
11
- autoload :Constraints, "chemicalml/convention/unit_type_dictionary/constraints"
11
+ autoload :Constraints, 'chemicalml/convention/unit_type_dictionary/constraints'
12
12
 
13
- QNAME = "convention:unitType-dictionary".freeze
13
+ QNAME = 'convention:unitType-dictionary'
14
14
  NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}unitType-dictionary".freeze
15
15
 
16
16
  def self.qname
@@ -39,9 +39,7 @@ module Chemicalml
39
39
  def +(other)
40
40
  return other if other.nil?
41
41
 
42
- unless other.is_a?(ValidationReport)
43
- raise ArgumentError, "cannot combine ValidationReport with #{other.class}"
44
- end
42
+ raise ArgumentError, "cannot combine ValidationReport with #{other.class}" unless other.is_a?(ValidationReport)
45
43
 
46
44
  ValidationReport.new(violations + other.violations)
47
45
  end
@@ -58,6 +56,35 @@ module Chemicalml
58
56
  def to_s
59
57
  "#{size} violation(s): #{errors.length} error(s), #{warnings.length} warning(s)"
60
58
  end
59
+
60
+ # Human-readable multi-line summary. Suitable for CLI output
61
+ # and logging. Each violation is rendered with severity, path,
62
+ # and message (and value when present).
63
+ #
64
+ # @return [String]
65
+ def summary
66
+ return 'OK — no violations' if ok? && !has_warnings?
67
+
68
+ lines = []
69
+ lines << "Errors: #{errors.size}, Warnings: #{warnings.size}"
70
+ unless errors.empty?
71
+ lines << ''
72
+ lines << 'Errors:'
73
+ errors.each { |v| lines << " ERROR #{v.path}: #{v.message}#{value_suffix(v)}" }
74
+ end
75
+ unless warnings.empty?
76
+ lines << ''
77
+ lines << 'Warnings:'
78
+ warnings.each { |v| lines << " WARN #{v.path}: #{v.message}#{value_suffix(v)}" }
79
+ end
80
+ lines.join("\n")
81
+ end
82
+
83
+ private
84
+
85
+ def value_suffix(violation)
86
+ violation.value.nil? ? '' : " (value=#{violation.value.inspect})"
87
+ end
61
88
  end
62
89
  end
63
90
  end
@@ -5,18 +5,23 @@ module Chemicalml
5
5
  # Value object describing one constraint violation. Severity is
6
6
  # either `:error` (document is invalid under the convention) or
7
7
  # `:warning` (document is valid but problematic).
8
+ #
9
+ # `value` carries the offending value where natural (e.g. the
10
+ # duplicated atom id, the malformed unit symbol). Optional —
11
+ # constraints that flag a missing attribute leave `value` nil.
8
12
  class Violation
9
13
  SEVERITIES = %i[error warning].freeze
10
14
 
11
- attr_reader :path, :message, :severity, :constraint
15
+ attr_reader :path, :message, :severity, :constraint, :value
12
16
 
13
- def initialize(path:, message:, severity: :error, constraint: nil)
17
+ def initialize(path:, message:, severity: :error, constraint: nil, value: nil)
14
18
  raise ArgumentError, "unknown severity: #{severity.inspect}" unless SEVERITIES.include?(severity.to_sym)
15
19
 
16
20
  @path = path
17
21
  @message = message
18
22
  @severity = severity.to_sym
19
23
  @constraint = constraint
24
+ @value = value
20
25
  freeze
21
26
  end
22
27
 
@@ -29,7 +34,8 @@ module Chemicalml
29
34
  end
30
35
 
31
36
  def to_s
32
- "#{severity.upcase} #{path}: #{message}"
37
+ suffix = value.nil? ? '' : " (value=#{value.inspect})"
38
+ "#{severity.upcase} #{path}: #{message}#{suffix}"
33
39
  end
34
40
  end
35
41
  end
@@ -8,41 +8,68 @@ module Chemicalml
8
8
  # convention owns its constraint classes; the framework walks
9
9
  # whatever was registered.
10
10
  #
11
- # The five built-in conventions:
11
+ # The built-in conventions:
12
12
  #
13
13
  # molecular http://www.xml-cml.org/convention/molecular
14
14
  # compchem http://www.xml-cml.org/convention/compchem
15
15
  # dictionary http://www.xml-cml.org/convention/dictionary
16
16
  # unit-dictionary http://www.xml-cml.org/convention/unit-dictionary
17
17
  # unitType-dictionary http://www.xml-cml.org/convention/unitType-dictionary
18
+ # spectroscopy http://www.xml-cml.org/convention/spectroscopy
19
+ # cascade http://www.xml-cml.org/convention/cascade
20
+ # simpleUnit http://www.xml-cml.org/convention/simpleUnit
18
21
  module Convention
19
- autoload :Base, "chemicalml/convention/base"
20
- autoload :Compchem, "chemicalml/convention/compchem"
21
- autoload :Constraint, "chemicalml/convention/constraint"
22
- autoload :Coordinator, "chemicalml/convention/coordinator"
23
- autoload :Detection, "chemicalml/convention/detection"
24
- autoload :Dictionary, "chemicalml/convention/dictionary"
25
- autoload :Molecular, "chemicalml/convention/molecular"
26
- autoload :Registry, "chemicalml/convention/registry"
27
- autoload :UnitDictionary, "chemicalml/convention/unit_dictionary"
28
- autoload :UnitTypeDictionary, "chemicalml/convention/unit_type_dictionary"
29
- autoload :ValidationReport, "chemicalml/convention/validation_report"
30
- autoload :Violation, "chemicalml/convention/violation"
22
+ autoload :Base, 'chemicalml/convention/base'
23
+ autoload :Cascade, 'chemicalml/convention/cascade'
24
+ autoload :Compchem, 'chemicalml/convention/compchem'
25
+ autoload :Constraint, 'chemicalml/convention/constraint'
26
+ autoload :Coordinator, 'chemicalml/convention/coordinator'
27
+ autoload :Detection, 'chemicalml/convention/detection'
28
+ autoload :Dictionary, 'chemicalml/convention/dictionary'
29
+ autoload :Molecular, 'chemicalml/convention/molecular'
30
+ autoload :Registry, 'chemicalml/convention/registry'
31
+ autoload :SimpleUnit, 'chemicalml/convention/simple_unit'
32
+ autoload :Spectroscopy, 'chemicalml/convention/spectroscopy'
33
+ autoload :UnitDictionary, 'chemicalml/convention/unit_dictionary'
34
+ autoload :UnitTypeDictionary, 'chemicalml/convention/unit_type_dictionary'
35
+ autoload :ValidationReport, 'chemicalml/convention/validation_report'
36
+ autoload :Violation, 'chemicalml/convention/violation'
31
37
 
32
- CONVENTION_NAMESPACE = "http://www.xml-cml.org/convention/".freeze
38
+ CONVENTION_NAMESPACE = 'http://www.xml-cml.org/convention/'
33
39
 
40
+ # Look up a convention by QName (e.g. "convention:molecular").
41
+ # @param qname [String] the convention QName.
42
+ # @return [Module, nil] the convention module, or nil if unknown.
34
43
  def self.lookup(qname)
35
44
  Registry.lookup(qname)
36
45
  end
37
46
 
47
+ # Validate a document against a named convention. Returns the
48
+ # raw violation list.
49
+ #
50
+ # @param document [Lutaml::Model::Serializable] the CML document.
51
+ # @param qname [String] the convention QName (e.g. "convention:molecular").
52
+ # @return [Array<Chemicalml::Convention::Violation>] the violations.
53
+ # @raise [ArgumentError] if the qname is not registered.
38
54
  def self.validate(document, qname:)
39
55
  Registry.validate(document, qname: qname)
40
56
  end
41
57
 
58
+ # Validate and return a structured report (errors + warnings).
59
+ #
60
+ # @param document [Lutaml::Model::Serializable] the CML document.
61
+ # @param qname [String] the convention QName.
62
+ # @return [Chemicalml::Convention::ValidationReport] the report.
42
63
  def self.validate_report(document, qname:)
43
64
  Registry.validate_report(document, qname: qname)
44
65
  end
45
66
 
67
+ # Auto-detect the convention from the document root's `convention`
68
+ # attribute and validate.
69
+ #
70
+ # @param document [Lutaml::Model::Serializable] the CML document.
71
+ # @return [Chemicalml::Convention::ValidationReport] the report.
72
+ # @raise [ArgumentError] if no convention is declared.
46
73
  def self.detect_and_validate(document)
47
74
  Registry.detect_and_validate(document)
48
75
  end
@@ -8,17 +8,21 @@ module Chemicalml
8
8
  # Built-in dictionaries ship as YAML under `data/dictionaries/` and
9
9
  # are loaded into `Chemicalml::Dictionary::Registry` lazily.
10
10
  module Dictionary
11
- autoload :Entry, "chemicalml/dictionary/entry"
12
- autoload :Enum, "chemicalml/dictionary/enum"
13
- autoload :Link, "chemicalml/dictionary/link"
14
- autoload :Loader, "chemicalml/dictionary/loader"
15
- autoload :Model, "chemicalml/dictionary/model"
16
- autoload :Registry, "chemicalml/dictionary/registry"
11
+ autoload :Entry, 'chemicalml/dictionary/entry'
12
+ autoload :Enum, 'chemicalml/dictionary/enum'
13
+ autoload :Link, 'chemicalml/dictionary/link'
14
+ autoload :Loader, 'chemicalml/dictionary/loader'
15
+ autoload :Model, 'chemicalml/dictionary/model'
16
+ autoload :Registry, 'chemicalml/dictionary/registry'
17
17
 
18
- BUILTIN_DIR = File.expand_path("../../data/dictionaries", __dir__).freeze
18
+ BUILTIN_DIR = File.expand_path('../../data/dictionaries', __dir__).freeze
19
19
 
20
- # Convenience: load a built-in dictionary by short name
21
- # (e.g. `:compchem`). Returns a `Chemicalml::Dictionary::Model`.
20
+ # Convenience: load a built-in dictionary by short name.
21
+ #
22
+ # @param name [Symbol, String] the dictionary short name (YAML
23
+ # filename stem, e.g. `:compchem`, `:cml`).
24
+ # @return [Chemicalml::Dictionary::Model] the loaded dictionary.
25
+ # @raise [KeyError] if `name` is not a built-in dictionary.
22
26
  def self.load(name)
23
27
  Registry.load_builtin(name)
24
28
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Chemicalml
6
+ # Logger used across the CLI and API. Wraps Ruby's ::Logger and can
7
+ # bridge to Thor's `say` / `say_error` when running under a Thor
8
+ # dispatcher — that gives coloured output on the terminal while
9
+ # plain `Logger.new($stderr)` works for library callers.
10
+ #
11
+ # Severity routing under Thor:
12
+ # debug → suppressed (set level manually if needed)
13
+ # info → say (green-ish, $stdout)
14
+ # warn → say_error with :yellow ($stderr)
15
+ # error → say_error with :red ($stderr)
16
+ # fatal → say_error with :red ($stderr)
17
+ class Logger < ::Logger
18
+ attr_reader :thor_shell
19
+
20
+ # Bridge to a Thor shell instance (self inside a Thor command).
21
+ # When set, info/warn/error route through Thor for coloured output.
22
+ def thor_shell=(shell)
23
+ @thor_shell = shell
24
+ end
25
+
26
+ def add(severity, message = nil, progname = nil)
27
+ return super unless thor_shell
28
+ return true if severity < level
29
+
30
+ msg = message || progname
31
+ case severity
32
+ when ERROR, FATAL
33
+ thor_shell.say_error(msg.to_s, :red)
34
+ when WARN
35
+ thor_shell.say_error(msg.to_s, :yellow)
36
+ else
37
+ thor_shell.say(msg.to_s)
38
+ end
39
+ end
40
+
41
+ # Convenience: create a default logger that writes to $stderr at
42
+ # INFO level. Used when no logger is explicitly provided.
43
+ def self.default
44
+ new($stderr).tap { |l| l.level = INFO }
45
+ end
46
+ end
47
+ end
@@ -19,7 +19,7 @@ module Chemicalml
19
19
  end
20
20
 
21
21
  def wire_namespace_constant
22
- ruby_namespace.split("::").reduce(Object) { |ns, name| ns.const_get(name) }
22
+ ruby_namespace.split('::').reduce(Object) { |ns, name| ns.const_get(name) }
23
23
  end
24
24
 
25
25
  def eql?(other)
@@ -34,7 +34,7 @@ module Chemicalml
34
34
  private
35
35
 
36
36
  def project_root
37
- File.expand_path("../../..", __dir__)
37
+ File.expand_path('../../..', __dir__)
38
38
  end
39
39
  end
40
40
  end
@@ -9,10 +9,10 @@ module Chemicalml
9
9
  # `reference-docs/schemas/`. They are never modified or regenerated
10
10
  # from code.
11
11
  module Schema
12
- autoload :Definition, "chemicalml/schema/definition"
13
- autoload :Registry, "chemicalml/schema/registry"
12
+ autoload :Definition, 'chemicalml/schema/definition'
13
+ autoload :Registry, 'chemicalml/schema/registry'
14
14
 
15
- CML_XML_NAMESPACE = "http://www.xml-cml.org/schema"
15
+ CML_XML_NAMESPACE = 'http://www.xml-cml.org/schema'
16
16
 
17
17
  # Built-in versions registered at load time. Adding a new CML schema
18
18
  # version = appending an entry here and creating the matching
@@ -20,15 +20,15 @@ module Chemicalml
20
20
  BUILTIN = {
21
21
  schema24: Definition.new(
22
22
  version: :schema24,
23
- xsd_path: "reference-docs/schemas/schema24/schema.xsd",
23
+ xsd_path: 'reference-docs/schemas/schema24/schema.xsd',
24
24
  xml_namespace: CML_XML_NAMESPACE,
25
- ruby_namespace: "Chemicalml::Cml::Schema24"
25
+ ruby_namespace: 'Chemicalml::Cml::Schema24'
26
26
  ),
27
27
  schema3: Definition.new(
28
28
  version: :schema3,
29
- xsd_path: "reference-docs/schemas/schema3/schema.xsd",
29
+ xsd_path: 'reference-docs/schemas/schema3/schema.xsd',
30
30
  xml_namespace: CML_XML_NAMESPACE,
31
- ruby_namespace: "Chemicalml::Cml::Schema3"
31
+ ruby_namespace: 'Chemicalml::Cml::Schema3'
32
32
  )
33
33
  }.freeze
34
34
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chemicalml
4
- VERSION = "0.2.1"
4
+ VERSION = '0.4.0'
5
5
  end
@@ -32,14 +32,14 @@ module Chemicalml
32
32
  # Adding a new root type = adding one entry here. OCP — no
33
33
  # existing code changes.
34
34
  KNOWN_ROOTS = {
35
- "cml" => :Document,
36
- "molecule" => :Molecule,
37
- "reaction" => :Reaction,
38
- "reactionList" => :ReactionList,
39
- "module" => :Module,
40
- "dictionary" => :Dictionary,
41
- "unitList" => :UnitList,
42
- "unitTypeList" => :UnitTypeList,
35
+ 'cml' => :Document,
36
+ 'molecule' => :Molecule,
37
+ 'reaction' => :Reaction,
38
+ 'reactionList' => :ReactionList,
39
+ 'module' => :Module,
40
+ 'dictionary' => :Dictionary,
41
+ 'unitList' => :UnitList,
42
+ 'unitTypeList' => :UnitTypeList
43
43
  }.freeze
44
44
 
45
45
  def parse(xml, namespace_exist: true)
@@ -57,7 +57,7 @@ module Chemicalml
57
57
  unless const_defined?(class_name, false)
58
58
  raise ArgumentError,
59
59
  "#{name} does not define #{class_name} " \
60
- "(not all root elements exist in every schema version)"
60
+ '(not all root elements exist in every schema version)'
61
61
  end
62
62
 
63
63
  const_get(class_name).from_xml(
@@ -83,7 +83,7 @@ module Chemicalml
83
83
  match = xml.match(/<([a-zA-Z][\w.-]*)(?::[a-zA-Z][\w.-]*)?/)
84
84
  return match[1] if match
85
85
 
86
- raise ArgumentError, "could not detect root element in XML"
86
+ raise ArgumentError, 'could not detect root element in XML'
87
87
  end
88
88
 
89
89
  def xml_input(xml, namespace_exist, root)
data/lib/chemicalml.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "lutaml/model"
3
+ require 'lutaml/model'
4
4
 
5
5
  # Chemicalml provides a Ruby object model for the Chemical Markup Language
6
6
  # (CML).
@@ -13,36 +13,99 @@ require "lutaml/model"
13
13
  # declared attributes and an XML mapping block. Serialization is
14
14
  # framework-backed — no hand-rolled XML in this library.
15
15
  module Chemicalml
16
- autoload :Cml, "chemicalml/cml"
17
- autoload :ContextConfiguration, "chemicalml/context_configuration"
18
- autoload :Convention, "chemicalml/convention"
19
- autoload :Dictionary, "chemicalml/dictionary"
20
- autoload :Error, "chemicalml/errors"
21
- autoload :ParseError, "chemicalml/errors"
22
- autoload :Schema, "chemicalml/schema"
23
- autoload :VERSION, "chemicalml/version"
24
- autoload :VersionedParser, "chemicalml/versioned_parser"
16
+ autoload :Cml, 'chemicalml/cml'
17
+ autoload :Cli, 'chemicalml/cli'
18
+ autoload :ContextConfiguration, 'chemicalml/context_configuration'
19
+ autoload :Convention, 'chemicalml/convention'
20
+ autoload :Dictionary, 'chemicalml/dictionary'
21
+ autoload :Error, 'chemicalml/errors'
22
+ autoload :Logger, 'chemicalml/logger'
23
+ autoload :ParseError, 'chemicalml/errors'
24
+ autoload :Schema, 'chemicalml/schema'
25
+ autoload :VERSION, 'chemicalml/version'
26
+ autoload :VersionedParser, 'chemicalml/versioned_parser'
27
+ autoload :Dictionary, 'chemicalml/dictionary'
28
+ autoload :Error, 'chemicalml/errors'
29
+ autoload :ParseError, 'chemicalml/errors'
30
+ autoload :Schema, 'chemicalml/schema'
31
+ autoload :VERSION, 'chemicalml/version'
32
+ autoload :VersionedParser, 'chemicalml/versioned_parser'
25
33
 
26
34
  module_function
27
35
 
36
+ # Parse a CML XML string into a wire document.
37
+ #
38
+ # @param xml [String] the XML content.
39
+ # @param schema [Symbol] one of `:schema3` (default) or `:schema24`.
40
+ # @param namespace_exist [Boolean] whether the XML already declares
41
+ # the CML namespace. When false, the parser injects it.
42
+ # @return [Chemicalml::Cml::Schema3::Document,
43
+ # Chemicalml::Cml::Schema24::Document,
44
+ # Chemicalml::Cml::Schema3::Molecule, ...] the parsed root,
45
+ # dispatch by detected root element.
46
+ # @raise [ArgumentError] if `xml` is nil/empty or `schema` is unknown.
47
+ # @raise [Chemicalml::ParseError] on malformed XML.
48
+ #
49
+ # @example Parse a Schema3 document
50
+ # doc = Chemicalml.parse("<cml xmlns='...'>...</cml>")
51
+ # @example Parse a Schema2.4 document
52
+ # doc = Chemicalml.parse(xml, schema: :schema24)
28
53
  def parse(xml, schema: :schema3, namespace_exist: true)
29
- raise ArgumentError, "xml must not be nil" if xml.nil?
30
- raise ArgumentError, "xml must not be empty" if xml.to_s.strip.empty?
54
+ raise ArgumentError, 'xml must not be nil' if xml.nil?
55
+ raise ArgumentError, 'xml must not be empty' if xml.to_s.strip.empty?
31
56
 
32
57
  parser_for(schema).parse(xml, namespace_exist: namespace_exist)
33
58
  end
34
59
 
35
- def serialize(document, **opts)
36
- document.to_xml(**opts)
60
+ # Parse a CML file from disk. Convenience wrapper around `parse`
61
+ # that handles the `File.read` boilerplate.
62
+ #
63
+ # @param path [String] path to the CML file.
64
+ # @param schema [Symbol] `:schema3` (default) or `:schema24`.
65
+ # @param namespace_exist [Boolean] forwarded to `parse`.
66
+ # @return (see .parse)
67
+ # @raise [ArgumentError] if the file doesn't exist or is empty.
68
+ def parse_file(path, schema: :schema3, namespace_exist: true)
69
+ raise ArgumentError, "file not found: #{path}" unless File.exist?(path)
70
+
71
+ parse(File.read(path), schema: schema, namespace_exist: namespace_exist)
72
+ end
73
+
74
+ # Serialize a wire document back to XML.
75
+ #
76
+ # @param document [Lutaml::Model::Serializable] a CML wire instance.
77
+ # @param opts [Hash] forwarded to `Lutaml::Model::Serializable#to_xml`.
78
+ # @return [String] the serialized XML.
79
+ def serialize(document, **)
80
+ document.to_xml(**)
37
81
  end
38
82
 
83
+ # Resolve the versioned-parser module for a schema id.
84
+ #
85
+ # @param schema [Symbol] `:schema3` or `:schema24`.
86
+ # @return [Module] the matching `Schema3` or `Schema24` module.
87
+ # @raise [ArgumentError] if `schema` is unknown.
39
88
  def parser_for(schema)
40
89
  case schema.to_sym
41
90
  when :schema3 then Chemicalml::Cml::Schema3
42
91
  when :schema24 then Chemicalml::Cml::Schema24
43
92
  else
44
93
  raise ArgumentError, "unsupported schema: #{schema.inspect} " \
45
- "(supported: :schema3, :schema24)"
94
+ '(supported: :schema3, :schema24)'
46
95
  end
47
96
  end
97
+
98
+ # Auto-detect the convention from the document's root and validate.
99
+ # Top-level convenience for `Convention.detect_and_validate`.
100
+ #
101
+ # @param document [Lutaml::Model::Serializable] the CML document.
102
+ # @param logger [Chemicalml::Logger, nil] optional logger for progress.
103
+ # @return [Chemicalml::Convention::ValidationReport] the report.
104
+ # @raise [ArgumentError] if no convention is declared.
105
+ def validate(document, logger: nil)
106
+ logger&.info 'Auto-detecting convention…'
107
+ report = Convention.detect_and_validate(document)
108
+ logger&.info "Validation complete: #{report.size} violation(s)"
109
+ report
110
+ end
48
111
  end
@@ -1,41 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "uri"
5
- require "fileutils"
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'fileutils'
6
6
 
7
7
  # Idempotent fetcher for xml-cml.org reference material.
8
8
  # Re-running this task only downloads files that are missing locally.
9
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
10
+ BASE = 'http://www.xml-cml.org'
11
+ LICENSE = 'Source: <url>. Distributed by xml-cml.org under CC-BY-3.0.'
12
12
 
13
13
  # Map of remote URL => local path under reference-docs/.
14
14
  SOURCES = {
15
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",
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
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",
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
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",
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
33
  # Schema / spec
34
- "https://www.xml-cml.org/schema/cmllite.html" => "reference-docs/cmllite.html",
34
+ 'https://www.xml-cml.org/schema/cmllite.html' => 'reference-docs/cmllite.html',
35
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"
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
39
  }.freeze
40
40
 
41
41
  def self.fetch_all
@@ -60,7 +60,7 @@ module ReferenceFetch
60
60
  return
61
61
  end
62
62
 
63
- if path.end_with?(".html")
63
+ if path.end_with?('.html')
64
64
  File.write(full, body)
65
65
  else
66
66
  File.write(full, header(url) + body)
@@ -73,19 +73,18 @@ module ReferenceFetch
73
73
  end
74
74
 
75
75
  def self.http_get(url, limit = 5)
76
- raise "too many redirects" if limit.zero?
76
+ raise 'too many redirects' if limit.zero?
77
77
 
78
78
  uri = URI(url)
79
79
  http = Net::HTTP.new(uri.host, uri.port)
80
80
  http.use_ssl = true if uri.is_a?(URI::HTTPS)
81
81
  http.read_timeout = 30
82
- req = Net::HTTP::Get.new(uri, "User-Agent" => "chemicalml-gem-reference-fetch")
82
+ req = Net::HTTP::Get.new(uri, 'User-Agent' => 'chemicalml-gem-reference-fetch')
83
83
  res = http.request(req)
84
84
 
85
85
  case res
86
86
  when Net::HTTPSuccess then res.body
87
- when Net::HTTPRedirection then http_get(res["location"], limit - 1)
88
- else nil
87
+ when Net::HTTPRedirection then http_get(res['location'], limit - 1)
89
88
  end
90
89
  rescue StandardError => e
91
90
  warn " error fetching #{url}: #{e.message}"
@@ -93,12 +92,12 @@ module ReferenceFetch
93
92
  end
94
93
 
95
94
  def self.project_root
96
- File.expand_path("..", __dir__)
95
+ File.expand_path('..', __dir__)
97
96
  end
98
97
  end
99
98
 
100
99
  namespace :reference do
101
- desc "Download all xml-cml.org reference material into reference-docs/. Idempotent."
100
+ desc 'Download all xml-cml.org reference material into reference-docs/. Idempotent.'
102
101
  task :fetch do
103
102
  ReferenceFetch.fetch_all
104
103
  end