Revision 94
Added by Aaron Marcuse-Kubitza about 13 years ago
scripts/lib/xpath.py | ||
---|---|---|
6 | 6 |
import xml_dom |
7 | 7 |
|
8 | 8 |
class XpathElem: |
9 |
def __init__(self, name, value=None, attrs=None, is_attr=False, |
|
10 |
is_ptr=False): |
|
11 |
if attrs == None: attrs = [] |
|
9 |
def __init__(self, name, value=None, is_attr=False): |
|
12 | 10 |
self.name = name |
13 | 11 |
self.value = value |
14 |
self.attrs = attrs |
|
15 | 12 |
self.is_attr = is_attr |
16 |
self.is_ptr = is_ptr |
|
13 |
self.is_ptr = False |
|
14 |
self.keys = [] |
|
15 |
self.attrs = [] |
|
17 | 16 |
self.other_branches = [] # temp implementation for split paths |
18 | 17 |
|
19 | 18 |
def __repr__(self): |
20 | 19 |
str_ = '' |
21 | 20 |
if self.is_attr: str_ += '@' |
22 | 21 |
str_ += self.name |
23 |
if self.attrs != []: str_ += repr(self.attrs) |
|
22 |
if self.keys != []: str_ += repr(self.keys) |
|
23 |
if self.attrs != []: str_ += ':'+repr(self.attrs) |
|
24 |
if self.is_ptr: str_ += '->' |
|
25 |
if self.other_branches != []: str_ += '{'+repr(self.other_branches)+'}' |
|
24 | 26 |
if self.value != None: str_ += '='+repr(self.value) |
25 |
if self.is_ptr: str_ += '->' |
|
26 | 27 |
return str_ |
27 | 28 |
|
28 | 29 |
def __eq__(self, other): return self.__dict__ == other.__dict__ |
... | ... | |
32 | 33 |
def set_value(path, value): path[-1].value = value |
33 | 34 |
|
34 | 35 |
def backward_id(elem): |
35 |
if len(elem.attrs) >= 1 and value(elem.attrs[0]) == None and\
|
|
36 |
len(elem.attrs[0]) == 1: return elem.attrs[0]
|
|
36 |
if len(elem.keys) >= 1 and value(elem.keys[0]) == None and\
|
|
37 |
len(elem.keys[0]) == 1: return elem.keys[0]
|
|
37 | 38 |
else: return None |
38 | 39 |
|
39 | 40 |
def parse(str_): |
... | ... | |
44 | 45 |
while True: |
45 | 46 |
# Split path |
46 | 47 |
if parser.str_('{'): |
47 |
paths = tree[-1].other_branches |
|
48 |
while True: |
|
49 |
paths.append(_path()) |
|
50 |
if not parser.str_(','): break |
|
48 |
tree[-1].other_branches = _paths() |
|
51 | 49 |
parser.str_('}', required=True) |
52 |
tree += paths.pop(0) # use first subpath for now
|
|
50 |
tree += tree[-1].other_branches.pop(0) # use first path for now
|
|
53 | 51 |
break # nothing allowed after split path |
54 | 52 |
|
55 | 53 |
elem = XpathElem(is_attr=parser.str_('@'), |
56 | 54 |
name=parser.re(r'[\w.*]+', required=True)) |
57 | 55 |
|
58 |
# Attrs
|
|
56 |
# Keys used to match nodes
|
|
59 | 57 |
if parser.str_('['): |
60 |
while True: |
|
61 |
elem.attrs.append(_path()) |
|
62 |
if not parser.str_(','): break |
|
58 |
elem.keys = _paths() |
|
63 | 59 |
parser.str_(']', required=True) |
64 | 60 |
|
61 |
# Attrs created when no matching node exists |
|
62 |
if parser.str_(':'): |
|
63 |
parser.str_('[', required=True) |
|
64 |
elem.attrs = _paths() |
|
65 |
parser.str_(']', required=True) |
|
66 |
|
|
65 | 67 |
elem.is_ptr = parser.str_('->') |
66 | 68 |
tree.append(elem) |
67 | 69 |
|
... | ... | |
70 | 72 |
parser.str_('/', required=True) # next / is inside () |
71 | 73 |
path = _path() |
72 | 74 |
parser.str_(')', required=True) |
73 |
elem.attrs.append(path)
|
|
75 |
elem.keys.append(path)
|
|
74 | 76 |
tree += path |
75 | 77 |
|
76 | 78 |
if not parser.str_('/'): break |
... | ... | |
96 | 98 |
|
97 | 99 |
return tree |
98 | 100 |
|
101 |
def _paths(): |
|
102 |
paths = [] |
|
103 |
while True: |
|
104 |
paths.append(_path()) |
|
105 |
if not parser.str_(','): break |
|
106 |
return paths |
|
107 |
|
|
99 | 108 |
parser.str_('/') # optional leading / |
100 | 109 |
path = _path() |
101 | 110 |
parser.end() |
... | ... | |
111 | 120 |
def set_id(path, id_, has_types=True): |
112 | 121 |
if has_types: id_level = instance_level |
113 | 122 |
else: id_level = 0 |
114 |
path[id_level].attrs.append([XpathElem('id', id_, is_attr=True)])
|
|
123 |
path[id_level].keys.append([XpathElem('id', id_, True)])
|
|
115 | 124 |
|
116 | 125 |
def is_id(path): return path[0].is_attr and path[0].name == 'id' |
117 | 126 |
|
118 |
def is_instance(elem): return elem.attrs != [] and is_id(elem.attrs[0])
|
|
127 |
def is_instance(elem): return elem.keys != [] and is_id(elem.keys[0])
|
|
119 | 128 |
|
120 | 129 |
def get(doc, xpath, create=False, last_only=None, parent=None): |
121 | 130 |
# Warning: The last_only optimization may put data that should be together |
... | ... | |
131 | 140 |
elif elem.name == '.': children = [parent] |
132 | 141 |
else: |
133 | 142 |
children = xml_dom.by_tag_name(parent, elem.name, |
134 |
last_only and (elem.attrs == [] or is_instance(elem)))
|
|
143 |
last_only and (elem.keys == [] or is_instance(elem)))
|
|
135 | 144 |
|
136 | 145 |
# Check each match |
137 | 146 |
node = None |
138 | 147 |
for child in children: |
139 | 148 |
is_match = elem.value == None or xml_dom.value(child) == elem.value |
140 |
for attr in elem.attrs:
|
|
149 |
for attr in elem.keys:
|
|
141 | 150 |
if not is_match: break |
142 | 151 |
is_match = get(doc, attr, False, last_only, child) != None |
143 | 152 |
if is_match: node = child; break |
... | ... | |
150 | 159 |
node = parent.getAttributeNode(elem.name) |
151 | 160 |
else: node = parent.appendChild(doc.createElement(elem.name)) |
152 | 161 |
if elem.value != None: xml_dom.set_value(doc, node, elem.value) |
153 |
for attr in elem.attrs: get(doc, attr, create, last_only, node) |
|
162 |
for attr in elem.keys + elem.attrs: |
|
163 |
get(doc, attr, create, last_only, node) |
|
154 | 164 |
|
155 | 165 |
for branch in elem.other_branches: |
156 | 166 |
branch = copy.deepcopy(branch) |
... | ... | |
168 | 178 |
id_ = xml_dom.value(node) |
169 | 179 |
obj_xpath = obj(xpath) # target object |
170 | 180 |
if id_ == None or get(doc, obj_xpath, False, True) == None: |
171 |
# no target or target attrs don't match
|
|
181 |
# no target or target keys don't match
|
|
172 | 182 |
if not create: return None |
173 | 183 |
|
174 | 184 |
# Use last target object's ID + 1 |
175 |
obj_xpath[-1].attrs = [] # just get by tag name
|
|
185 |
obj_xpath[-1].keys = [] # just get by tag name
|
|
176 | 186 |
last = get(doc, obj_xpath, False, True) |
177 | 187 |
if last != None: id_ = str(int(xml_dom.get_id(last)) + 1) |
178 | 188 |
else: id_ = '0' |
179 | 189 |
|
180 |
# Will append if target attrs didn't match. Place ! in XPath
|
|
190 |
# Will append if target keys didn't match. Place ! in XPath
|
|
181 | 191 |
# after element to fork at to avoid this. |
182 | 192 |
xml_dom.set_value(doc, node, id_) |
183 | 193 |
else: last_only = False |
Also available in: Unified diff
xpath.py: Added concept of keys vs attrs in XPath elem