diff --git a/CHANGELOG.md b/CHANGELOG.md index 02aa3f3..02666f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The first name in a bullet point below is the Python class name while the second Elements marked as `collectable` means that they can be added directly to a package. Non-collectable elements are various sub-elements to collectable elements. +## [Unreleased] + +### Changed + +* Reader class attempts to resume parsing at next element if an error occurs. + * To stop parsing on first error, give the option `stop_on_error=True` to method `Reader.read_file`. + ## [v0.5.3] - 2024-03-31 ### Fixed diff --git a/examples/xml/reader/data/xml_with_errors.arxml b/examples/xml/reader/data/xml_with_errors.arxml new file mode 100644 index 0000000..4ed1fe5 --- /dev/null +++ b/examples/xml/reader/data/xml_with_errors.arxml @@ -0,0 +1,55 @@ + + + + + Constants + + + C_HazardSwitchIllumination_IV + + + C_HazardSwitchIllumination_IV + 0 + + + + + C_TelltaleBrightness_IV + + + C_TelltaleBrightness_IV + FiftyFive + + + + + C_HazardSwitchIllumination_IV + + + C_HazardSwitchIllumination_IV + 0 + + + + + C_LightSensor_IV + + + C_LightSensor_IV + 65535 + + + + + C_HazardSwitchIllumination_IV + + + C_HazardSwitchIllumination_IV + 0 + + + + + + + diff --git a/examples/xml/reader/print_errors.py b/examples/xml/reader/print_errors.py new file mode 100644 index 0000000..71101f8 --- /dev/null +++ b/examples/xml/reader/print_errors.py @@ -0,0 +1,27 @@ +""" +SwAddrMethod example +""" +import os +import autosar.xml +import autosar.xml.element as ar_element + + +def print_package_stats(package: ar_element.Package, parent: str = "/"): + """ + Prints number of elements in package and sub-packages + """ + print(f'Number of elements in "{parent}{package.name}": {len(package.elements)}') + parent_path = parent + package.name + "/" + for sub_package in package.packages: + print_package_stats(sub_package, parent_path) + + +if __name__ == "__main__": + + file_path = os.path.join(os.path.dirname(__file__), 'data', 'xml_with_errors.arxml') + # This file contains an invalid numerical constants as well as two duplicates. + # From the 5 elements in the XML only 2 is successfully placed into the resulting document. + reader = autosar.xml.Reader() + document = reader.read_file(file_path) + for pkg in document.packages: + print_package_stats(pkg) diff --git a/run_examples.cmd b/run_examples.cmd index 61fc183..4f9d274 100644 --- a/run_examples.cmd +++ b/run_examples.cmd @@ -13,6 +13,7 @@ python examples\xml\port_interface\client_server_interface.py python examples\xml\port_interface\mode_switch_interface.py python examples\xml\component\application_component.py python examples\xml\component\composition_component.py +python examples\xml\reader\print_errors.py python examples\template\generate_xml_using_config.py python examples\template\generate_xml_without_config.py python examples\generator\data_types\gen_type_defs_scalar.py diff --git a/src/autosar/xml/element.py b/src/autosar/xml/element.py index 60f6430..7ab9ed9 100644 --- a/src/autosar/xml/element.py +++ b/src/autosar/xml/element.py @@ -3411,7 +3411,7 @@ def append(self, item: CollectableElement): if isinstance(item, Package): package: Package = item if package.name in self._collection_map: - raise ValueError( + raise ar_except.DuplicateElement( f"Package with SHORT-NAME '{package.name}' already exists in package '{self.name}") package.parent = self self.packages.append(package) @@ -3419,7 +3419,7 @@ def append(self, item: CollectableElement): elif isinstance(item, ARElement): elem: ARElement = item if elem.name in self._collection_map: - raise ValueError( + raise ar_except.DuplicateElement( f"Element with SHORT-NAME '{elem.name}' already exists in package '{self.name}'") elem.parent = self self.elements.append(elem) diff --git a/src/autosar/xml/exception.py b/src/autosar/xml/exception.py index e00d9e6..9fee3f2 100644 --- a/src/autosar/xml/exception.py +++ b/src/autosar/xml/exception.py @@ -11,6 +11,12 @@ class ParseError(RuntimeError): """ +class DuplicateElement(ValueError): + """ + Element with this name already in exist in current context + """ + + class VersionError(ValueError): """ Invalid/Unsupported XML version diff --git a/src/autosar/xml/reader.py b/src/autosar/xml/reader.py index 166813d..e41337d 100644 --- a/src/autosar/xml/reader.py +++ b/src/autosar/xml/reader.py @@ -110,6 +110,7 @@ def __init__(self, self.schema_file: str = '' self.schema_version = schema_version self.document: ar_document.Document = None + self.stop_on_error = False self.switcher_collectable = { # Collectable elements # CompuMethod 'COMPU-METHOD': self._read_compu_method, @@ -277,7 +278,7 @@ def __init__(self, "VariableDataPrototype": self._read_variable_data_prototype, } - def read_file(self, file_path: str) -> ar_document.Document: + def read_file(self, file_path: str, stop_on_error: bool = False) -> ar_document.Document: """ Reads ARXML document file """ @@ -286,12 +287,13 @@ def read_file(self, file_path: str) -> ar_document.Document: self.file_path = file_path self.file_base_name = os.path.basename(file_path) self.observed_unsupported_elements = set() + self.stop_on_error = stop_on_error self._clean_namespace('http://autosar.org/schema/r4.0') self._read_root_element() self._read_packages() return self.document - def read_str(self, xml: str) -> None | ar_document.Document: + def read_str(self, xml: str, stop_on_error: bool = False) -> None | ar_document.Document: """ Reads ARXML document from string. """ @@ -300,6 +302,7 @@ def read_str(self, xml: str) -> None | ar_document.Document: self.xml_root = ElementTree.fromstring(bytes(xml, encoding="utf-8")) self.file_path = "" self.file_base_name = "" + self.stop_on_error = stop_on_error self._clean_namespace('http://autosar.org/schema/r4.0') self._read_root_element() self._read_packages() @@ -344,13 +347,19 @@ def _report_unprocessed_element(self, xml_elem: ElementTree.Element): else: print(f"Unprocessed element <{xml_elem.tag}>", file=sys.stderr) - def _raise_parse_error(self, element: ElementTree.Element, message: str): + def _element_error_message(self, element: ElementTree.Element, message: str) -> str: """ - Raises an ARXML parse error with message + Generates an error message with file-name and source-line """ file = self.file_path if self.use_full_path_on_warning else self.file_base_name header = f"{file}({element.sourceline}): " - raise ar_exception.ParseError(header + message) + return header + message + + def _raise_parse_error(self, element: ElementTree.Element, message: str): + """ + Raises an ARXML parse error with message + """ + raise ar_exception.ParseError(self._element_error_message(element, message)) def _clean_namespace(self, namespace: str) -> None: """ @@ -574,9 +583,23 @@ def _read_package_elements(self, package: ar_element.Package, xml_elements: Elem read_method = self.switcher_collectable.get( xml_child_elem.tag, None) if read_method is not None: - element = read_method(xml_child_elem) - assert isinstance(element, ar_element.ARElement) - package.append(element) + try: + element = read_method(xml_child_elem) + assert isinstance(element, ar_element.ARElement) + package.append(element) + except ar_exception.ParseError as exc: + msg = "Parse error encountered while reading element starting on this line" + message = self._element_error_message(xml_child_elem, msg) + if self.stop_on_error: + raise ar_exception.ParseError(message) from exc + print(message + ":") + print(" " + str(exc)) + except ar_exception.DuplicateElement as exc: + message = self._element_error_message(xml_child_elem, + str(exc)) + if self.stop_on_error: + raise ar_exception.DuplicateElement(message) from exc + print(message) else: self._report_unprocessed_element(xml_child_elem) @@ -2608,7 +2631,7 @@ def _read_numerical_value_specification_group(self, """ xml_child = child_elements.get("VALUE") if xml_child is not None: - data["value"] = ar_element.NumericalValue(xml_child.text).value + data["value"] = self._read_number(xml_child.text) def _read_not_available_value_specification(self, xml_element: ElementTree.Element diff --git a/tests/xml/test_document.py b/tests/xml/test_document.py index 496c10a..6414211 100644 --- a/tests/xml/test_document.py +++ b/tests/xml/test_document.py @@ -28,7 +28,7 @@ def test_read_write_document_with_multi_level_package_structure(self): writer = autosar.xml.Writer() xml = writer.write_str(document1, False) reader = autosar.xml.Reader() - document2 = reader.read_str(xml) + document2 = reader.read_str(xml, stop_on_error=True) self.assertEqual(len(document2.packages), 1) datatype_package: ar_element.Package = document2.find("/DataTypes") self.assertEqual(len(datatype_package.packages), 2)