diff --git a/tests/missing_dspacing_angle.xdi b/tests/missing_dspacing_angle.xdi new file mode 100644 index 0000000..6ae4c98 --- /dev/null +++ b/tests/missing_dspacing_angle.xdi @@ -0,0 +1,22 @@ +# XDI/1.0 EDC/5.02 +# Beamline.name: X11A +# Column.1: angle deg +# Column.2: mutrans +# Element.edge: K +# Element.symbol: Cu +# Facility.energy: 2.584 GeV +# Facility.name: NSLS +# Facility.xray_source: bend magnet +# Mono.name: Si(111) +# Sample.name: Cu +# Sample.prep: Cu metal foil +# Sample.temperature: 10 K +# Scan.edge_energy: 8980.0 eV +# Scan.start_time: 1992-09-15T01:52:53 +# /// +# Cu foil, 10K — angle-abscissa fixture (missing Mono.d_spacing on purpose) +# --- +# angle mutrans + .1234500E+02 .1013661E+01 + .1234600E+02 .9949864E+00 + .1234700E+02 .9900679E+00 diff --git a/tests/test_MissingFields.py b/tests/test_MissingFields.py index 0ea160f..d01aa2c 100644 --- a/tests/test_MissingFields.py +++ b/tests/test_MissingFields.py @@ -18,5 +18,9 @@ def test_missing_element(self): self.assertIn("element", self.errors) self.assertEqual(2, len(self.errors["element"])) - def test_missing_mono(self): - self.assertIn("mono", self.errors) \ No newline at end of file + def test_mono_dspacing_not_required_for_energy_abscissa(self): + # missing_fields.xdi has Column.1: energy — per XDI/1.0 spec, + # Mono.d_spacing is only required when the abscissa is + # monochromator angle or encoder step count. So no mono error + # should fire despite Mono.d_spacing being absent. + self.assertNotIn("mono", self.errors) \ No newline at end of file diff --git a/tests/test_MonoDspacingConditional.py b/tests/test_MonoDspacingConditional.py new file mode 100644 index 0000000..92d9c2a --- /dev/null +++ b/tests/test_MonoDspacingConditional.py @@ -0,0 +1,49 @@ +"""XDI/1.0 spec: Mono.d_spacing is required only when the abscissa +(Column.1) is monochromator angle or encoder step count. For energy +or wavelength abscissae, Mono.d_spacing is optional. + +Ref: https://github.com/XraySpectroscopy/XAS-Data-Interchange/blob/master/specification/spec.md +""" +import os +import unittest +from xdi_validator import validate + + +HERE = os.path.dirname(__file__) + + +class TestMonoDspacingConditional(unittest.TestCase): + + def _validate(self, fname): + with open(os.path.join(HERE, fname), "r") as f: + errors, _data = validate(f) + return errors + + def test_angle_abscissa_missing_dspacing_errors(self): + errors = self._validate("missing_dspacing_angle.xdi") + self.assertIn("mono", errors, + "Column.1: angle → Mono.d_spacing must be required") + + def test_angle_abscissa_with_dspacing_ok(self): + errors = self._validate("valid_angle_dspacing.xdi") + self.assertNotIn("mono", errors, + "Column.1: angle with Mono.d_spacing present → no mono error") + + def test_energy_abscissa_missing_dspacing_ok(self): + # valid.xdi has Column.1: energy and Mono.d_spacing present — + # remove the d_spacing header at read-time (in-memory) and + # confirm no mono error fires for an energy abscissa. + import io + with open(os.path.join(HERE, "valid.xdi"), "r") as f: + src = f.read() + stripped = "\n".join( + line for line in src.splitlines() + if not line.lstrip("# ").startswith("Mono.d_spacing") + ) + errors, _ = validate(io.StringIO(stripped)) + self.assertNotIn("mono", errors, + "Column.1: energy without Mono.d_spacing → no mono error") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_WriteToFile.py b/tests/test_WriteToFile.py index a7d6564..e9cf367 100644 --- a/tests/test_WriteToFile.py +++ b/tests/test_WriteToFile.py @@ -14,6 +14,9 @@ def setUp(self): self.generate_xdi_result = write_xdi(self.data, os.path.dirname(__file__) + "/generated_valid.xdi" ) self.invalid_data = copy.deepcopy(self.data) self.invalid_data["element"].pop("symbol") + # Switch abscissa to angle so d_spacing becomes required per + # XDI/1.0 spec; then remove it to exercise the mono error path. + self.invalid_data["column"]["1"] = "angle deg" self.invalid_data["mono"].pop("d_spacing") diff --git a/tests/test_WriteToFileTwo.py b/tests/test_WriteToFileTwo.py index 626758b..261b6e1 100644 --- a/tests/test_WriteToFileTwo.py +++ b/tests/test_WriteToFileTwo.py @@ -14,6 +14,9 @@ def setUp(self): self.generate_xdi_result = write_xdi(self.data, os.path.dirname(__file__) + "/generated_valid.xdi" ) self.invalid_data = copy.deepcopy(self.data) self.invalid_data["element"].pop("symbol") + # Switch abscissa to angle so d_spacing becomes required per + # XDI/1.0 spec; then remove it to exercise the mono error path. + self.invalid_data["column"]["1"] = "angle deg" self.invalid_data["mono"].pop("d_spacing") diff --git a/tests/valid_angle_dspacing.xdi b/tests/valid_angle_dspacing.xdi new file mode 100644 index 0000000..60e3d25 --- /dev/null +++ b/tests/valid_angle_dspacing.xdi @@ -0,0 +1,23 @@ +# XDI/1.0 EDC/5.02 +# Beamline.name: X11A +# Column.1: angle deg +# Column.2: mutrans +# Element.edge: K +# Element.symbol: Cu +# Facility.energy: 2.584 GeV +# Facility.name: NSLS +# Facility.xray_source: bend magnet +# Mono.d_spacing: 3.135301 +# Mono.name: Si(111) +# Sample.name: Cu +# Sample.prep: Cu metal foil +# Sample.temperature: 10 K +# Scan.edge_energy: 8980.0 eV +# Scan.start_time: 1992-09-15T01:52:53 +# /// +# Cu foil, 10K — angle abscissa with Mono.d_spacing present +# --- +# angle mutrans + .1234500E+02 .1013661E+01 + .1234600E+02 .9949864E+00 + .1234700E+02 .9900679E+00 diff --git a/xdi_validator/XDI_validator.py b/xdi_validator/XDI_validator.py index 4d80700..845b857 100644 --- a/xdi_validator/XDI_validator.py +++ b/xdi_validator/XDI_validator.py @@ -408,7 +408,6 @@ def get_schema() -> dict: "name": {"type": "string"}, "d_spacing": {"type": "number"}, }, - "required": ["d_spacing"], }, "detector": { "description": "Tags related to the details of the photon detection system", @@ -566,6 +565,37 @@ def get_schema() -> dict: }, }, "required": ["version", "subversion", "element", "mono"], + "allOf": [ + { + "$comment": ( + "XDI/1.0 spec: 'The Mono.d_spacing header field must be " + "specified if the abscissa is conveyed as monochromator " + "angle.' d_spacing is required only when Column.1 " + "(the abscissa) is angle or encoder; for energy or " + "wavelength abscissae, d_spacing is optional. " + "See https://github.com/XraySpectroscopy/" + "XAS-Data-Interchange/blob/master/specification/spec.md" + ), + "if": { + "properties": { + "column": { + "properties": { + "1": { + "pattern": "^(angle|encoder)(\\s|$)" + } + }, + "required": ["1"] + } + }, + "required": ["column"] + }, + "then": { + "properties": { + "mono": {"required": ["d_spacing"]} + } + } + } + ], } return schemadef