switch from dom to xmlschema

as suggested by @Moult, xmlschema better fits the purpose than xml.dom.minidom
This commit is contained in:
ArturTomczak
2021-06-10 15:19:23 +02:00
parent 4017097f19
commit 8c9ab98697
+47 -42
View File
@@ -1,7 +1,6 @@
import operator import operator
import ifcopenshell.util.element import ifcopenshell.util.element
from xml.dom.minidom import parse
from xmlschema import XMLSchema from xmlschema import XMLSchema
@@ -47,16 +46,22 @@ class facet(metaclass=meta_facet):
The base class for IDS facets. IDS facets are functors constructed from The base class for IDS facets. IDS facets are functors constructed from
XML nodes that return True or False. A getattr method is provided for XML nodes that return True or False. A getattr method is provided for
conveniently extracting XML child node text content. conveniently extracting XML child node text content.
""" """
def __init__(self, node): def __init__(self, node):
self.node = node self.node = node
def __getattr__(self, k): def __getattr__(self, k):
try:
v = self.node.getElementsByTagName(k)[0] if k in self.node:
except IndexError: v = self.node[k]
else:
v = None v = None
return v
#TODO implement restrictons
"""
if v: if v:
elems = [n for n in v.childNodes if n.nodeType == n.ELEMENT_NODE] elems = [n for n in v.childNodes if n.nodeType == n.ELEMENT_NODE]
if elems: if elems:
@@ -65,6 +70,7 @@ class facet(metaclass=meta_facet):
return v.firstChild.nodeValue.strip() return v.firstChild.nodeValue.strip()
else: else:
return None return None
"""
def __iter__(self): def __iter__(self):
for k in self.parameters: for k in self.parameters:
@@ -90,10 +96,16 @@ class entity(facet):
if self.predefinedtype and hasattr(inst, "PredefinedType"): if self.predefinedtype and hasattr(inst, "PredefinedType"):
# logger.debug("Testing if entity predefinedtype '%s' == '%s'", inst.PredefinedType, self.predefinedtype) # logger.debug("Testing if entity predefinedtype '%s' == '%s'", inst.PredefinedType, self.predefinedtype)
self.message = "an entity name '%(name)s' of predefined type '%(predefinedtype)s'" self.message = "an entity name '%(name)s' of predefined type '%(predefinedtype)s'"
return facet_evaluation(inst.is_a(self.name) and inst.PredefinedType == self.predefinedtype, self.message % {"name": inst.is_a(), "predefinedtype": inst.PredefinedType}) return facet_evaluation(
inst.is_a(self.name) and inst.PredefinedType == self.predefinedtype,
self.message % {"name": inst.is_a(), "predefinedtype": inst.PredefinedType}
)
else: else:
self.message = "an entity name '%(name)s'" self.message = "an entity name '%(name)s'"
return facet_evaluation(inst.is_a(self.name), self.message % {"name": inst.is_a()}) return facet_evaluation(
inst.is_a(self.name),
self.message % {"name": inst.is_a()}
)
class classification(facet): class classification(facet):
@@ -124,8 +136,6 @@ class property(facet):
""" """
parameters = ["name", "propertyset", "value"] parameters = ["name", "propertyset", "value"]
# import pdb;pdb.set_trace()
message = "a property '%(name)s' in '%(propertyset)s' with value '%(value)s'" message = "a property '%(name)s' in '%(propertyset)s' with value '%(value)s'"
def __call__(self, inst, logger): def __call__(self, inst, logger):
@@ -211,20 +221,25 @@ class restriction:
self.restriction_on = node.getAttribute("base") self.restriction_on = node.getAttribute("base")
self.options = [] self.options = []
self.type = [] self.type = []
for n in node.childNodes: #TODO implement restrictions
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration"): # for n in node.childNodes:
self.options.append(n.getAttribute("value")) # if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration"):
self.type = "enumeration" # self.options.append(n.getAttribute("value"))
elif n.nodeType == n.ELEMENT_NODE and (n.tagName.endswith("Inclusive") or n.tagName.endswith("Exclusive")): # self.type = "enumeration"
self.options.append(n.getAttribute("value")) # elif n.nodeType == n.ELEMENT_NODE and (n.tagName.endswith("Inclusive") or n.tagName.endswith("Exclusive")):
self.type = "bounds" # self.options.append(n.getAttribute("value"))
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("length"): # self.type = "bounds"
self.options.append(n.getAttribute("value")) # elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("length"):
self.type = "length" # self.options.append(n.getAttribute("value"))
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("pattern"): # self.type = "length"
self.options.append(n.getAttribute("value")) # elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("pattern"):
self.type = "pattern" # self.options.append(n.getAttribute("value"))
# self.type = "pattern"
#TODO add min/maxLength
#TODO add fractionDigits
#TODO add totalDigits
#TODO add whiteSpace
def __eq__(self, other): def __eq__(self, other):
return other in self.options return other in self.options
@@ -248,27 +263,22 @@ class specification:
def __init__(self, node): def __init__(self, node):
def parse_rules(node): def parse_rules(node):
children = [n for n in node.childNodes if n.nodeType == n.ELEMENT_NODE] children = [node[n][0] for n in node]
names = map(operator.attrgetter("tagName"), children) names = [n for n in node]
classes = map(meta_facet.facets.__getitem__, names) classes = map(meta_facet.facets.__getitem__, names)
return [cls(n) for cls, n in zip(classes, children)] return [cls(n) for cls, n in zip(classes, children)]
phrases = [n for n in node.childNodes if n.nodeType == n.ELEMENT_NODE] self.applicability = boolean_and(parse_rules(node['applicability']))
self.requirements = boolean_and(parse_rules(node['requirements']))
len(phrases) == 2 or error("expected two child nodes for <specification>")
phrases[0].tagName == "applicability" or error("expected <applicability>")
phrases[1].tagName == "requirements" or error("expected <requirements>")
self.applicability, self.requirements = (boolean_and(parse_rules(phrase)) for phrase in phrases)
def __call__(self, inst, logger): def __call__(self, inst, logger):
if self.applicability(inst, logger): if self.applicability(inst, logger):
valid = self.requirements(inst, logger) valid = self.requirements(inst, logger)
if valid: if valid:
logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n'" + inst.Name + "' (id:" + inst.GlobalId + ") has " + str(valid) + " so is compliant"}) logger.info({'guid':inst.GlobalId, 'result':valid.success,'sentence':str(self) + "\n" + inst.is_a() + " '" + str(inst.Name) + "' (#" + str(inst.id()) + ") has " + str(valid) + " so is compliant"})
else: else:
logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n'" + inst.Name + "' (id:" + inst.GlobalId + ") has " + str(valid) + " so is not compliant"}) logger.error({'guid':inst.GlobalId, 'result':valid.success, 'sentence':str(self) + "\n" + inst.is_a() + " '" + str(inst.Name) + "' (#" + str(inst.id()) + ") has " + str(valid) + " so is not compliant"})
def __str__(self): def __str__(self):
return "Given an instance with %(applicability)s\nWe expect %(requirements)s" % self.__dict__ return "Given an instance with %(applicability)s\nWe expect %(requirements)s" % self.__dict__
@@ -282,14 +292,9 @@ class ids:
def __init__(self, fn): def __init__(self, fn):
ids_schema = XMLSchema("http://standards.buildingsmart.org/IDS/ids.xsd") ids_schema = XMLSchema("http://standards.buildingsmart.org/IDS/ids.xsd")
ids_schema.validate(fn) ids_schema.validate(fn)
dom = parse(fn) ids = ids_schema.to_dict(fn)
ids = dom.childNodes[0] self.specifications = [specification(s) for s in ids['specification']]
ids.tagName == "ids" or error("expected <ids>")
self.specifications = [
specification(n) for n in ids.childNodes if n.nodeType == n.ELEMENT_NODE and n.tagName == "specification"
]
def validate(self, ifc_file, logger): def validate(self, ifc_file, logger):
for spec in self.specifications: for spec in self.specifications: