1
|
# 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
|
else: children = xml_dom.by_tag_name(parent, elem.name, last_only)
|
22
|
|
23
|
# Check each match
|
24
|
node = None
|
25
|
for child in children:
|
26
|
is_match = elem.value == None or xml_dom.value(child) == elem.value
|
27
|
for attr in elem.attrs:
|
28
|
if not is_match: break
|
29
|
is_match = get(doc, attr, False, last_only, child) != None
|
30
|
if is_match: node = child; break
|
31
|
|
32
|
# Create node
|
33
|
if node == None:
|
34
|
if not create: return None
|
35
|
if elem.is_attr:
|
36
|
parent.setAttribute(elem.name, '')
|
37
|
node = parent.getAttributeNode(elem.name)
|
38
|
else: node = parent.appendChild(doc.createElement(elem.name))
|
39
|
if elem.value != None: xml_dom.set_value(doc, node, elem.value)
|
40
|
for attr in elem.attrs: get(doc, attr, create, last_only, node)
|
41
|
|
42
|
# Follow pointer
|
43
|
if elem.is_ptr:
|
44
|
path = copy.deepcopy(path[elem_idx+1:]) # rest of path
|
45
|
id_elem = xpath.backward_id(path[xpath.instance_level])
|
46
|
if id_elem != None:
|
47
|
# backward (child-to-parent) pointer with target ID attr
|
48
|
xpath.set_value(id_elem, xml_dom.get_id(node))
|
49
|
else: # forward (parent-to-child) pointer
|
50
|
id_ = xml_dom.value(node)
|
51
|
obj_path = xpath.obj(path) # target object
|
52
|
if id_ == None or get(doc, obj_path, False, True) == None:
|
53
|
# no target or target attrs don't match
|
54
|
if not create: return None
|
55
|
|
56
|
# Use last target object's ID + 1
|
57
|
obj_path[-1].attrs = [] # just get by tag name
|
58
|
last = get(doc, obj_path, False, True)
|
59
|
if last != None: id_ = str(int(xml_dom.get_id(last)) + 1)
|
60
|
else: id_ = '0'
|
61
|
|
62
|
# Will append if target attrs didn't match. Place ! in XPath
|
63
|
# after element to fork at to avoid this.
|
64
|
xml_dom.set_value(doc, node, id_)
|
65
|
else: last_only = False
|
66
|
xpath.set_id(path, id_)
|
67
|
return get(doc, path, create, last_only)
|
68
|
|
69
|
parent = node
|
70
|
elem_idx += 1
|
71
|
return parent
|