Skip to content

cdm_data_loaders.converters.readers.xsd

Read XML Schema Definition (XSD) documents into the converter IR.

XsdReader dataclass

Build typed converter IR documents from XML Schema Definition (XSD) files.

Parameters:

Name Type Description Default
xml_tag str | None

Default target XML element tag name when not supplied to :meth:read.

None
max_recursion_depth int

Maximum times a recursive XsdComplexType may be expanded along an ancestor path.

DEFAULT_MAX_RECURSION_DEPTH
error_type type[ConversionError]

Base exception class raised when a construct cannot be converted.

ConversionError
error_by_code Mapping[str, type[ConversionError]]

Mapping from specification error code (E1-E9) to exception class.

frozendict()
Source code in src/cdm_data_loaders/converters/readers/xsd.py
@dataclass(frozen=True, slots=True)
class XsdReader:
    """Build typed converter IR documents from XML Schema Definition (XSD) files.

    :param xml_tag: Default target XML element tag name when not supplied to :meth:`read`.
    :type xml_tag: str | None
    :param max_recursion_depth: Maximum times a recursive ``XsdComplexType`` may be expanded along an ancestor path.
    :type max_recursion_depth: int
    :param error_type: Base exception class raised when a construct cannot be converted.
    :type error_type: type[ConversionError]
    :param error_by_code: Mapping from specification error code (``E1``-``E9``) to exception class.
    :type error_by_code: Mapping[str, type[ConversionError]]
    """

    xml_tag: str | None = None
    max_recursion_depth: int = DEFAULT_MAX_RECURSION_DEPTH
    error_type: type[ConversionError] = ConversionError
    error_by_code: Mapping[str, type[ConversionError]] = field(default_factory=frozendict)

    def __post_init__(self) -> None:
        """Validate reader configuration."""
        validate_max_recursion_depth(self.max_recursion_depth)

    def _raise_err(self, code: str, msg: str) -> NoReturn:
        """Raise the configured exception subclass for ``code`` or fall back to ``error_type``.

        :param code: Specification error code (``E1``-``E9``).
        :type code: str
        :param msg: Diagnostic error message.
        :type msg: str
        :raises ConversionError: Always raised with the resolved error class.
        """
        _raise_code(code, msg, error_type=self.error_type, error_by_code=self.error_by_code)

    def read(
        self,
        source: xmlschema.XMLSchema | str | Path,
        xml_tag: str | None = None,
        *,
        struct_cache: dict[tuple[int, bool], Any] | None = None,
    ) -> SchemaDocument:
        """Read an ``xmlschema.XMLSchema`` element into a ``SchemaDocument``.

        :param source: Loaded ``XMLSchema`` instance or filesystem path to an XSD file.
        :type source: xmlschema.XMLSchema | str | Path
        :param xml_tag: Target XML element name (local name or Clark notation). Defaults to ``self.xml_tag`` or the sole root element.
        :type xml_tag: str | None
        :param struct_cache: Optional cache sharing IR struct ``TypedNode``s across calls.
        :type struct_cache: dict[tuple[int, bool], Any] | None
        :return: Structural IR document with ``source="xsd"`` provenance.
        :rtype: SchemaDocument
        :raises ConversionError: If the schema or ``xml_tag`` violates ``E1``-``E9``.
        """
        schema = load_schema(source) if isinstance(source, str | Path) else source
        tag = xml_tag if xml_tag is not None else self.xml_tag
        if tag is None:
            roots = resolve_root_elements(schema, error_type=self.error_type, error_by_code=self.error_by_code)
            if len(roots) != 1 or roots[0].local_name is None:
                msg = "xml_tag must be specified when the XSD schema does not have a single root element"
                self._raise_err("E1", msg)
            tag = roots[0].local_name

        target_elem, _, ctx = _prepare_target_traversal(
            schema,
            tag,
            max_recursion_depth=self.max_recursion_depth,
            struct_cache=struct_cache,
            error_type=self.error_type,
            error_by_code=self.error_by_code,
        )
        root_struct = _build_element_struct_node(target_elem, tag, ctx)
        return _finish_element_doc(schema, target_elem, tag, root_struct)

    def read_at_level(
        self,
        source: xmlschema.XMLSchema | str | Path,
        level: int,
        *,
        root_tag: str | None = None,
    ) -> dict[str, SchemaDocument]:
        """Read all XML element types at depth ``level`` in an XSD schema into ``SchemaDocument``s.

        :param source: Loaded ``XMLSchema`` instance or filesystem path to an XSD file.
        :type source: xmlschema.XMLSchema | str | Path
        :param level: Non-negative depth relative to the root element(s).
        :type level: int
        :param root_tag: Optional explicit root element name to anchor the traversal.
        :type root_tag: str | None
        :return: Mapping of element local tag name to its ``SchemaDocument``.
        :rtype: dict[str, SchemaDocument]
        """
        schema = load_schema(source) if isinstance(source, str | Path) else source
        elements_at_level = resolve_elements_at_level(
            schema,
            level,
            root_tag=root_tag,
            error_type=self.error_type,
            error_by_code=self.error_by_code,
        )
        struct_cache: dict[tuple[int, bool], Any] = {}
        return {
            tag_name: _build_element_table_doc(
                schema,
                target_elem,
                tag_name,
                max_recursion_depth=self.max_recursion_depth,
                struct_cache=struct_cache,
                error_type=self.error_type,
                error_by_code=self.error_by_code,
            )
            for tag_name, target_elem in elements_at_level.items()
        }

