Mercurial > genshi > mirror
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) |