diff --git a/.gitignore b/.gitignore
index 1e25594542..fbf3fd897e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Hidden files
.*
!.github
+!.readthedocs.yaml
# Python byte / compiled / optimized
*.py[cod]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000000..fe110f1895
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,12 @@
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.12"
+ commands:
+ - pip install -r requirements.txt
+ - make prepare
+ - make html
+ - mkdir -p $READTHEDOCS_OUTPUT/html
+ - cp -r build/manual/build/html/* $READTHEDOCS_OUTPUT/html/
diff --git a/applications/NXxas.nxdl.xml b/applications/NXxas.nxdl.xml
index f076e9bb42..10ef59dfd6 100644
--- a/applications/NXxas.nxdl.xml
+++ b/applications/NXxas.nxdl.xml
@@ -2,9 +2,9 @@
-
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://definition.nexusformat.org/nxdl/3.1 ../nxdl.xsd ">
-
- The symbol(s) listed here will be used below to coordinate datasets with the same shape.
-
-
- Number of points
+
+ Number of energy data points
+
+
+ Number of raw data channels
-
+
- This is an application definition for raw data from an X-ray absorption spectroscopy experiment.
-
- This is essentially a scan on energy versus incoming/
- absorbed beam.
+ Application definition for X-ray absorption spectroscopy (XAS).
+
+ This definition contains the common fields shared by all XAS
+ measurements: energy axis, processed intensity, element, edge,
+ and sample information.
+
+ The measurement mode (transmission, fluorescence yield, electron
+ yield, HERFD, etc.) is described by an optional ``mode`` group
+ whose NeXus type is one of the mode base classes (NXtrans,
+ NXtfy, NXpfy, NXherfd, NXtey, NXpey).
+ The mode group holds all mode-specific raw data, detectors,
+ and instrument geometry.
-
-
- Official NeXus NXDL schema to which this file conforms
+ Official NeXus NXDL schema to which this file conforms.
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This data corresponds to the sample signal.
-
-
-
-
-
+
+ Excited element
+
+ Absorption edge
+
+
+ Specify if the data comes from a calculation.
+
+
+
+ The energy axis of the spectrum.
+
+
+
+
+
+
+
+ The processed absorption spectrum. The precise definition
+ depends on the acquisition mode (transmission, fluorescence
+ yield, electron yield, etc.)
+
+
+
+
+
+
+
+ The errors associated with the intensity of the spectrum.
+
+
+
+
+
- Descriptive name of sample
+ Descriptive name of the sample
-
-
-
- Count to a preset value based on either clock time (timer)
- or received monitor counts (monitor).
-
-
-
-
-
-
-
- preset value for time or monitor
-
-
- This field could be a link to ``/NXentry/NXinstrument/incoming_beam:NXdetector/data``
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Plot of the X-ray absorption intensity versus energy
+
+
-
-
-
-
- Detection method used for observing the sample absorption (pick one from the enumerated list and spell exactly)
-
-
-
-
-
-
-
-
+
+
+ Table like data structure common in the XAS domain.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/applications/NXxas_new.nxdl.xml b/applications/NXxas_new.nxdl.xml
deleted file mode 100644
index 1c2df512ee..0000000000
--- a/applications/NXxas_new.nxdl.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-
-
-
-
-
-
- The symbol(s) listed here will be used below to coordinate datasets with the same shape.
-
-
- Number of energy data points
-
-
- Number of electronic transitions
-
-
-
- This is an application definition for X-ray absorption spectroscopy.
-
-
-
- Official NeXus NXDL schema to which this file conforms. TODO: replace NXxas
-
-
-
-
-
-
-
-
- Excited element
-
-
- Absorption edge
-
-
- Specify if the data commes from a calculation
-
-
- TODO
-
-
-
-
-
- TODO
-
-
-
-
-
- TODO
-
-
-
-
-
-
- Descriptive name of the sample
-
-
-
-
- Description on how :ref:`energy </NXxas_new/ENTRY/energy-field>`
- and :ref:`intensity </NXxas_new/ENTRY/intensity-field>` were obtained
- from the raw data.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- spacing between crystal planes of the reflection
-
-
- Type or material of monochromating substance (Si, Ge, Multilayer).
-
-
- Miller indices (hkl) values of nominal reflection
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- XAS intensity versus energy plot
-
-
-
-
-
- Table like data structure common in the XAS domain.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/base_classes/NXherfd.nxdl.xml b/base_classes/NXherfd.nxdl.xml
new file mode 100644
index 0000000000..f8dfd10870
--- /dev/null
+++ b/base_classes/NXherfd.nxdl.xml
@@ -0,0 +1,523 @@
+
+
+
+
+
+
+ Number of energy data points
+
+
+
+ High-energy resolution fluorescence detection (HERFD) is a particular case
+ of partial fluorescence yield measured with a crystal analyzer
+ spectrometer with an energy bandwidth of approximately 1-2 eV.
+
+ The HERFD spectrum corresponds to a constant-emission-energy cut
+ through the Resonant Inelastic X-ray Scattering (RIXS) plane.
+ The spectral shape depends on the emission energy, making the
+ emission line and emission energy mandatory metadata.
+
+ .. math:: \mu(E) \propto I_f/I_0
+
+ The spectrometer uses Rowland circle geometry (Johann or Johansson
+ type). Multiple crystal analyzers may be arranged at different
+ horizontal angles around the sample to increase solid angle coverage.
+
+
+
+ The emission line at which the HERFD spectrum is measured.
+
+
+
+
+ The emission energy at which the spectrometer is set.
+
+
+
+
+ Beamline coordinate system with the sample at the origin:
+ x along the beam, y horizontal, z opposite to gravity.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Should point to
+ ``transformations/beam``.
+
+
+
+
+ Two rotations relating the beamline frame to
+ the NeXus/McStas laboratory frame, plus direction
+ vectors labeling beam and gravity.
+
+
+
+ Direction of the incident beam in the beamline
+ coordinate system.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Direction of gravity in the beamline coordinate
+ system.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Active rotation moving gravity from the
+ beamline direction (-z) to the McStas direction (-y).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Active rotation moving the beam from the
+ beamline direction (+x) to the McStas direction (+z).
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Should point to ``.`` (the McStas laboratory
+ frame).
+
+
+
+
+
+
+ The incident X-ray beam.
+
+
+ Energy of the incident beam at the sample position.
+
+
+
+
+ Should point to
+ ``beam/transformations/beam_direction``.
+
+
+
+
+
+ Beam direction in the beamline coordinate
+ system. The beam travels along +x.
+
+
+
+
+
+
+
+
+ Should point to the beamline coordinate system
+ or to the sample.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Detector measuring the incident beam intensity
+ :math:`I_0`, positioned upstream of the sample along
+ the beam direction.
+
+
+
+
+
+
+
+
+ Should point to
+ ``i0/transformations/i0_distance``.
+
+
+
+
+
+ Distance from the sample to the I0 detector,
+ measured upstream along the beam (negative x
+ direction in the beamline frame).
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Should point to the sample.
+
+
+
+
+
+
+
+ Crystal analyzer on the Rowland circle. For
+ multi-crystal spectrometers, use one group per
+ crystal (e.g. ``analyzer1``, ``analyzer2``).
+
+
+
+ Type or material of the analyzer crystal
+ (Si, Ge, etc.).
+
+
+
+
+ Miller indices (hkl) values of the nominal
+ reflection.
+
+
+
+
+
+
+
+ The spacing between crystal planes of the
+ reflection.
+
+
+
+
+ Bragg angle :math:`\theta_B` of the nominal
+ reflection.
+
+
+
+
+ Bending radius of the spherically bent crystal
+ analyzer. In Johann geometry this is :math:`2R_R`
+ (twice the Rowland radius).
+
+
+
+
+ Radius of the Rowland circle :math:`R_R`. The
+ sample, crystal center, and detector focus all
+ lie on this circle.
+
+
+
+
+ The energy bandwidth or resolution of the crystal
+ analyzer.
+
+
+
+
+ The type of crystal analyzer geometry.
+
+
+
+
+
+
+
+
+ Diameter of the crystal analyzer wafer.
+
+
+
+
+ Should point to the last transformation in the
+ chain, i.e.
+ ``transformations/analyzer_distance``.
+
+
+
+
+ Transformation chain placing the analyzer relative
+ to the sample: azimuthal angle, polar angle,
+ then distance.
+
+
+
+ Sample-to-analyzer distance.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Polar angle of the analyzer in the vertical
+ Rowland plane. Elevation from the horizontal
+ beam plane to the sample-analyzer direction.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Azimuthal (horizontal) angle of the
+ spectrometer arm from the incident beam
+ direction. Rotation around the vertical
+ z-axis. Typically around 90 degrees to
+ minimize elastic scattering.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Should point to the sample.
+
+
+
+
+
+
+
+ Detector measuring the fluorescence intensity
+ :math:`I_f` diffracted by the crystal analyzer(s).
+
+
+
+
+
+
+
+
+ Should point to the last transformation in the
+ chain, i.e.
+ ``transformations/detector_distance``.
+
+
+
+
+ Transformation chain placing the detector relative
+ to the sample: azimuthal angle, polar angle,
+ then distance.
+
+
+
+ Distance from the sample to the detector.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Polar angle of the detector in the vertical
+ Rowland plane.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Azimuthal (horizontal) angle of the detector
+ from the incident beam direction. Should
+ match the analyzer azimuthal angle for
+ on-Rowland focusing.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Should point to the sample.
+
+
+
+
+
+
+
+ Description of how the intensity was obtained from
+ the raw detector data (i0, if).
+
+
+ Name of the program used for processing.
+
+
+ Version of the program used for processing.
+
+
+ Date and time of processing.
+
+
+
diff --git a/base_classes/NXpey.nxdl.xml b/base_classes/NXpey.nxdl.xml
new file mode 100644
index 0000000000..ba6dd76951
--- /dev/null
+++ b/base_classes/NXpey.nxdl.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+ Number of energy data points
+
+
+
+ Partial electron yield (PEY) mode XAS. Electrons above a certain
+ kinetic energy threshold are collected. A retarding voltage is
+ applied to discriminate against low-energy secondary electrons:
+
+ .. math:: \mu(E) \propto I_{ey}/I_0
+
+
+
+ Detector measuring the incident beam intensity
+ :math:`I_0`.
+
+
+
+
+
+
+
+
+
+ Detector measuring the partial electron yield
+ :math:`I_{ey}`.
+
+
+
+
+
+
+
+
+ The retarding voltage (bias) applied to select
+ electrons above a kinetic energy threshold.
+
+
+
+
+
+ Description of how the intensity was obtained from
+ the raw detector data (i0, iey).
+
+
+ Name of the program used for processing.
+
+
+ Version of the program used for processing.
+
+
+ Date and time of processing.
+
+
+
diff --git a/base_classes/NXpfy.nxdl.xml b/base_classes/NXpfy.nxdl.xml
new file mode 100644
index 0000000000..afaab464a3
--- /dev/null
+++ b/base_classes/NXpfy.nxdl.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+ Number of energy data points
+
+
+
+ Partial fluorescence yield (PFY) mode XAS. An energy-dispersive
+ detector selects a portion of the fluorescence signal around a
+ chosen emission line:
+
+ .. math:: \mu(E) \propto I_f/I_0
+
+
+
+ The emission line(s) selected in the ROI for the partial
+ fluorescence yield measurement.
+
+
+
+
+ Detector measuring the incident beam intensity
+ :math:`I_0`.
+
+
+
+
+
+
+
+
+
+ Energy-dispersive fluorescence detector. The ``data``
+ field holds the fluorescence intensity integrated
+ over the ROI, corrected for dead-time.
+
+
+
+
+
+
+
+ Dead-time correction constant.
+
+
+
+ Detector live time per energy point.
+
+
+
+
+
+
+
+
+ Description of how the intensity was obtained from
+ the raw detector data (i0, if), including dead-time
+ correction and any self-absorption correction applied.
+
+
+ Name of the program used for processing.
+
+
+ Version of the program used for processing.
+
+
+ Date and time of processing.
+
+
+
diff --git a/base_classes/NXtey.nxdl.xml b/base_classes/NXtey.nxdl.xml
new file mode 100644
index 0000000000..c22bef5eee
--- /dev/null
+++ b/base_classes/NXtey.nxdl.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ Number of energy data points
+
+
+
+ Total electron yield (TEY) mode XAS. The drain current or total
+ electron current is proportional to the absorption coefficient:
+
+ .. math:: \mu(E) \propto I_{ey}/I_0
+
+ TEY is inherently surface-sensitive because electrons are readily
+ absorbed by most materials, limiting the probing depth to a few
+ nanometers.
+
+
+
+ Detector measuring the incident beam intensity
+ :math:`I_0`.
+
+
+
+
+
+
+
+
+
+ Detector measuring the total electron yield
+ :math:`I_{ey}` (drain current).
+
+
+
+
+
+
+
+
+
+ Description of how the intensity was obtained from
+ the raw detector data (i0, iey).
+
+
+ Name of the program used for processing.
+
+
+ Version of the program used for processing.
+
+
+ Date and time of processing.
+
+
+
diff --git a/base_classes/NXtfy.nxdl.xml b/base_classes/NXtfy.nxdl.xml
new file mode 100644
index 0000000000..073216892d
--- /dev/null
+++ b/base_classes/NXtfy.nxdl.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+ Number of energy data points
+
+
+
+ Total fluorescence yield (TFY) mode XAS. The absorption coefficient
+ is proportional to the ratio of the total fluorescence intensity
+ and the incident beam intensity:
+
+ .. math:: \mu(E) \propto I_f/I_0
+
+
+
+ Detector measuring the incident beam intensity
+ :math:`I_0`.
+
+
+
+
+
+
+
+
+
+ Detector measuring the total fluorescence emission
+ :math:`I_f`.
+
+
+
+
+
+
+
+
+
+ Description of how the intensity was obtained from
+ the raw detector data (i0, if).
+
+
+ Name of the program used for processing.
+
+
+ Version of the program used for processing.
+
+
+ Date and time of processing.
+
+
+
diff --git a/base_classes/NXtrans.nxdl.xml b/base_classes/NXtrans.nxdl.xml
new file mode 100644
index 0000000000..346b06cf2f
--- /dev/null
+++ b/base_classes/NXtrans.nxdl.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+ Number of energy data points
+
+
+
+ Transmission mode XAS. The absorption coefficient is given by
+ the Beer-Lambert law:
+
+ .. math:: \mu(E)t = -\ln(I/I_0)
+
+ where :math:`I` is the intensity of the transmitted beam, :math:`I_0` is
+ the intensity of the incident beam, and :math:`t` is the thickness of the
+ sample.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The spacing between crystal planes of the reflection
+
+
+
+ Type or material of monochromating substance
+ (Si, Ge, Multilayer).
+
+
+
+ Miller indices (hkl) values of nominal reflection
+
+
+
+
+
+
+
+
+ Detector measuring the incident beam intensity
+ :math:`I_0`.
+
+
+
+
+
+
+
+
+
+ Detector measuring the transmitted beam intensity
+ :math:`I`.
+
+
+
+
+
+
+
+
+
+ Description of how the intensity was obtained from
+ the raw detector data (i0, itrans).
+
+
+ Name of the program used for processing.
+
+
+ Version of the program used for processing.
+
+
+ Date and time of processing.
+
+
+
diff --git a/base_classes/NXxas_mode.nxdl.xml b/base_classes/NXxas_mode.nxdl.xml
deleted file mode 100644
index 02da979ea5..0000000000
--- a/base_classes/NXxas_mode.nxdl.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-
-
-
-
- XAS measurement mode
-
-
- X-ray absorption spectroscopy (XAS) is a technique that measures the absorption coefficient :math:`\mu(E)` of a material as a function of energy.
-
- The name of the XAS mode indicates the type of process being monitored to obtain the spectrum. Below is a description of the available modes, with emphasis on the expected values for the `intensity` and `monitor` fields.
-
- 1. Transmission
-
- The absorption coefficient is obtained by measuring the intensity of the incident :math:`I_0` and transmitted beam :math:`I`.
-
- .. math::
- \mu(E) = -\ln(I/I_0)
-
- 2. Total fluorescence yield (TFY)
-
- The absorption coefficient is obtained by measuring the intensity of the emitted fluorescence :math:`I_f` and the incident beam :math:`I_0`.
-
- .. math::
- \mu(E) \propto I_f/I_0
-
- 3. Partial fluorescence yield (PFY)
-
- 4. Inverse partial fluorescence yield (IPFY)
-
- 5. High-energy resolution fluorescence detection (HERFD)
-
- 6. Total electron yield (TEY)
-
- 7. Partial electron yield (PEY)
-
- 8. Electron energy loss (EELS)
-
- 9. X-ray Raman Scattering (XRS)
-
- 10. Diffraction Anomalous Fine Structure (DAFS)
-
- 11. X-ray Excited Optical Luminescence (XEOL)
-
- 12. Grazing Angle Reflection Extended X-ray Absorption Fine Structure (ReflEXAFS)
-
- 13. Other
-
-
- -
-
- Transmission
-
-
- -
-
- Total Fluorescence Yield
-
-
- -
-
- Partial Fluorescence Yield
-
-
- -
-
- Inverse Partial Fluorescence Yield
-
-
- -
-
- High Energy Resolution Fluorescence Detected
-
-
- -
-
- Total Electron Yield
-
-
- -
-
- Partial Electron Yield
-
-
- -
-
- Electron Energy Loss
-
-
- -
-
- X-ray Raman Scattering
-
-
- -
-
- Diffraction Anomalous Fine Structure
-
-
- -
-
- X-ray Excited Optical Luminescence
-
-
- -
-
- Grazing Angle Reflection Extended X-ray Absorption Fine Structure
-
-
- -
-
- Other
-
-
-
-
-
-
- Collection of emission lines detected or used in this measurement.
-
-
-
-
-
diff --git a/dev_tools/docs/nxdl.py b/dev_tools/docs/nxdl.py
index 5b3dc64622..bd8e98ccf2 100644
--- a/dev_tools/docs/nxdl.py
+++ b/dev_tools/docs/nxdl.py
@@ -364,7 +364,7 @@ def _get_required_or_optional_text(self, node):
:returns: formatted text
"""
tag = node.tag.split("}")[-1]
- if tag in ("field", "group"):
+ if tag in ("field", "group", "choice"):
optional_default = not self._use_application_defaults
optional = node.get("optional", optional_default) in (True, "true", "1", 1)
recommended = node.get("recommended", None) in (True, "true", "1", 1)
@@ -624,91 +624,144 @@ def _print_full_tree(self, ns, parent, name, indent, parent_path):
:param indent: to keep track of indentation level
:param parent_path: NX class path of parent nodes
"""
- for node in parent.xpath("nx:field", namespaces=ns):
- name = node.get("name")
- formatted_name = get_rst_formatted_name(node)
- index_name = name
- dims = self._analyze_dimensions(ns, node)
-
- optional_text = self._get_required_or_optional_text(node)
- self._print(f"{indent}{self._hyperlink_target(parent_path, name, 'field')}")
- self._print(f"{indent}.. index:: {index_name} (field)\n")
- self._print(
- f"{indent}{formatted_name}: "
- f"{optional_text}"
- f"{self._format_type(node)}"
- f"{dims}"
- f"{self._format_units(node)}"
- f" {self.get_first_parent_ref(f'{parent_path}/{name}', 'field')}"
- "\n"
- )
+ # Process children in document order to preserve XML ordering.
+ for node in parent.xpath("nx:field|nx:group|nx:choice|nx:link", namespaces=ns):
+ tag = node.tag.split("}")[-1]
+
+ if tag == "field":
+ name = node.get("name")
+ formatted_name = get_rst_formatted_name(node)
+ index_name = name
+ dims = self._analyze_dimensions(ns, node)
+
+ optional_text = self._get_required_or_optional_text(node)
+ self._print(
+ f"{indent}{self._hyperlink_target(parent_path, name, 'field')}"
+ )
+ self._print(f"{indent}.. index:: {index_name} (field)\n")
+ self._print(
+ f"{indent}{formatted_name}: "
+ f"{optional_text}"
+ f"{self._format_type(node)}"
+ f"{dims}"
+ f"{self._format_units(node)}"
+ f" {self.get_first_parent_ref(f'{parent_path}/{name}', 'field')}"
+ "\n"
+ )
- self._print_if_deprecated(ns, node, indent + self._INDENTATION_UNIT)
- self._print_doc_enum(indent, ns, node)
+ self._print_if_deprecated(ns, node, indent + self._INDENTATION_UNIT)
+ self._print_doc_enum(indent, ns, node)
+
+ for subnode in node.xpath("nx:attribute", namespaces=ns):
+ optional = self._get_required_or_optional_text(subnode)
+ self._print_attribute(
+ ns,
+ "field",
+ subnode,
+ optional,
+ indent + self._INDENTATION_UNIT,
+ parent_path + "/" + name,
+ )
- for subnode in node.xpath("nx:attribute", namespaces=ns):
- optional = self._get_required_or_optional_text(subnode)
- self._print_attribute(
- ns,
- "field",
- subnode,
- optional,
- indent + self._INDENTATION_UNIT,
- parent_path + "/" + name,
+ elif tag == "group":
+ name = node.get("name", "")
+ formatted_name = get_rst_formatted_name(node)
+ typ = node.get("type", "untyped (this is an error; please report)")
+
+ optional_text = self._get_required_or_optional_text(node)
+ if typ.startswith("NX"):
+ if name == "":
+ name = typ.lstrip("NX").upper()
+ typ = f":ref:`{typ}`"
+ hTarget = self._hyperlink_target(parent_path, name, "group")
+ # target = hTarget.replace(".. _", "").replace(":\n", "")
+ # TODO: https://github.com/nexusformat/definitions/issues/1057
+ self._print(f"{indent}{hTarget}")
+ self._print(
+ f"{indent}{formatted_name}: {optional_text}{typ} "
+ f"{self.get_first_parent_ref(f'{parent_path}/{name}', 'group')}\n"
)
- for node in parent.xpath("nx:group", namespaces=ns):
- name = node.get("name", "")
- formatted_name = get_rst_formatted_name(node)
- typ = node.get("type", "untyped (this is an error; please report)")
-
- optional_text = self._get_required_or_optional_text(node)
- if typ.startswith("NX"):
- if name == "":
- name = typ.lstrip("NX").upper()
- typ = f":ref:`{typ}`"
- hTarget = self._hyperlink_target(parent_path, name, "group")
- # target = hTarget.replace(".. _", "").replace(":\n", "")
- # TODO: https://github.com/nexusformat/definitions/issues/1057
- self._print(f"{indent}{hTarget}")
- self._print(
- f"{indent}{formatted_name}: {optional_text}{typ} {self.get_first_parent_ref(f'{parent_path}/{name}', 'group')}\n"
- )
-
- self._print_if_deprecated(ns, node, indent + self._INDENTATION_UNIT)
- self._print_doc_enum(indent, ns, node)
+ self._print_if_deprecated(ns, node, indent + self._INDENTATION_UNIT)
+ self._print_doc_enum(indent, ns, node)
+
+ for subnode in node.xpath("nx:attribute", namespaces=ns):
+ optional = self._get_required_or_optional_text(subnode)
+ self._print_attribute(
+ ns,
+ "group",
+ subnode,
+ optional,
+ indent + self._INDENTATION_UNIT,
+ parent_path + "/" + name,
+ )
- for subnode in node.xpath("nx:attribute", namespaces=ns):
- optional = self._get_required_or_optional_text(subnode)
- self._print_attribute(
+ nodename = "%s/%s" % (name, node.get("type"))
+ self._print_full_tree(
ns,
- "group",
- subnode,
- optional,
+ node,
+ nodename,
indent + self._INDENTATION_UNIT,
parent_path + "/" + name,
)
- nodename = "%s/%s" % (name, node.get("type"))
- self._print_full_tree(
- ns,
- node,
- nodename,
- indent + self._INDENTATION_UNIT,
- parent_path + "/" + name,
- )
+ elif tag == "choice":
+ name = node.get("name", "")
+ hTarget = self._hyperlink_target(parent_path, name, "choice")
+ self._print(f"{indent}{hTarget}")
+ optional_text = self._get_required_or_optional_text(node).strip("() ")
+ self._print(
+ f"{indent}**{name}**: ({optional_text}) "
+ "Only one of the following groups may be present:\n"
+ )
+ self._print_doc_enum(indent, ns, node)
- for node in parent.xpath("nx:link", namespaces=ns):
- name = node.get("name")
- formatted_name = get_rst_formatted_name(node)
- self._print(f"{indent}{self._hyperlink_target(parent_path, name, 'link')}")
- self._print(
- f"{indent}{formatted_name}: "
- ":ref:`link` "
- f"(suggested target: ``{node.get('target')}``)"
- "\n"
- )
- self._print_doc_enum(indent, ns, node)
+ # Print each group option within the choice.
+ for subnode in node.xpath("nx:group", namespaces=ns):
+ subname = subnode.get("name", "")
+ typ = subnode.get(
+ "type", "untyped (this is an error; please report)"
+ )
+ if typ.startswith("NX"):
+ if subname == "":
+ subname = typ.lstrip("NX").upper()
+ typ_ref = f":ref:`{typ}`"
+ else:
+ typ_ref = typ
+ sub_indent = indent + self._INDENTATION_UNIT
+ subTarget = self._hyperlink_target(
+ parent_path + "/" + name, subname, "group"
+ )
+ self._print(f"{sub_indent}{subTarget}")
+ self._print(f"{sub_indent}**{subname}**: {typ_ref}\n")
+ self._print_doc_enum(sub_indent, ns, subnode)
+
+ # Recursively print any content within this group option.
+ nodename = "%s/%s" % (subname, subnode.get("type"))
+ self._print_full_tree(
+ ns,
+ subnode,
+ nodename,
+ sub_indent + self._INDENTATION_UNIT,
+ parent_path + "/" + name + "/" + subname,
+ )
+
+ elif tag == "link":
+ name = node.get("name")
+ formatted_name = get_rst_formatted_name(node)
+ self._print(
+ f"{indent}{self._hyperlink_target(parent_path, name, 'link')}"
+ )
+ self._print(
+ f"{indent}{formatted_name}: "
+ ":ref:`link` "
+ f"(suggested target: ``{node.get('target')}``)"
+ "\n"
+ )
+ self._print_doc_enum(indent, ns, node)
+
+ else:
+ raise ValueError(f"Unknown node type: {tag}")
def _print(self, *args, end="\n"):
# TODO: change instances of \t to proper indentation
diff --git a/dev_tools/tests/test_nxdl_utils.py b/dev_tools/tests/test_nxdl_utils.py
index 96aa90db26..666337a3f6 100644
--- a/dev_tools/tests/test_nxdl_utils.py
+++ b/dev_tools/tests/test_nxdl_utils.py
@@ -1,220 +1,220 @@
-"""This is a code that performs several tests on nexus tool"""
-
-from pathlib import Path
-
-import lxml.etree as ET
-import pytest
-
-from ..utils import nxdl_utils as nexus
-
-
-def test_get_nexus_classes_units_attributes():
- """Check the correct parsing of a separate list for:
- Nexus classes (base_classes)
- Nexus units (memberTypes)
- Nexus attribute type (primitiveTypes)
- the tested functions can be found in nexus.py file"""
-
- # Test 1
- nexus_classes_list = nexus.get_nx_classes()
-
- assert "NXbeam" in nexus_classes_list
-
- # Test 2
- nexus_units_list = nexus.get_nx_units()
- assert "NX_TEMPERATURE" in nexus_units_list
-
- # Test 3
- nexus_attribute_list = nexus.get_nx_attribute_type()
- assert "NX_FLOAT" in nexus_attribute_list
-
-
-def test_get_node_at_nxdl_path():
- """Test to verify if we receive the right XML element for a given NXDL path"""
- local_dir = Path(__file__).resolve().parent
- nxdl_file_path = local_dir / "NXtest.nxdl.xml"
- elem = ET.parse(nxdl_file_path).getroot()
- node = nexus.get_node_at_nxdl_path("/ENTRY/NXODD_name", elem=elem)
- assert node.attrib["type"] == "NXdata"
- assert node.attrib["name"] == "NXODD_name"
-
- node = nexus.get_node_at_nxdl_path("/ENTRY/NXODD_name/float_value", elem=elem)
- assert node.attrib["type"] == "NX_FLOAT"
- assert node.attrib["name"] == "float_value"
-
- node = nexus.get_node_at_nxdl_path(
- "/ENTRY/NXODD_name/AXISNAME/long_name", elem=elem
- )
- assert node.attrib["name"] == "long_name"
-
- nxdl_file_path = local_dir / "../../contributed_definitions/NXiv_temp.nxdl.xml"
-
- elem = ET.parse(nxdl_file_path).getroot()
- node = nexus.get_node_at_nxdl_path(
- "/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller", elem=elem
- )
- assert node.attrib["name"] == "voltage_controller"
-
- node = nexus.get_node_at_nxdl_path(
- "/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller/calibration_time", elem=elem
- )
- assert node.attrib["name"] == "calibration_time"
-
-
-def test_get_inherited_nodes():
- """Test to verify if we receive the right XML element list for a given NXDL path."""
- local_dir = Path(__file__).resolve().parent
- nxdl_file_path = local_dir / "NXtest.nxdl.xml"
-
- elem = ET.parse(nxdl_file_path).getroot()
- _, _, elist = nexus.get_inherited_nodes(nxdl_path="/ENTRY/NXODD_name", elem=elem)
- assert len(elist) == 5
-
- nxdl_file_path = (
- local_dir.parent.parent / "contributed_definitions" / "NXiv_temp.nxdl.xml"
- )
-
- elem = ET.parse(nxdl_file_path).getroot()
- _, _, elist = nexus.get_inherited_nodes(
- nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT", elem=elem
- )
- assert len(elist) == 4
-
- _, _, elist = nexus.get_inherited_nodes(
- nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller", elem=elem
- )
- assert len(elist) == 6
-
- _, _, elist = nexus.get_inherited_nodes(
- nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller",
- nx_name="NXiv_temp",
- )
- assert len(elist) == 6
-
-
-@pytest.mark.parametrize(
- "hdf_name,concept_name, name_type, should_fit",
- [
- ("same_name", "same_name", "specified", True),
- ("same_name", "same_name", "any", True),
- ("same_name", "same_name", "partial", True),
- ("source_pump", "source", "specified", False),
- ("source_pump", "source", "any", True),
- ("source_pump", "source", "partial", False),
- ("source_pump", "sourceType", "specified", False),
- ("source_pump", "sourceType", "any", True),
- ("source_pump", "sourceType", "partial", False),
- ("source_pump", "sourceTYPE", "specified", False),
- ("source_pump", "sourceTYPE", "any", True),
- ("source_pump", "sourceTYPE", "partial", True),
- ("source pump", "sourceTYPE", "specified", False),
- ("source pump", "sourceTYPE", "any", False),
- ("source pump", "sourceTYPE", "partial", False),
- ("Name with some whitespaces in it", "ENTRY", "specified", False),
- ("Name with some whitespaces in it", "ENTRY", "any", False),
- ("Name with some whitespaces in it", "ENTRY", "partial", False),
- ("source", "sourceTYPE", "specified", False),
- ("source", "sourceTYPE", "any", True),
- ("source", "sourceTYPE", "partial", True),
- ("SOURCE", "SOURCE", "specified", True),
- ("SOURCE", "SOURCE", "any", True),
- ("SOURCE", "SOURCE", "partial", True),
- ("source123", "SOURCE", "specified", False),
- ("source123", "SOURCE", "any", True),
- ("source123", "SOURCE", "partial", True),
- ("1source", "SOURCE", "specified", False),
- ("1source", "SOURCE", "any", True),
- ("1source", "SOURCE", "partial", True),
- ("_source", "SOURCE", "specified", False),
- ("_source", "SOURCE", "any", True),
- ("_source", "SOURCE", "partial", True),
- ("angular_energy_resolution", "angularNresolution", "specified", False),
- ("angular_energy_resolution", "angularNresolution", "any", True),
- ("angular_energy_resolution", "angularNresolution", "partial", True),
- (".test", "TEST", "specified", False),
- (".test", "TEST", "any", False),
- (".test", "TEST", "partial", False),
- ],
-)
-def test_namefitting(hdf_name, concept_name, name_type, should_fit):
- """Test namefitting of nexus concept names"""
- name_any = name_type == "any"
- name_partial = name_type == "partial"
-
- if should_fit:
- assert nexus.get_nx_namefit(hdf_name, concept_name, name_any, name_partial) > -1
- else:
- assert (
- nexus.get_nx_namefit(hdf_name, concept_name, name_any, name_partial) == -1
- )
-
-
-@pytest.mark.parametrize(
- "hdf_name,concept_name, score",
- [
- ("test_name", "TEST_name", 9),
- ("te_name", "TEST_name", 7),
- ("my_other_name", "TEST_name", 5),
- ("test_name", "test_name", 18),
- ("test_other", "test_name", -1),
- ("my_fancy_yet_long_name", "my_SOME_name", 8),
- ("something", "XXXX", 0),
- ("something", "OTHER", 1),
- ],
-)
-def test_namefitting_scores(hdf_name, concept_name, score):
- """Test namefitting of nexus concept names"""
- assert nexus.get_nx_namefit(hdf_name, concept_name, name_partial=True) == score
-
-
-@pytest.mark.parametrize(
- "better_fit,better_ref,worse_fit,worse_ref",
- [
- ("sourcetype", "sourceTYPE", "source_pump", "sourceTYPE"),
- ("source_pump", "sourceTYPE", "source_pump", "TEST"),
- ],
-)
-def test_namefitting_precedence(better_fit, better_ref, worse_fit, worse_ref):
- """Test if namefitting follows proper precedence rules"""
-
- assert nexus.get_nx_namefit(
- better_fit, better_ref, name_partial=True
- ) > nexus.get_nx_namefit(worse_fit, worse_ref)
-
-
-@pytest.mark.parametrize(
- "string_obj, decode, expected",
- [
- # Test with lists of bytes and strings
- ([b"bytes", "string"], True, ["bytes", "string"]),
- ([b"bytes", "string"], False, [b"bytes", "string"]),
- ([b"bytes", b"more_bytes", "string"], True, ["bytes", "more_bytes", "string"]),
- (
- [b"bytes", b"more_bytes", "string"],
- False,
- [b"bytes", b"more_bytes", "string"],
- ),
- ([b"fixed", b"length", b"strings"], True, ["fixed", "length", "strings"]),
- ([b"fixed", b"length", b"strings"], False, [b"fixed", b"length", b"strings"]),
- # Test with nested lists
- ([[b"nested1"], [b"nested2"]], True, [["nested1"], ["nested2"]]),
- ([[b"nested1"], [b"nested2"]], False, [[b"nested1"], [b"nested2"]]),
- # Test with bytes
- (b"single", True, "single"),
- (b"single", False, b"single"),
- # Test with str
- ("single", True, "single"),
- ("single", False, "single"),
- # Test with int
- (123, True, 123),
- (123, False, 123),
- ],
-)
-def test_decode_or_not(string_obj, decode, expected):
- # Handle normal cases
- result = nexus.decode_or_not(elem=string_obj, decode=decode)
- if isinstance(expected, list):
- assert isinstance(result, list), f"Expected list, but got {type(result)}"
- # Handle all other cases
- else:
- assert result == expected, f"Failed for {string_obj} with decode={decode}"
+"""This is a code that performs several tests on nexus tool"""
+
+from pathlib import Path
+
+import lxml.etree as ET
+import pytest
+
+from ..utils import nxdl_utils as nexus
+
+
+def test_get_nexus_classes_units_attributes():
+ """Check the correct parsing of a separate list for:
+ Nexus classes (base_classes)
+ Nexus units (memberTypes)
+ Nexus attribute type (primitiveTypes)
+ the tested functions can be found in nexus.py file"""
+
+ # Test 1
+ nexus_classes_list = nexus.get_nx_classes()
+
+ assert "NXbeam" in nexus_classes_list
+
+ # Test 2
+ nexus_units_list = nexus.get_nx_units()
+ assert "NX_TEMPERATURE" in nexus_units_list
+
+ # Test 3
+ nexus_attribute_list = nexus.get_nx_attribute_type()
+ assert "NX_FLOAT" in nexus_attribute_list
+
+
+def test_get_node_at_nxdl_path():
+ """Test to verify if we receive the right XML element for a given NXDL path"""
+ local_dir = Path(__file__).resolve().parent
+ nxdl_file_path = local_dir / "NXtest.nxdl.xml"
+ elem = ET.parse(nxdl_file_path).getroot()
+ node = nexus.get_node_at_nxdl_path("/ENTRY/NXODD_name", elem=elem)
+ assert node.attrib["type"] == "NXdata"
+ assert node.attrib["name"] == "NXODD_name"
+
+ node = nexus.get_node_at_nxdl_path("/ENTRY/NXODD_name/float_value", elem=elem)
+ assert node.attrib["type"] == "NX_FLOAT"
+ assert node.attrib["name"] == "float_value"
+
+ node = nexus.get_node_at_nxdl_path(
+ "/ENTRY/NXODD_name/AXISNAME/long_name", elem=elem
+ )
+ assert node.attrib["name"] == "long_name"
+
+ nxdl_file_path = local_dir / "../../contributed_definitions/NXiv_temp.nxdl.xml"
+
+ elem = ET.parse(nxdl_file_path).getroot()
+ node = nexus.get_node_at_nxdl_path(
+ "/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller", elem=elem
+ )
+ assert node.attrib["name"] == "voltage_controller"
+
+ node = nexus.get_node_at_nxdl_path(
+ "/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller/calibration_time", elem=elem
+ )
+ assert node.attrib["name"] == "calibration_time"
+
+
+def test_get_inherited_nodes():
+ """Test to verify if we receive the right XML element list for a given NXDL path."""
+ local_dir = Path(__file__).resolve().parent
+ nxdl_file_path = local_dir / "NXtest.nxdl.xml"
+
+ elem = ET.parse(nxdl_file_path).getroot()
+ _, _, elist = nexus.get_inherited_nodes(nxdl_path="/ENTRY/NXODD_name", elem=elem)
+ assert len(elist) == 5
+
+ nxdl_file_path = (
+ local_dir.parent.parent / "contributed_definitions" / "NXiv_temp.nxdl.xml"
+ )
+
+ elem = ET.parse(nxdl_file_path).getroot()
+ _, _, elist = nexus.get_inherited_nodes(
+ nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT", elem=elem
+ )
+ assert len(elist) == 4
+
+ _, _, elist = nexus.get_inherited_nodes(
+ nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller", elem=elem
+ )
+ assert len(elist) == 6
+
+ _, _, elist = nexus.get_inherited_nodes(
+ nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller",
+ nx_name="NXiv_temp",
+ )
+ assert len(elist) == 6
+
+
+@pytest.mark.parametrize(
+ "hdf_name,concept_name, name_type, should_fit",
+ [
+ ("same_name", "same_name", "specified", True),
+ ("same_name", "same_name", "any", True),
+ ("same_name", "same_name", "partial", True),
+ ("source_pump", "source", "specified", False),
+ ("source_pump", "source", "any", True),
+ ("source_pump", "source", "partial", False),
+ ("source_pump", "sourceType", "specified", False),
+ ("source_pump", "sourceType", "any", True),
+ ("source_pump", "sourceType", "partial", False),
+ ("source_pump", "sourceTYPE", "specified", False),
+ ("source_pump", "sourceTYPE", "any", True),
+ ("source_pump", "sourceTYPE", "partial", True),
+ ("source pump", "sourceTYPE", "specified", False),
+ ("source pump", "sourceTYPE", "any", False),
+ ("source pump", "sourceTYPE", "partial", False),
+ ("Name with some whitespaces in it", "ENTRY", "specified", False),
+ ("Name with some whitespaces in it", "ENTRY", "any", False),
+ ("Name with some whitespaces in it", "ENTRY", "partial", False),
+ ("source", "sourceTYPE", "specified", False),
+ ("source", "sourceTYPE", "any", True),
+ ("source", "sourceTYPE", "partial", True),
+ ("SOURCE", "SOURCE", "specified", True),
+ ("SOURCE", "SOURCE", "any", True),
+ ("SOURCE", "SOURCE", "partial", True),
+ ("source123", "SOURCE", "specified", False),
+ ("source123", "SOURCE", "any", True),
+ ("source123", "SOURCE", "partial", True),
+ ("1source", "SOURCE", "specified", False),
+ ("1source", "SOURCE", "any", True),
+ ("1source", "SOURCE", "partial", True),
+ ("_source", "SOURCE", "specified", False),
+ ("_source", "SOURCE", "any", True),
+ ("_source", "SOURCE", "partial", True),
+ ("angular_energy_resolution", "angularNresolution", "specified", False),
+ ("angular_energy_resolution", "angularNresolution", "any", True),
+ ("angular_energy_resolution", "angularNresolution", "partial", True),
+ (".test", "TEST", "specified", False),
+ (".test", "TEST", "any", False),
+ (".test", "TEST", "partial", False),
+ ],
+)
+def test_namefitting(hdf_name, concept_name, name_type, should_fit):
+ """Test namefitting of nexus concept names"""
+ name_any = name_type == "any"
+ name_partial = name_type == "partial"
+
+ if should_fit:
+ assert nexus.get_nx_namefit(hdf_name, concept_name, name_any, name_partial) > -1
+ else:
+ assert (
+ nexus.get_nx_namefit(hdf_name, concept_name, name_any, name_partial) == -1
+ )
+
+
+@pytest.mark.parametrize(
+ "hdf_name,concept_name, score",
+ [
+ ("test_name", "TEST_name", 9),
+ ("te_name", "TEST_name", 7),
+ ("my_other_name", "TEST_name", 5),
+ ("test_name", "test_name", 18),
+ ("test_other", "test_name", -1),
+ ("my_fancy_yet_long_name", "my_SOME_name", 8),
+ ("something", "XXXX", 0),
+ ("something", "OTHER", 1),
+ ],
+)
+def test_namefitting_scores(hdf_name, concept_name, score):
+ """Test namefitting of nexus concept names"""
+ assert nexus.get_nx_namefit(hdf_name, concept_name, name_partial=True) == score
+
+
+@pytest.mark.parametrize(
+ "better_fit,better_ref,worse_fit,worse_ref",
+ [
+ ("sourcetype", "sourceTYPE", "source_pump", "sourceTYPE"),
+ ("source_pump", "sourceTYPE", "source_pump", "TEST"),
+ ],
+)
+def test_namefitting_precedence(better_fit, better_ref, worse_fit, worse_ref):
+ """Test if namefitting follows proper precedence rules"""
+
+ assert nexus.get_nx_namefit(
+ better_fit, better_ref, name_partial=True
+ ) > nexus.get_nx_namefit(worse_fit, worse_ref)
+
+
+@pytest.mark.parametrize(
+ "string_obj, decode, expected",
+ [
+ # Test with lists of bytes and strings
+ ([b"bytes", "string"], True, ["bytes", "string"]),
+ ([b"bytes", "string"], False, [b"bytes", "string"]),
+ ([b"bytes", b"more_bytes", "string"], True, ["bytes", "more_bytes", "string"]),
+ (
+ [b"bytes", b"more_bytes", "string"],
+ False,
+ [b"bytes", b"more_bytes", "string"],
+ ),
+ ([b"fixed", b"length", b"strings"], True, ["fixed", "length", "strings"]),
+ ([b"fixed", b"length", b"strings"], False, [b"fixed", b"length", b"strings"]),
+ # Test with nested lists
+ ([[b"nested1"], [b"nested2"]], True, [["nested1"], ["nested2"]]),
+ ([[b"nested1"], [b"nested2"]], False, [[b"nested1"], [b"nested2"]]),
+ # Test with bytes
+ (b"single", True, "single"),
+ (b"single", False, b"single"),
+ # Test with str
+ ("single", True, "single"),
+ ("single", False, "single"),
+ # Test with int
+ (123, True, 123),
+ (123, False, 123),
+ ],
+)
+def test_decode_or_not(string_obj, decode, expected):
+ # Handle normal cases
+ result = nexus.decode_or_not(elem=string_obj, decode=decode)
+ if isinstance(expected, list):
+ assert isinstance(result, list), f"Expected list, but got {type(result)}"
+ # Handle all other cases
+ else:
+ assert result == expected, f"Failed for {string_obj} with decode={decode}"
diff --git a/requirements.txt b/requirements.txt
index ba3eb8068a..2f45a3c7b4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,6 +16,6 @@ h5py
pytest
# Code style and auto-formatting
-black==26.1
+black==26.3.1
flake8==7.3
isort==7.0