__post_init__

__post_init__() -> None

Validate reader configuration.

Source code in src/cdm_data_loaders/converters/readers/xsd.py
def __post_init__(self) -> None:
    """Validate reader configuration."""
    validate_max_recursion_depth(self.max_recursion_depth)

read

read(
    source: XMLSchema | str | Path,
    xml_tag: str | None = None,
    *,
    struct_cache: dict[tuple[int, bool], Any] | None = None
) -> SchemaDocument

Read an xmlschema.XMLSchema element into a SchemaDocument.

Parameters:

Name Type Description Default
source XMLSchema | str | Path

Loaded XMLSchema instance or filesystem path to an XSD file.

required
xml_tag str | None

Target XML element name (local name or Clark notation). Defaults to self.xml_tag or the sole root element.

None
struct_cache dict[tuple[int, bool], Any] | None

Optional cache sharing IR struct TypedNodes across calls.

None

Returns:

Type Description
SchemaDocument

Structural IR document with source="xsd" provenance.

Raises:

Type Description
ConversionError

If the schema or xml_tag violates E1-E9.

Source code in src/cdm_data_loaders/converters/readers/xsd.py
def read(
    self,
    source: xmlschema.XMLSchema | str | Path,
    xml_tag: str | None = None,
    *,
    struct_cache: dict[tuple[int, bool], Any] | None = None,
) -> SchemaDocument:
    """Read an ``xmlschema.XMLSchema`` element into a ``SchemaDocument``.

    :param source: Loaded ``XMLSchema`` instance or filesystem path to an XSD file.
    :type source: xmlschema.XMLSchema | str | Path
    :param xml_tag: Target XML element name (local name or Clark notation). Defaults to ``self.xml_tag`` or the sole root element.
    :type xml_tag: str | None
    :param struct_cache: Optional cache sharing IR struct ``TypedNode``s across calls.
    :type struct_cache: dict[tuple[int, bool], Any] | None
    :return: Structural IR document with ``source="xsd"`` provenance.
    :rtype: SchemaDocument
    :raises ConversionError: If the schema or ``xml_tag`` violates ``E1``-``E9``.
    """
    schema = load_schema(source) if isinstance(source, str | Path) else source
    tag = xml_tag if xml_tag is not None else self.xml_tag
    if tag is None:
        roots = resolve_root_elements(schema, error_type=self.error_type, error_by_code=self.error_by_code)
        if len(roots) != 1 or roots[0].local_name is None:
            msg = "xml_tag must be specified when the XSD schema does not have a single root element"
            self._raise_err("E1", msg)
        tag = roots[0].local_name

    target_elem, _, ctx = _prepare_target_traversal(
        schema,
        tag,
        max_recursion_depth=self.max_recursion_depth,
        struct_cache=struct_cache,
        error_type=self.error_type,
        error_by_code=self.error_by_code,
    )
    root_struct = _build_element_struct_node(target_elem, tag, ctx)
    return _finish_element_doc(schema, target_elem, tag, root_struct)

