1 |
55
|
aaronmk
|
# XPath-based XML tree manipulation
|
2 |
|
|
|
3 |
|
|
import copy
|
4 |
|
|
|
5 |
|
|
import xml_dom
|
6 |
|
|
import xpath
|
7 |
|
|
|
8 |
|
|
def get(doc, path, create=False, last_only=None, parent=None):
|
9 |
|
|
# Warning: The last_only optimization may put data that should be together
|
10 |
|
|
# into separate nodes
|
11 |
|
|
if parent == None: parent = doc.documentElement
|
12 |
|
|
if last_only == None: last_only = create
|
13 |
|
|
elem_idx = 0
|
14 |
|
|
for elem in path:
|
15 |
|
|
# Find possible matches
|
16 |
|
|
children = []
|
17 |
|
|
if elem.is_attr:
|
18 |
|
|
child = parent.getAttributeNode(elem.name)
|
19 |
|
|
if child != None: children = [child]
|
20 |
|
|
elif elem.name == '.': children = [parent]
|
21 |
62
|
aaronmk
|
else:
|
22 |
|
|
children = xml_dom.by_tag_name(parent, elem.name,
|
23 |
|
|
last_only and (elem.attrs == [] or xpath.is_instance(elem)))
|
24 |
55
|
aaronmk
|
|
25 |
|
|
# Check each match
|
26 |
|
|
node = None
|
27 |
|
|
for child in children:
|
28 |
|
|
is_match = elem.value == None or xml_dom.value(child) == elem.value
|
29 |
|
|
for attr in elem.attrs:
|
30 |
|
|
if not is_match: break
|
31 |
|
|
is_match = get(doc, attr, False, last_only, child) != None
|
32 |
|
|
if is_match: node = child; break
|
33 |
|
|
|
34 |
|
|
# Create node
|
35 |
|
|
if node == None:
|
36 |
|
|
if not create: return None
|
37 |
|
|
if elem.is_attr:
|
38 |
|
|
parent.setAttribute(elem.name, '')
|
39 |
|
|
node = parent.getAttributeNode(elem.name)
|
40 |
|
|
else: node = parent.appendChild(doc.createElement(elem.name))
|
41 |
|
|
if elem.value != None: xml_dom.set_value(doc, node, elem.value)
|
42 |
|
|
for attr in elem.attrs: get(doc, attr, create, last_only, node)
|
43 |
|
|
|
44 |
|
|
# Follow pointer
|
45 |
|
|
if elem.is_ptr:
|
46 |
|
|
path = copy.deepcopy(path[elem_idx+1:]) # rest of path
|
47 |
|
|
id_elem = xpath.backward_id(path[xpath.instance_level])
|
48 |
|
|
if id_elem != None:
|
49 |
|
|
# backward (child-to-parent) pointer with target ID attr
|
50 |
|
|
xpath.set_value(id_elem, xml_dom.get_id(node))
|
51 |
|
|
else: # forward (parent-to-child) pointer
|
52 |
|
|
id_ = xml_dom.value(node)
|
53 |
|
|
obj_path = xpath.obj(path) # target object
|
54 |
|
|
if id_ == None or get(doc, obj_path, False, True) == None:
|
55 |
|
|
# no target or target attrs don't match
|
56 |
|
|
if not create: return None
|
57 |
|
|
|
58 |
|
|
# Use last target object's ID + 1
|
59 |
|
|
obj_path[-1].attrs = [] # just get by tag name
|
60 |
|
|
last = get(doc, obj_path, False, True)
|
61 |
|
|
if last != None: id_ = str(int(xml_dom.get_id(last)) + 1)
|
62 |
|
|
else: id_ = '0'
|
63 |
|
|
|
64 |
|
|
# Will append if target attrs didn't match. Place ! in XPath
|
65 |
|
|
# after element to fork at to avoid this.
|
66 |
|
|
xml_dom.set_value(doc, node, id_)
|
67 |
|
|
else: last_only = False
|
68 |
|
|
xpath.set_id(path, id_)
|
69 |
|
|
return get(doc, path, create, last_only)
|
70 |
|
|
|
71 |
|
|
parent = node
|
72 |
|
|
elem_idx += 1
|
73 |
|
|
return parent
|