Skip to content

Commit

Permalink
Resume XML parsing after reporting error
Browse files Browse the repository at this point in the history
  • Loading branch information
cogu committed Mar 31, 2024
1 parent c03961f commit 1ff9766
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 12 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions examples/xml/reader/data/xml_with_errors.arxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00051.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AR-PACKAGES>
<AR-PACKAGE>
<SHORT-NAME>Constants</SHORT-NAME>
<ELEMENTS>
<CONSTANT-SPECIFICATION>
<SHORT-NAME>C_HazardSwitchIllumination_IV</SHORT-NAME>
<VALUE-SPEC>
<NUMERICAL-VALUE-SPECIFICATION>
<SHORT-LABEL>C_HazardSwitchIllumination_IV</SHORT-LABEL>
<VALUE>0</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</VALUE-SPEC>
</CONSTANT-SPECIFICATION>
<CONSTANT-SPECIFICATION>
<SHORT-NAME>C_TelltaleBrightness_IV</SHORT-NAME>
<VALUE-SPEC>
<NUMERICAL-VALUE-SPECIFICATION>
<SHORT-LABEL>C_TelltaleBrightness_IV</SHORT-LABEL>
<VALUE>FiftyFive</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</VALUE-SPEC>
</CONSTANT-SPECIFICATION>
<CONSTANT-SPECIFICATION>
<SHORT-NAME>C_HazardSwitchIllumination_IV</SHORT-NAME>
<VALUE-SPEC>
<NUMERICAL-VALUE-SPECIFICATION>
<SHORT-LABEL>C_HazardSwitchIllumination_IV</SHORT-LABEL>
<VALUE>0</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</VALUE-SPEC>
</CONSTANT-SPECIFICATION>
<CONSTANT-SPECIFICATION>
<SHORT-NAME>C_LightSensor_IV</SHORT-NAME>
<VALUE-SPEC>
<NUMERICAL-VALUE-SPECIFICATION>
<SHORT-LABEL>C_LightSensor_IV</SHORT-LABEL>
<VALUE>65535</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</VALUE-SPEC>
</CONSTANT-SPECIFICATION>
<CONSTANT-SPECIFICATION>
<SHORT-NAME>C_HazardSwitchIllumination_IV</SHORT-NAME>
<VALUE-SPEC>
<NUMERICAL-VALUE-SPECIFICATION>
<SHORT-LABEL>C_HazardSwitchIllumination_IV</SHORT-LABEL>
<VALUE>0</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</VALUE-SPEC>
</CONSTANT-SPECIFICATION>
</ELEMENTS>
</AR-PACKAGE>
</AR-PACKAGES>
</AUTOSAR>
27 changes: 27 additions & 0 deletions examples/xml/reader/print_errors.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions run_examples.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/autosar/xml/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -3411,15 +3411,15 @@ 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)
self._collection_map[package.name] = package
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)
Expand Down
6 changes: 6 additions & 0 deletions src/autosar/xml/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 32 additions & 9 deletions src/autosar/xml/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
"""
Expand All @@ -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.
"""
Expand All @@ -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()
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/xml/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 1ff9766

Please sign in to comment.