read_at_level

read_at_level(
    source: XMLSchema | str | Path,
    level: int,
    *,
    root_tag: str | None = None
) -> dict[str, SchemaDocument]

Read all XML element types at depth level in an XSD schema into SchemaDocuments.

Parameters:

Name Type Description Default
source XMLSchema | str | Path

Loaded XMLSchema instance or filesystem path to an XSD file.

required
level int

Non-negative depth relative to the root element(s).

required
root_tag str | None

Optional explicit root element name to anchor the traversal.

None

Returns:

Type Description
dict[str, SchemaDocument]

Mapping of element local tag name to its SchemaDocument.

Source code in src/cdm_data_loaders/converters/readers/xsd.py
def read_at_level(
    self,
    source: xmlschema.XMLSchema | str | Path,
    level: int,
    *,
    root_tag: str | None = None,
) -> dict[str, SchemaDocument]:
    """Read all XML element types at depth ``level`` in an XSD schema into ``SchemaDocument``s.

    :param source: Loaded ``XMLSchema`` instance or filesystem path to an XSD file.
    :type source: xmlschema.XMLSchema | str | Path
    :param level: Non-negative depth relative to the root element(s).
    :type level: int
    :param root_tag: Optional explicit root element name to anchor the traversal.
    :type root_tag: str | None
    :return: Mapping of element local tag name to its ``SchemaDocument``.
    :rtype: dict[str, SchemaDocument]
    """
    schema = load_schema(source) if isinstance(source, str | Path) else source
    elements_at_level = resolve_elements_at_level(
        schema,
        level,
        root_tag=root_tag,
        error_type=self.error_type,
        error_by_code=self.error_by_code,
    )
    struct_cache: dict[tuple[int, bool], Any] = {}
    return {
        tag_name: _build_element_table_doc(
            schema,
            target_elem,
            tag_name,
            max_recursion_depth=self.max_recursion_depth,
            struct_cache=struct_cache,
            error_type=self.error_type,
            error_by_code=self.error_by_code,
        )
        for tag_name, target_elem in elements_at_level.items()
    }

load_schema

load_schema(schema_path: str | Path) -> xmlschema.XMLSchema

Load an XML schema document into an XMLSchema object.

Parameters:

Name Type Description Default
schema_path str | Path

path to the schema file

required

Returns:

Type Description
xmlschema.XMLSchema

loaded schema

Source code in src/cdm_data_loaders/converters/readers/xsd.py
def load_schema(schema_path: str | Path) -> xmlschema.XMLSchema:
    """Load an XML schema document into an XMLSchema object.

    :param schema_path: path to the schema file
    :type schema_path: str | Path
    :return: loaded schema
    :rtype: xmlschema.XMLSchema
    """
    return xmlschema.XMLSchema(str(schema_path))

resolve_root_elements

resolve_root_elements(
    schema: XMLSchema,
    root_tag: str | None = None,
    *,
    error_type: type[ConversionError] = ConversionError,
    error_by_code: Mapping[
        str, type[ConversionError]
    ] = frozendict()
) -> list[XsdElement]

Resolve the level-0 root XsdElement declarations in an XSD schema.

When root_tag is omitted, root elements are deduced as global <xs:element> declarations that are not referenced as child elements inside any other complex type's content model (falling back to all global elements if all global elements participate in a mutual reference cycle).

Parameters:

Name Type Description Default
schema XMLSchema

loaded XMLSchema instance

required
root_tag str | None

optional explicit root element name (local name or Clark notation)

None
error_type type[ConversionError]

fallback exception class

ConversionError
error_by_code Mapping[str, type[ConversionError]]

mapping of specification error codes to exception classes

frozendict()

Returns:

Type Description
list[XsdElement]

list of root XsdElement declarations in schema order

