Skip to content

Commit 0805f1f

Browse files
Merge pull request #6 from acplt/feature/compliance_tool/aasx_schema_check
Feature/compliance tool/aasx schema check
2 parents 63f242f + 50f92e1 commit 0805f1f

4 files changed

Lines changed: 123 additions & 9 deletions

File tree

basyx/aas/compliance_tool/compliance_check_aasx.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
import datetime
2323
import logging
2424
from typing import Optional, Tuple
25+
import io
26+
from lxml import etree # type: ignore
2527

2628
import pyecma376_2
2729

30+
from . import compliance_check_json, compliance_check_xml
2831
from .. import model
2932
from ..adapter import aasx
3033
from ..examples.data import example_aas, create_example_aas_binding
@@ -90,6 +93,69 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana
9093
return obj_store, files, new_cp
9194

9295

96+
def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> None:
97+
"""
98+
Checks a given file against the official json schema and reports any issues using the given
99+
:class:`~aas.compliance_tool.state_manager.ComplianceToolStateManager`
100+
101+
Opens the file and checks if the data inside is stored in XML or JSON. Then calls the respective compliance tool
102+
schema check
103+
"""
104+
logger = logging.getLogger('compliance_check')
105+
logger.addHandler(state_manager)
106+
logger.propagate = False
107+
logger.setLevel(logging.INFO)
108+
109+
# create handler to get logger info
110+
logger_deserialization = logging.getLogger(aasx.__name__)
111+
logger_deserialization.addHandler(state_manager)
112+
logger_deserialization.propagate = False
113+
logger_deserialization.setLevel(logging.INFO)
114+
115+
state_manager.add_step('Open file')
116+
try:
117+
# open given file
118+
reader = aasx.AASXReader(file_path)
119+
state_manager.set_step_status_from_log()
120+
except ValueError as error:
121+
logger.error(error)
122+
state_manager.set_step_status_from_log()
123+
state_manager.add_step('Read file')
124+
state_manager.set_step_status(Status.NOT_EXECUTED)
125+
return
126+
127+
try:
128+
# read given file (Find XML and JSON parts)
129+
state_manager.add_step('Read file')
130+
core_rels = reader.reader.get_related_parts_by_type()
131+
try:
132+
aasx_origin_part = core_rels[aasx.RELATIONSHIP_TYPE_AASX_ORIGIN][0]
133+
except IndexError as e:
134+
raise ValueError("Not a valid AASX file: aasx-origin Relationship is missing.") from e
135+
state_manager.set_step_status(Status.SUCCESS)
136+
for aas_part in reader.reader.get_related_parts_by_type(aasx_origin_part)[
137+
aasx.RELATIONSHIP_TYPE_AAS_SPEC]:
138+
content_type = reader.reader.get_content_type(aas_part)
139+
extension = aas_part.split("/")[-1].split(".")[-1]
140+
with reader.reader.open_part(aas_part) as p:
141+
if content_type.split(";")[0] in (
142+
"text/xml", "application/xml") or content_type == "" and extension == "xml":
143+
logger.debug("Parsing AAS objects from XML stream in OPC part {} ...".format(aas_part))
144+
compliance_check_xml._check_schema(p, state_manager)
145+
elif content_type.split(";")[0] == "application/json" \
146+
or content_type == "" and extension == "json":
147+
logger.debug("Parsing AAS objects from JSON stream in OPC part {} ...".format(aas_part))
148+
compliance_check_json._check_schema(io.TextIOWrapper(p, encoding='utf-8-sig'), state_manager)
149+
else:
150+
raise ValueError("Could not determine part format of AASX part {} (Content Type: {}, extension: {}"
151+
.format(aas_part, content_type, extension))
152+
except ValueError as error:
153+
logger.error(error)
154+
state_manager.set_step_status(Status.FAILED)
155+
finally:
156+
reader.close()
157+
158+
93159
def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager) -> None:
94160
"""
95161
Checks if a file contains all elements of the aas example and reports any issues using the given

basyx/aas/compliance_tool/compliance_check_json.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"""
2525
import json
2626
import logging
27-
from typing import Optional
27+
from typing import Optional, IO
2828

2929
from .. import model
3030
from ..adapter.json import json_deserialization, JSON_SCHEMA_FILE
@@ -61,6 +61,15 @@ def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> N
6161
state_manager.add_step('Validate file against official json schema')
6262
state_manager.set_step_status(Status.NOT_EXECUTED)
6363
return
64+
return _check_schema(file_to_be_checked, state_manager)
65+
66+
67+
def _check_schema(file_to_be_checked: IO[str], state_manager: ComplianceToolStateManager):
68+
logger = logging.getLogger('compliance_check')
69+
logger.addHandler(state_manager)
70+
logger.propagate = False
71+
logger.setLevel(logging.INFO)
72+
6473
try:
6574
with file_to_be_checked:
6675
state_manager.set_step_status(Status.SUCCESS)

basyx/aas/compliance_tool/compliance_check_xml.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> N
5454
try:
5555
# open given file
5656
file_to_be_checked = open(file_path, 'rb')
57+
state_manager.set_step_status(Status.SUCCESS)
5758
except IOError as error:
5859
state_manager.set_step_status(Status.FAILED)
5960
logger.error(error)
@@ -62,20 +63,31 @@ def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> N
6263
state_manager.add_step('Validate file against official xml schema')
6364
state_manager.set_step_status(Status.NOT_EXECUTED)
6465
return
66+
return _check_schema(file_to_be_checked, state_manager)
67+
68+
69+
def _check_schema(file_to_be_checked, state_manager):
70+
logger = logging.getLogger('compliance_check')
71+
logger.addHandler(state_manager)
72+
logger.propagate = False
73+
logger.setLevel(logging.INFO)
74+
75+
state_manager.add_step('Read file and check if it is conform to the xml syntax')
6576
try:
66-
with file_to_be_checked:
67-
state_manager.set_step_status(Status.SUCCESS)
68-
# read given file and check if it is conform to the xml syntax
69-
state_manager.add_step('Read file and check if it is conform to the xml syntax')
70-
parser = etree.XMLParser(remove_blank_text=True, remove_comments=True)
71-
etree.parse(file_to_be_checked, parser)
72-
state_manager.set_step_status(Status.SUCCESS)
77+
# read given file and check if it is conform to the xml syntax
78+
parser = etree.XMLParser(remove_blank_text=True, remove_comments=True)
79+
etree.parse(file_to_be_checked, parser)
80+
state_manager.set_step_status(Status.SUCCESS)
7381
except etree.XMLSyntaxError as error:
7482
state_manager.set_step_status(Status.FAILED)
7583
logger.error(error)
7684
state_manager.add_step('Validate file against official xml schema')
7785
state_manager.set_step_status(Status.NOT_EXECUTED)
86+
file_to_be_checked.close()
7887
return
88+
except Exception:
89+
file_to_be_checked.close()
90+
raise
7991

8092
# load aas xml schema
8193
aas_xml_schema = etree.XMLSchema(file=XML_SCHEMA_FILE)
@@ -84,7 +96,8 @@ def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> N
8496
state_manager.add_step('Validate file against official xml schema')
8597
# validate given file against schema
8698
try:
87-
with open(file_path, 'rb') as file_to_be_checked:
99+
file_to_be_checked.seek(0) # Reset reading file offset (cursor) to the beginning of the file
100+
with file_to_be_checked:
88101
etree.parse(file_to_be_checked, parser=parser)
89102
except etree.ParseError as error:
90103
state_manager.set_step_status(Status.FAILED)

test/compliance_tool/test_compliance_check_aasx.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
88
import os
99
import unittest
10+
import sys
1011

1112
from basyx.aas.compliance_tool import compliance_check_aasx as compliance_tool
1213
from basyx.aas.compliance_tool.state_manager import ComplianceToolStateManager, Status
@@ -130,3 +131,28 @@ def test_check_aasx_files_equivalence(self) -> None:
130131
'[Identifier(IRI=https://acplt.org/Test_AssetAdministrationShell)] must be ==',
131132
manager.format_step(4, verbose_level=1))
132133
self.assertEqual(Status.NOT_EXECUTED, manager.steps[5].status)
134+
135+
@unittest.skipIf(sys.version_info < (3, 7), "The XML schema check fails for Python <= 3.6")
136+
def test_check_schema(self):
137+
manager = ComplianceToolStateManager()
138+
script_dir = os.path.dirname(__file__)
139+
140+
file_path_2 = os.path.join(script_dir, 'files/test_demo_full_example.aasx')
141+
compliance_tool.check_schema(file_path_2, manager)
142+
self.assertEqual(10, len(manager.steps))
143+
for i in range(10):
144+
self.assertEqual(Status.SUCCESS, manager.steps[i].status)
145+
146+
manager.steps = []
147+
file_path_3 = os.path.join(script_dir, 'files/test_demo_full_example2.aasx')
148+
compliance_tool.check_schema(file_path_3, manager)
149+
self.assertEqual(4, len(manager.steps))
150+
for i in range(4):
151+
self.assertEqual(Status.SUCCESS, manager.steps[i].status)
152+
153+
manager.steps = []
154+
file_path_4 = os.path.join(script_dir, 'files/test_demo_full_example_wrong_attribute.aasx')
155+
compliance_tool.check_schema(file_path_4, manager)
156+
self.assertEqual(10, len(manager.steps))
157+
for i in range(10):
158+
self.assertEqual(Status.SUCCESS, manager.steps[i].status)

0 commit comments

Comments
 (0)