First Commit
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
from . import mvdxml_expression
|
||||
|
||||
from xml.dom.minidom import parse, Element
|
||||
|
||||
class rule(object):
|
||||
"""
|
||||
A class for representing an mvdXML EntityRule or AttributeRule
|
||||
"""
|
||||
|
||||
def __init__(self, tag, attribute, nodes, bind=None, optional=False):
|
||||
self.tag, self.attribute, self.nodes, self.bind = tag, attribute, nodes, bind
|
||||
self.optional = optional
|
||||
|
||||
def to_string(self, indent=0):
|
||||
# return "%s%s%s[%s](%s%s)%s" % ("\n" if indent else "", " "*indent, self.tag, self.attribute, "".join(n.to_string(indent+2) for n in self.nodes), ("\n" + " "*indent) if len(self.nodes) else "", (" -> %s" % self.bind) if self.bind else "")
|
||||
return "<%s %s%s>" % (self.tag, f"{self.bind}=" if self.bind else "", self.attribute)
|
||||
|
||||
def __repr__(self):
|
||||
return self.to_string()
|
||||
|
||||
class template(object):
|
||||
"""
|
||||
Representation of an mvdXML template
|
||||
"""
|
||||
|
||||
def __init__(self, concept, root, constraints=None, rules=None, parent=None):
|
||||
self.concept, self.root, self.constraints, self.parent = concept, root, (constraints or []), parent
|
||||
self.rules = rules or []
|
||||
self.entity = str(root.attributes['applicableEntity'].value)
|
||||
try:
|
||||
self.name = root.attributes['name'].value
|
||||
except:
|
||||
self.name = None
|
||||
|
||||
def bind(self, constraints):
|
||||
return template(self.concept, self.root, constraints, self.rules)
|
||||
|
||||
def parse(self, visited=None):
|
||||
for rules in self.root.getElementsByTagNameNS("*", "Rules"):
|
||||
for r in rules.childNodes:
|
||||
if not isinstance(r, Element): continue
|
||||
self.rules.append(self.parse_rule(r, visited=visited))
|
||||
|
||||
def traverse(self, fn, root=None, with_parents=False):
|
||||
def visit(n, p=root, ps=[root]):
|
||||
if with_parents:
|
||||
close = fn(rule=n, parents=ps)
|
||||
else:
|
||||
close = fn(rule=n, parent=p)
|
||||
|
||||
for s in n.nodes:
|
||||
visit(s, n, ps + [n])
|
||||
|
||||
if close:
|
||||
close()
|
||||
|
||||
for r in self.rules:
|
||||
visit(r)
|
||||
|
||||
def parse_rule(self, root, visited=None):
|
||||
def visit(node, prefix="", visited=None, parent=None):
|
||||
r = None
|
||||
n = node
|
||||
nm = None
|
||||
p = prefix
|
||||
optional = False
|
||||
visited = set() if visited is None else visited
|
||||
|
||||
if node.localName == "AttributeRule":
|
||||
r = node.attributes["AttributeName"].value
|
||||
try:
|
||||
nm = node.attributes["RuleID"].value
|
||||
except:
|
||||
# without binding, it's wrapped in a SPARQL OPTIONAL {} clause
|
||||
# Aim is to insert this clause once as high in the stack as possible
|
||||
# All topmost attribute rules are optional anyway as in the binding requirements on existence is specified
|
||||
|
||||
def child_has_ruleid_or_prefix(node):
|
||||
if type(node).__name__ == "Element":
|
||||
if "RuleID" in node.attributes or "IdPrefix" in node.attributes:
|
||||
return True
|
||||
for n in node.childNodes:
|
||||
if child_has_ruleid_or_prefix(n): return True
|
||||
|
||||
optional = node.parentNode.localName == "Rules" or not child_has_ruleid_or_prefix(node)
|
||||
elif node.localName == "EntityRule":
|
||||
r = node.attributes["EntityName"].value
|
||||
elif node.localName == "Template":
|
||||
ref = node.attributes['ref'].value
|
||||
# we break infinite recursion using this set
|
||||
if ref not in visited:
|
||||
n = self.concept.template(ref, visited=visited | {ref}).root
|
||||
try:
|
||||
p = p + node.attributes["IdPrefix"].value
|
||||
except:
|
||||
pass
|
||||
elif node.localName == "Constraint":
|
||||
r = mvdxml_expression.parse(node.attributes["Expression"].value)
|
||||
elif node.localName == "EntityRules": pass
|
||||
elif node.localName == "AttributeRules": pass
|
||||
elif node.localName == "Rules": pass
|
||||
elif node.localName == "Constraints": pass
|
||||
elif node.localName == "References": pass
|
||||
elif node.localName == "Definitions": return
|
||||
elif node.localName == "SubTemplates": return # @todo perhaps just traverse them?
|
||||
else:
|
||||
raise ValueError(node.localName)
|
||||
|
||||
def _(n):
|
||||
for subnode in n.childNodes:
|
||||
if not isinstance(subnode, Element): continue
|
||||
for x in visit(subnode, p, visited=visited): yield x
|
||||
|
||||
if r:
|
||||
R = rule(node.localName, r, list(_(n)), (p + nm) if nm else nm, optional=optional)
|
||||
for rr in R.nodes:
|
||||
rr.parent = R
|
||||
yield R
|
||||
else:
|
||||
for subnode in n.childNodes:
|
||||
if not isinstance(subnode, Element): continue
|
||||
for x in visit(subnode, p, visited=visited): yield x
|
||||
|
||||
return list(visit(root, visited=visited))[0]
|
||||
|
||||
class concept_or_applicability(object):
|
||||
"""
|
||||
Representation of either a mvdXML Concept or the Applicability node. Basically a structure
|
||||
for the hierarchical TemplateRule
|
||||
"""
|
||||
|
||||
def __init__(self, root, c):
|
||||
self.root = root
|
||||
self.concept_node = c
|
||||
try:
|
||||
self.name = c.attributes["name"].value
|
||||
except:
|
||||
# probably applicability and not concept
|
||||
self.name = "Applicability"
|
||||
|
||||
def template(self, id=None, visited=None):
|
||||
if id is None:
|
||||
id = self.concept_node.getElementsByTagNameNS("*","Template")[0].attributes['ref'].value
|
||||
|
||||
for node in self.root.dom.getElementsByTagNameNS('*',"ConceptTemplate"):
|
||||
if node.attributes["uuid"].value == id:
|
||||
t = template(self, node)
|
||||
t.parse(visited=visited)
|
||||
t_with_rules = t.bind(self.rules())
|
||||
return t_with_rules
|
||||
|
||||
def rules(self):
|
||||
# Get the top most TemplateRule and traverse
|
||||
try:
|
||||
rules = self.concept_node.getElementsByTagNameNS("*","TemplateRules")[0]
|
||||
except:
|
||||
return []
|
||||
|
||||
def visit(rules):
|
||||
def _():
|
||||
for i, r in enumerate([c for c in rules.childNodes if isinstance(c, Element)]):
|
||||
if i:
|
||||
yield rules.attributes["operator"].value
|
||||
if r.localName == "TemplateRules":
|
||||
yield visit(r)
|
||||
elif r.localName == "TemplateRule":
|
||||
yield mvdxml_expression.parse(r.attributes["Parameters"].value)
|
||||
else:
|
||||
raise Exception()
|
||||
|
||||
return list(_())
|
||||
|
||||
return visit(rules)
|
||||
|
||||
class concept_root(object):
|
||||
def __init__(self, dom, root):
|
||||
self.dom, self.root = dom, root
|
||||
self.name = root.attributes['name'].value
|
||||
self.entity = str(root.attributes['applicableRootEntity'].value)
|
||||
|
||||
def applicability(self):
|
||||
return concept_or_applicability(self, self.root.getElementsByTagNameNS("*","Applicability")[0])
|
||||
|
||||
def concepts(self):
|
||||
for c in self.root.getElementsByTagNameNS("*","Concept"):
|
||||
yield concept_or_applicability(self, c)
|
||||
|
||||
@staticmethod
|
||||
def parse(fn):
|
||||
dom = parse(fn)
|
||||
if len(dom.getElementsByTagNameNS("*","ConceptRoot")):
|
||||
for root in dom.getElementsByTagNameNS("*","ConceptRoot"):
|
||||
CR = concept_root(dom, root)
|
||||
yield CR
|
||||
else:
|
||||
for templ in dom.getElementsByTagNameNS("*","ConceptTemplate"):
|
||||
t = template(None, templ)
|
||||
t.parse()
|
||||
yield t
|
||||
Reference in New Issue
Block a user