Skip to content

Commit d7345e6

Browse files
committed
Preliminary work on MVD parser/converter/checker
0 parents  commit d7345e6

File tree

3 files changed

+835
-0
lines changed

3 files changed

+835
-0
lines changed

__init__.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import sys
2+
import mvdxml_expression
3+
4+
from xml.dom.minidom import parse, Element
5+
6+
import sparql
7+
8+
class rule(object):
9+
"""
10+
A class for representing an mvdXML EntityRule or AttributeRule
11+
"""
12+
13+
def __init__(self, tag, attribute, nodes, bind=None, optional=False):
14+
self.tag, self.attribute, self.nodes, self.bind = tag, attribute, nodes, bind
15+
self.optional = optional
16+
17+
def to_string(self, indent=0):
18+
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 "")
19+
20+
def __repr__(self):
21+
return self.to_string()
22+
23+
class template(object):
24+
"""
25+
Representation of an mvdXML template
26+
"""
27+
28+
def __init__(self, concept, root, params=None, rules=None):
29+
self.concept, self.root, self.params = concept, root, params
30+
self.rules = rules or []
31+
self.entity = str(root.attributes['applicableEntity'].value)
32+
self.name = root.attributes['name'].value
33+
34+
def bind(self, params):
35+
return template(self.concept, self.root, params, self.rules)
36+
37+
def parse(self):
38+
for rules in self.root.childNodes:
39+
if not isinstance(rules, Element): continue
40+
41+
for r in rules.childNodes:
42+
if not isinstance(r, Element): continue
43+
self.rules.append(self.parse_rule(r))
44+
45+
46+
def traverse(self, fn, root=None, with_parents=False):
47+
def _(n, p=root, ps=[root]):
48+
if with_parents:
49+
close = fn(rule=n, parents=ps)
50+
else:
51+
close = fn(rule=n, parent=p)
52+
53+
for s in n.nodes:
54+
_(s, n, ps + [n])
55+
56+
if close:
57+
close()
58+
59+
for r in self.rules:
60+
_(r)
61+
62+
def parse_rule(self, root):
63+
def visit(node, prefix=""):
64+
r = None
65+
n = node
66+
nm = None
67+
p = prefix
68+
optional=False
69+
70+
if node.tagName == "AttributeRule":
71+
r = node.attributes["AttributeName"].value
72+
try:
73+
nm = node.attributes["RuleID"].value
74+
except:
75+
# without binding, it's wrapped in a SPARQL OPTIONAL {} clause
76+
# Aim is to insert this clause once as high in the stack as possible
77+
# All topmost attribute rules are optional anyway as in the binding requirements on existence is specified
78+
79+
def child_has_ruleid_or_prefix(node):
80+
if type(node).__name__ == "Element":
81+
if "RuleID" in node.attributes or "IdPrefix" in node.attributes:
82+
return True
83+
for n in node.childNodes:
84+
if child_has_ruleid_or_prefix(n): return True
85+
86+
optional = node.parentNode.tagName == "Rules" or not child_has_ruleid_or_prefix(node)
87+
elif node.tagName == "EntityRule":
88+
r = node.attributes["EntityName"].value
89+
elif node.tagName == "References":
90+
ref = node.getElementsByTagName("Template")[0].attributes['ref'].value
91+
n = self.concept.template(ref).root
92+
try: p = p + node.attributes["IdPrefix"].value
93+
except: pass
94+
95+
def _(n):
96+
for subnode in n.childNodes:
97+
if not isinstance(subnode, Element): continue
98+
for x in visit(subnode, p): yield x
99+
100+
if r:
101+
yield rule(node.tagName, r, list(_(n)), (p + nm) if nm else nm, optional=optional)
102+
else:
103+
for subnode in n.childNodes:
104+
if not isinstance(subnode, Element): continue
105+
for x in visit(subnode, p): yield x
106+
107+
return list(visit(root))[0]
108+
109+
class concept_or_applicability(object):
110+
"""
111+
Representation of either a mvdXML Concept or the Applicability node. Basically a structure
112+
for the hierarchical TemplateRule
113+
"""
114+
115+
def __init__(self, root, c):
116+
self.root = root
117+
self.concept_node = c
118+
try:
119+
self.name = c.attributes["name"].value
120+
except:
121+
# probably applicability and not concept
122+
self.name = "Applicability"
123+
124+
def template(self, id = None):
125+
if id is None:
126+
id = self.concept_node.getElementsByTagName("Template")[0].attributes['ref'].value
127+
128+
for node in self.root.dom.getElementsByTagName("ConceptTemplate"):
129+
if node.attributes["uuid"].value == id:
130+
t = template(self, node)
131+
t.parse()
132+
t_with_rules = t.bind(self.rules())
133+
return t_with_rules
134+
135+
136+
def rules(self):
137+
# Get the top most TemplateRule and traverse
138+
rules = self.concept_node.getElementsByTagName("TemplateRules")[0]
139+
140+
def visit(rules):
141+
def _():
142+
for i, r in enumerate([c for c in rules.childNodes if isinstance(c, Element)]):
143+
if i:
144+
yield rules.attributes["operator"].value
145+
if r.tagName == "TemplateRules":
146+
yield visit(r)
147+
elif r.tagName == "TemplateRule":
148+
yield mvdxml_expression.parse(r.attributes["Parameters"].value)
149+
else:
150+
raise Exception()
151+
152+
return list(_())
153+
154+
return visit(rules)
155+
156+
class concept_root(object):
157+
def __init__(self, dom, root):
158+
self.dom, self.root = dom, root
159+
self.name = root.attributes['name'].value
160+
self.entity = str(root.attributes['applicableRootEntity'].value)
161+
162+
def applicability(self):
163+
return concept_or_applicability(self, self.root.getElementsByTagName("Applicability")[0])
164+
165+
def concepts(self):
166+
for c in self.root.getElementsByTagName("Concept"):
167+
yield concept_or_applicability(self, c)
168+
169+
@staticmethod
170+
def parse(fn):
171+
dom = parse(fn)
172+
try:
173+
root = dom.getElementsByTagName("ConceptRoot")[0]
174+
CR = concept_root(dom, root)
175+
return CR
176+
except:
177+
root = dom.getElementsByTagName("ConceptTemplate")[0]
178+
t = template(None, root)
179+
t.parse()
180+
return t
181+
182+
183+
if __name__ == "__main__":
184+
185+
if len(sys.argv) == 3:
186+
ttlfn, mvdfn = sys.argv[1:]
187+
sparql.derive_prefix(ttlfn)
188+
ttlfn = sparql.infer_subtypes(ttlfn)
189+
MVD = concept_root.parse(mvdfn)
190+
sparql.executor.run(MVD, mvdfn, ttlfn)
191+
192+
else:
193+
mvdfn = sys.argv[1]
194+
MVD = concept_root.parse(mvdfn)
195+
196+
def dump(rule, parents):
197+
print(" " * len(parents), rule.tag, rule.attribute)
198+
199+
for c in MVD.concepts():
200+
print(c.name)
201+
print()
202+
203+
t = c.template()
204+
print("RootEntity", t.entity)
205+
t.traverse(dump, with_parents=True)
206+
print(" ".join(map(str, t.params)))
207+
208+
print()

mvdxml_expression.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pyparsing as pp
2+
3+
class node(object):
4+
def __init__(self, args):
5+
assert (args[1], args[3], args[4]) == ('[', ']', '=')
6+
self.a, self.b, self.c = args[0], args[2], args[5]
7+
def __repr__(self): return "{%s[%s]=%s}" % (self.a, self.b, self.c)
8+
9+
word = pp.Word(pp.alphanums+"_")
10+
quoted = pp.Combine("'" + word + "'")
11+
bool_value = pp.CaselessLiteral("TRUE") | pp.CaselessLiteral("FALSE")
12+
rhs = quoted | bool_value
13+
stmt = (word + "[" + word + "]" + "=" + rhs).setParseAction(node)
14+
bool_op = pp.CaselessLiteral("AND") | pp.CaselessLiteral("OR")
15+
grammar = stmt + pp.Optional(pp.OneOrMore(bool_op + stmt))
16+
17+
def parse(expr):
18+
return grammar.parseString(expr)

0 commit comments

Comments
 (0)