Skip to content

Measurement.to_xml() writes invalid LuminousIntensity on FilterMeasurement (XSD violation) #25

Description

@Light-oneNL

Measurement.to_xml() in pygdtf/__init__.py writes the LuminousIntensity
attribute unconditionally whenever self.luminous_intensity is not None
(default 0.0 on read):

def to_xml(self):
    attrs = {}
    if self.physical is not None:
        attrs["Physical"] = f"{self.physical:.6f}"
    if self.luminous_intensity is not None:
        attrs["LuminousIntensity"] = f"{self.luminous_intensity:.6f}"
    if self.transmission is not None and (
        "Transmission" in self._attr_keys or self.transmission != 0
    ):
        attrs["Transmission"] = f"{self.transmission:.6f}"
    ...

This is correct for an EmitterMeasurement, which requires
LuminousIntensity. But the same Measurement class is also used for
FilterMeasurement, where the GDTF XSD explicitly forbids
LuminousIntensity and requires Transmission instead. Since
luminous_intensity defaults to 0.0 on read regardless of whether the
source XML had the attribute, every Filter/Measurement round-tripped
through pygdtf gains an invalid LuminousIntensity="0.000000".

Reproduction

Round-trip any GDTF file whose PhysicalDescriptions/Filters contain
Measurement elements (e.g. the Robe Robin Esprite / iSpiider / MegaPointe
files published on gdtf-share.com) through:

with pygdtf.FixtureType(path) as fixture:
    pygdtf.FixtureTypeWriter(fixture).write_gdtf(out_path)

then validate description.xml against gdtf.xsd. Result, once per
Filter/Measurement:

/GDTF/FixtureType/PhysicalDescriptions/Filters/Filter[N]/Measurement
    'LuminousIntensity' attribute not allowed for element

13, 3, and 17 such errors respectively on the three Robe files mentioned
above, on otherwise-valid, XSD-passing originals.

Root cause

Transmission already handles this correctly one line below, via
self._attr_keys — the set of attribute names actually present on the
source element, captured in _read_xml:

if self.transmission is not None and (
    "Transmission" in self._attr_keys or self.transmission != 0
):

LuminousIntensity should use the identical condition:

if self.luminous_intensity is not None and (
    "LuminousIntensity" in self._attr_keys or self.luminous_intensity != 0
):
    attrs["LuminousIntensity"] = f"{self.luminous_intensity:.6f}"

With this change, an EmitterMeasurement (where the source XML always has
LuminousIntensity) keeps writing it as before, while a
FilterMeasurement (where it's never in the source) correctly omits it.

Version

pygdtf 1.4.5 (PyPI).

Note

A related, lower-severity issue: Wheel/Slot round-trips gain a spurious
MediaFileName="" attribute even when the source slot had no media
(Resource(name="") created unconditionally on read, __init__.py
~r794-795, then written back unconditionally). XSD-valid but noisy — happy
to file that separately if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions