From ec600ec72e825e468086f771c05387a0f53c30cb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 14 Jan 2025 13:35:45 +1100 Subject: [PATCH] IfcTester can now read from XML strings in addition to opening files on disk --- src/ifctester/ifctester/ids.py | 19 ++++++++++++++----- src/ifctester/test/test_ids.py | 29 +++++++++++------------------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/ifctester/ifctester/ids.py b/src/ifctester/ifctester/ids.py index 2de95a6d4e..1f2abf4313 100644 --- a/src/ifctester/ifctester/ids.py +++ b/src/ifctester/ifctester/ids.py @@ -50,11 +50,7 @@ class IdsXmlValidationError(Exception): super().__init__(message) -@overload -def open(filepath: str, validate: Literal[False] = False) -> Ids: ... -@overload -def open(filepath: str, validate: Literal[True]) -> None: ... -def open(filepath: str, validate=False) -> Union[Ids, None]: +def open(filepath: str, validate: bool = False) -> Ids: try: if validate: get_schema().validate(filepath) @@ -66,6 +62,19 @@ def open(filepath: str, validate=False) -> Union[Ids, None]: return Ids().parse(decode) +def from_string(xml: str, validate: bool = False) -> Ids: + tree = ET.ElementTree(ET.fromstring(xml)) + try: + if validate: + get_schema().validate(tree) + decode = get_schema().decode( + tree, strip_namespaces=True, namespaces={"": "http://standards.buildingsmart.org/IDS"} + ) + except XMLSchemaValidationError as e: + raise IdsXmlValidationError(e, "Provided XML appears to be invalid. See details above.") + return Ids().parse(decode) + + def get_schema(): global schema if schema is None: diff --git a/src/ifctester/test/test_ids.py b/src/ifctester/test/test_ids.py index 6099e1bcea..d25c2ffd28 100644 --- a/src/ifctester/test/test_ids.py +++ b/src/ifctester/test/test_ids.py @@ -65,6 +65,15 @@ class TestIds: "specifications": {"specification": []}, } + def test_reading_an_ids_from_an_xml_string(self): + specs = ids.Ids() + spec = ids.Specification(name="Name") + spec.applicability.append(ids.Entity(name="IFCWALL")) + specs.specifications.append(spec) + xml = specs.to_string() + specs2 = ids.from_string(xml) + assert len(specs2.specifications) == 1 + def test_create_an_ids_with_all_possible_information(self): specs = ids.Ids( title="title", @@ -117,7 +126,7 @@ class TestIds: spec.requirements.append(ids.Attribute(name="Name", value="Waldo")) specs.specifications.append(spec) fn = "tmp.xml" - result = specs.to_xml(fn) + specs.to_xml(fn) os.remove(fn) def test_creating_a_minimal_ids_and_validating(self): @@ -187,27 +196,11 @@ class TestIds: spec.set_usage("optional") model = ifcopenshell.file() wall = model.createIfcWall(Name="Waldo") - spec.requirements.append(description_attr := ids.Attribute(name="Description", value="Foobar")) + spec.requirements.append(ids.Attribute(name="Description", value="Foobar")) run("A specification passes only if all requirements pass 1/2", specs, model, False, [wall], [wall]) wall.Description = "Foobar" run("A specification passes only if all requirements pass 2/2", specs, model, True, [wall]) - # optional attribute - # description_attr.minOccurs = 0 - # description_attr.maxOccurs = "unbounded" - # wall.Description = None - # run("Specification optionality and facet optionality can be combined", specs, model, True, [wall]) - - # double negative / required attributes - # spec.set_usage("prohibited") - # name_attr.minOccurs = 0 - # name_attr.maxOccurs = 0 - # description_attr.minOccurs = 0 - # description_attr.maxOccurs = 0 - # wall.Name = "Waldo" - # wall.Description = "Foobar" - # run("A prohibited specification and a prohibited facet results in a double negative", specs, model, True, [wall]) - # specs independency specs = ids.Ids(title="Title") spec = ids.Specification(name="Name")