comparison markup/path.py @ 77:f5ec6d4a61e4 trunk

* Simplify implementation of the individual XPath tests (use closures instead of callable classes) * Add support for using `select("@*")` in `py:attrs` directives (#10).
author cmlenz
date Thu, 13 Jul 2006 12:32:11 +0000
parents 1da51d718391
children f9473bdc93b2
comparison
equal deleted inserted replaced
76:85f70ec37112 77:f5ec6d4a61e4
49 in_predicate = True 49 in_predicate = True
50 elif op == ']': 50 elif op == ']':
51 in_predicate = False 51 in_predicate = False
52 elif op.startswith('('): 52 elif op.startswith('('):
53 if cur_tag == 'text': 53 if cur_tag == 'text':
54 steps[-1] = (False, self._FunctionText(), []) 54 steps[-1] = (False, self._function_text(), [])
55 else: 55 else:
56 raise NotImplementedError('XPath function "%s" not ' 56 raise NotImplementedError('XPath function "%s" not '
57 'supported' % cur_tag) 57 'supported' % cur_tag)
58 elif op == '.': 58 elif op == '.':
59 steps.append([False, self._CurrentElement(), []]) 59 steps.append([False, self._node_test_current_element(), []])
60 else: 60 else:
61 cur_op += op 61 cur_op += op
62 cur_tag = '' 62 cur_tag = ''
63 else: 63 else:
64 closure = cur_op in ('', '//') 64 closure = cur_op in ('', '//')
65 if cur_op == '@': 65 if cur_op == '@':
66 if tag == '*': 66 if tag == '*':
67 node_test = self._AnyAttribute() 67 node_test = self._node_test_any_attribute()
68 else: 68 else:
69 node_test = self._AttributeByName(tag) 69 node_test = self._node_test_attribute_by_name(tag)
70 else: 70 else:
71 if tag == '*': 71 if tag == '*':
72 node_test = self._AnyChildElement() 72 node_test = self._node_test_any_child_element()
73 elif in_predicate: 73 elif in_predicate:
74 if len(tag) > 1 and (tag[0], tag[-1]) in self._QUOTES: 74 if len(tag) > 1 and (tag[0], tag[-1]) in self._QUOTES:
75 node_test = self._LiteralString(tag[1:-1]) 75 node_test = self._literal_string(tag[1:-1])
76 if cur_op == '=': 76 if cur_op == '=':
77 node_test = self._OperatorEq(steps[-1][2][-1], 77 node_test = self._operator_eq(steps[-1][2][-1],
78 node_test) 78 node_test)
79 steps[-1][2].pop() 79 steps[-1][2].pop()
80 elif cur_op == '!=': 80 elif cur_op == '!=':
81 node_test = self._OperatorNeq(steps[-1][2][-1], 81 node_test = self._operator_neq(steps[-1][2][-1],
82 node_test) 82 node_test)
83 steps[-1][2].pop() 83 steps[-1][2].pop()
84 else: 84 else:
85 node_test = self._ChildElementByName(tag) 85 node_test = self._node_test_child_element_by_name(tag)
86 if in_predicate: 86 if in_predicate:
87 steps[-1][2].append(node_test) 87 steps[-1][2].append(node_test)
88 else: 88 else:
89 steps.append([closure, node_test, []]) 89 steps.append([closure, node_test, []])
90 cur_op = '' 90 cur_op = ''
156 stack = [0] # stack of cursors into the location path 156 stack = [0] # stack of cursors into the location path
157 157
158 def _test(kind, data, pos): 158 def _test(kind, data, pos):
159 if not stack: 159 if not stack:
160 return False 160 return False
161 161 cursor = stack[-1]
162 elif kind is END: 162
163 if kind is END:
163 stack.pop() 164 stack.pop()
164 return None 165 return None
165 166
166 elif kind is START: 167 elif kind is START:
167 stack.append(stack[-1]) 168 stack.append(cursor)
168 169
169 matched = False 170 matched = False
170 closure, node_test, predicates = self.steps[stack[-1]] 171 closure, node_test, predicates = self.steps[cursor]
171 172
172 matched = node_test(kind, data, pos) 173 matched = node_test(kind, data, pos)
173 if matched and predicates: 174 if matched and predicates:
174 for predicate in predicates: 175 for predicate in predicates:
175 if not predicate(kind, data, pos): 176 if not predicate(kind, data, pos):
176 matched = None 177 matched = None
177 break 178 break
178 179
179 if matched: 180 if matched:
180 if stack[-1] == len(self.steps) - 1: 181 if cursor == len(self.steps) - 1:
181 if ignore_context or len(stack) > 2 \ 182 if ignore_context or len(stack) > 2 \
182 or node_test.axis != 'child': 183 or node_test.axis != 'child':
183 return matched 184 return matched
184 else: 185 else:
185 stack[-1] += 1 186 stack[-1] += 1
187 elif kind is START and not closure: 188 elif kind is START and not closure:
188 # If this step is not a closure, it cannot be matched until the 189 # If this step is not a closure, it cannot be matched until the
189 # current element is closed... so we need to move the cursor 190 # current element is closed... so we need to move the cursor
190 # back to the last closure and retest that against the current 191 # back to the last closure and retest that against the current
191 # element 192 # element
192 closures = [step for step in self.steps[:stack[-1]] if step[0]] 193 closures = [step for step in self.steps[:cursor] if step[0]]
193 closures.reverse() 194 closures.reverse()
194 for closure, node_test, predicates in closures: 195 for closure, node_test, predicates in closures:
195 stack[-1] -= 1 196 cursor -= 1
196 if closure: 197 if closure:
197 matched = node_test(kind, data, pos) 198 matched = node_test(kind, data, pos)
198 if matched: 199 if matched:
199 stack[-1] += 1 200 cursor += 1
200 break 201 break
202 stack[-1] = cursor
201 203
202 return None 204 return None
203 205
204 return _test 206 return _test
205 207
206 class _NodeTest(object): 208 def _node_test_current_element(self):
207 """Abstract node test.""" 209 def _test(kind, *_):
208 axis = None 210 return kind is START
209 def __repr__(self): 211 _test.axis = 'self'
210 return '<%s>' % self.__class__.__name__ 212 return _test
211 213
212 class _CurrentElement(_NodeTest): 214 def _node_test_any_child_element(self):
213 """Node test that matches the context node.""" 215 def _test(kind, *_):
214 axis = 'self' 216 return kind is START
215 def __call__(self, kind, *_): 217 _test.axis = 'child'
216 if kind is START: 218 return _test
217 return True 219
218 return None 220 def _node_test_child_element_by_name(self, name):
219 221 def _test(kind, data, _):
220 class _AnyChildElement(_NodeTest): 222 return kind is START and data[0].localname == name
221 """Node test that matches any child element.""" 223 _test.axis = 'child'
222 axis = 'child' 224 return _test
223 def __call__(self, kind, *_): 225
224 if kind is START: 226 def _node_test_any_attribute(self):
225 return True 227 def _test(kind, data, _):
226 return None 228 if kind is START and data[1]:
227 229 return data[1]
228 class _ChildElementByName(_NodeTest): 230 _test.axis = 'attribute'
229 """Node test that matches a child element with a specific tag name.""" 231 return _test
230 axis = 'child' 232
231 def __init__(self, name): 233 def _node_test_attribute_by_name(self, name):
232 self.name = QName(name) 234 def _test(kind, data, pos):
233 def __call__(self, kind, data, _): 235 if kind is START and name in data[1]:
234 if kind is START: 236 return TEXT, data[1].get(name), pos
235 return data[0].localname == self.name 237 _test.axis = 'attribute'
236 return None 238 return _test
237 def __repr__(self): 239
238 return '<%s "%s">' % (self.__class__.__name__, self.name) 240 def _function_text(self):
239 241 def _test(kind, data, pos):
240 class _AnyAttribute(_NodeTest): 242 return kind is TEXT and (kind, data, pos)
241 """Node test that matches any attribute.""" 243 _test.axis = None
242 axis = 'attribute' 244 return _test
243 def __call__(self, kind, data, pos): 245
244 if kind is START: 246 def _literal_string(self, text):
245 text = ''.join([val for _, val in data[1]]) 247 def _test(*_):
246 if text: 248 return TEXT, text, (None, -1, -1)
247 return TEXT, text, pos 249 _test.axis = None
248 return None 250 return _test
249 return None 251
250 252 def _operator_eq(self, lval, rval):
251 class _AttributeByName(_NodeTest): 253 def _test(kind, data, pos):
252 """Node test that matches an attribute with a specific name.""" 254 lv = lval(kind, data, pos)
253 axis = 'attribute' 255 rv = rval(kind, data, pos)
254 def __init__(self, name): 256 return (lv and lv[1]) == (rv and rv[1])
255 self.name = QName(name) 257 _test.axis = None
256 def __call__(self, kind, data, pos): 258 return _test
257 if kind is START: 259
258 if self.name in data[1]: 260 def _operator_neq(self, lval, rval):
259 return TEXT, data[1].get(self.name), pos 261 def _test(kind, data, pos):
260 return None 262 lv = lval(kind, data, pos)
261 return None 263 rv = rval(kind, data, pos)
262 def __repr__(self): 264 return (lv and lv[1]) != (rv and rv[1])
263 return '<%s "%s">' % (self.__class__.__name__, self.name) 265 _test.axis = None
264 266 return _test
265 class _Function(_NodeTest):
266 """Abstract node test representing a function."""
267
268 class _FunctionText(_Function):
269 """Function that returns text content."""
270 def __call__(self, kind, data, pos):
271 if kind is TEXT:
272 return kind, data, pos
273 return None
274
275 class _LiteralString(_NodeTest):
276 """Always returns a literal string."""
277 def __init__(self, value):
278 self.value = value
279 def __call__(self, *_):
280 return TEXT, self.value, (-1, -1)
281
282 class _OperatorEq(_NodeTest):
283 """Equality comparison operator."""
284 def __init__(self, lval, rval):
285 self.lval = lval
286 self.rval = rval
287 def __call__(self, kind, data, pos):
288 lval = self.lval(kind, data, pos)
289 rval = self.rval(kind, data, pos)
290 return (lval and lval[1]) == (rval and rval[1])
291 def __repr__(self):
292 return '<%s %r = %r>' % (self.__class__.__name__, self.lval,
293 self.rval)
294
295 class _OperatorNeq(_NodeTest):
296 """Inequality comparison operator."""
297 def __init__(self, lval, rval):
298 self.lval = lval
299 self.rval = rval
300 def __call__(self, kind, data, pos):
301 lval = self.lval(kind, data, pos)
302 rval = self.rval(kind, data, pos)
303 return (lval and lval[1]) != (rval and rval[1])
304 def __repr__(self):
305 return '<%s %r != %r>' % (self.__class__.__name__, self.lval,
306 self.rval)
Copyright (C) 2012-2017 Edgewall Software