Source code in src/cdm_data_loaders/converters/readers/xsd.py
def resolve_root_elements(
    schema: xmlschema.XMLSchema,
    root_tag: str | None = None,
    *,
    error_type: type[ConversionError] = ConversionError,
    error_by_code: Mapping[str, type[ConversionError]] = frozendict(),
) -> list[XsdElement]:
    """Resolve the level-0 root ``XsdElement`` declarations in an XSD schema.

    When ``root_tag`` is omitted, root elements are deduced as global ``<xs:element>`` declarations
    that are not referenced as child elements inside any other complex type's content model (falling
    back to all global elements if all global elements participate in a mutual reference cycle).

    :param schema: loaded XMLSchema instance
    :type schema: xmlschema.XMLSchema
    :param root_tag: optional explicit root element name (local name or Clark notation)
    :type root_tag: str | None
    :param error_type: fallback exception class
    :type error_type: type[ConversionError]
    :param error_by_code: mapping of specification error codes to exception classes
    :type error_by_code: Mapping[str, type[ConversionError]]
    :return: list of root XsdElement declarations in schema order
    :rtype: list[XsdElement]
    """
    if root_tag is not None:
        return [_resolve_target_element(schema, root_tag, error_type=error_type, error_by_code=error_by_code)]

    global_elements = [el for el in schema.elements.values() if isinstance(el, XsdElement)]
    if not global_elements:
        msg = "No global elements found in XSD schema"
        _raise_code("E1", msg, error_type=error_type, error_by_code=error_by_code)

    referenced_names = _collect_referenced_global_element_names(schema)
    unreferenced = [el for el in global_elements if el.name not in referenced_names]
    return unreferenced or global_elements

resolve_elements_at_level

resolve_elements_at_level(
    schema: XMLSchema,
    level: int,
    *,
    root_tag: str | None = None,
    error_type: type[ConversionError] = ConversionError,
    error_by_code: Mapping[
        str, type[ConversionError]
    ] = frozendict()
) -> dict[str, XsdElement]

Deduce all XsdElement declarations that can appear at depth level in an XSD schema.

Depth 0 corresponds to the root element(s); depth 1 corresponds to direct children of the root element(s); depth 2 corresponds to grandchildren of the root element(s), and so on.

Parameters:

Name Type Description Default
schema XMLSchema

loaded XMLSchema instance

required
level int

non-negative depth relative to the root element(s)

required
root_tag str | None

optional explicit root element name to anchor the traversal

None
error_type type[ConversionError]

fallback exception class

ConversionError
error_by_code Mapping[str, type[ConversionError]]

mapping of specification error codes to exception classes

frozendict()

Returns:

Type Description
dict[str, XsdElement]

mapping of element local tag name to its validated XsdElement declaration at level

Source code in src/cdm_data_loaders/converters/readers/xsd.py
def resolve_elements_at_level(
    schema: xmlschema.XMLSchema,
    level: int,
    *,
    root_tag: str | None = None,
    error_type: type[ConversionError] = ConversionError,
    error_by_code: Mapping[str, type[ConversionError]] = frozendict(),
) -> dict[str, XsdElement]:
    """Deduce all ``XsdElement`` declarations that can appear at depth ``level`` in an XSD schema.

    Depth ``0`` corresponds to the root element(s); depth ``1`` corresponds to direct children of
    the root element(s); depth ``2`` corresponds to grandchildren of the root element(s), and so on.

    :param schema: loaded XMLSchema instance
    :type schema: xmlschema.XMLSchema
    :param level: non-negative depth relative to the root element(s)
    :type level: int
    :param root_tag: optional explicit root element name to anchor the traversal
    :type root_tag: str | None
    :param error_type: fallback exception class
    :type error_type: type[ConversionError]
    :param error_by_code: mapping of specification error codes to exception classes
    :type error_by_code: Mapping[str, type[ConversionError]]
    :return: mapping of element local tag name to its validated XsdElement declaration at ``level``
    :rtype: dict[str, XsdElement]
    """
    if level < 0:
        msg = f"level must be >= 0, got {level}"
        raise ValueError(msg)

    current_elements = resolve_root_elements(
        schema,
        root_tag=root_tag,
        error_type=error_type,
        error_by_code=error_by_code,
    )
    for _ in range(level):
        current_elements = _advance_elements_one_level(
            schema,
            current_elements,
            error_type=error_type,
            error_by_code=error_by_code,
        )
        if not current_elements:
            break

    deduped = _group_and_deduplicate_elements(current_elements, error_type=error_type, error_by_code=error_by_code)
    if not deduped:
        msg = f"No XML elements found at level {level} in XSD schema"
        _raise_code("E1", msg, error_type=error_type, error_by_code=error_by_code)
    return deduped