diff --git a/basyx/aas/adapter/xml/xml_deserialization.py b/basyx/aas/adapter/xml/xml_deserialization.py index e42a9a6f6..f8136a64b 100644 --- a/basyx/aas/adapter/xml/xml_deserialization.py +++ b/basyx/aas/adapter/xml/xml_deserialization.py @@ -49,11 +49,12 @@ import enum from typing import Any, Callable, Dict, IO, Iterable, Optional, Set, Tuple, Type, TypeVar -from .._generic import XML_NS_AAS, MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, \ +from .._generic import XML_NS_MAP, XML_NS_AAS, MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, \ ENTITY_TYPES_INVERSE, IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, \ REFERENCE_TYPES_INVERSE, DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE NS_AAS = XML_NS_AAS +REQUIRED_NAMESPACES: Set[str] = {XML_NS_MAP["aas"]} logger = logging.getLogger(__name__) @@ -97,7 +98,7 @@ def _element_pretty_identifier(element: etree.Element) -> str: Returns a pretty element identifier for a given XML element. If the prefix is known, the namespace in the element tag is replaced by the prefix. - If additionally also the sourceline is known, is is added as a suffix to name. + If additionally also the sourceline is known, it is added as a suffix to name. For example, instead of "{https://admin-shell.io/aas/3/0}assetAdministrationShell" this function would return "aas:assetAdministrationShell on line $line", if both, prefix and sourceline, are known. @@ -106,7 +107,11 @@ def _element_pretty_identifier(element: etree.Element) -> str: """ identifier = element.tag if element.prefix is not None: - identifier = element.prefix + ":" + element.tag.split("}")[1] + # Only replace the namespace by the prefix if it matches our known namespaces, + # so the replacement by the prefix doesn't mask errors such as incorrect namespaces. + namespace, tag = element.tag.split("}", 1) + if namespace[1:] in XML_NS_MAP.values(): + identifier = element.prefix + ":" + tag if element.sourceline is not None: identifier += f" on line {element.sourceline}" return identifier @@ -1196,13 +1201,22 @@ def _parse_xml_document(file: IO, failsafe: bool = True, **parser_kwargs: Any) - parser = etree.XMLParser(remove_blank_text=True, remove_comments=True, **parser_kwargs) try: - return etree.parse(file, parser).getroot() + root = etree.parse(file, parser).getroot() except etree.XMLSyntaxError as e: if failsafe: logger.error(e) return None raise e + missing_namespaces: Set[str] = REQUIRED_NAMESPACES - set(root.nsmap.values()) + if missing_namespaces: + error_message = f"The following required namespaces are not declared: {' | '.join(missing_namespaces)}" \ + + " - Is the input document of an older version?" + if not failsafe: + raise KeyError(error_message) + logger.error(error_message) + return root + def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromXmlDecoder]]) \ -> Type[AASFromXmlDecoder]: