Mercurial > genshi > mirror
annotate markup/path.py @ 70:dd73921530e8 trunk
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
author | cmlenz |
---|---|
date | Tue, 11 Jul 2006 17:40:41 +0000 |
parents | c40a5dcd2b55 |
children | 1da51d718391 |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
61
diff
changeset
|
3 # Copyright (C) 2006 Edgewall Software |
1 | 4 # All rights reserved. |
5 # | |
6 # This software is licensed as described in the file COPYING, which | |
7 # you should have received as part of this distribution. The terms | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
61
diff
changeset
|
8 # are also available at http://markup.edgewall.org/wiki/License. |
1 | 9 # |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
61
diff
changeset
|
12 # history and logs, available at http://markup.edgewall.org/log/. |
1 | 13 |
14 """Basic support for evaluating XPath expressions against streams.""" | |
15 | |
16 import re | |
17 | |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
18 from markup.core import QName, Stream, START, END, TEXT |
1 | 19 |
20 __all__ = ['Path'] | |
21 | |
22 | |
23 class Path(object): | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
24 """Implements basic XPath support on streams. |
1 | 25 |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
26 Instances of this class represent a "compiled" XPath expression, and provide |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
27 methods for testing the path against a stream, as well as extracting a |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
28 substream matching that path. |
1 | 29 """ |
30 _TOKEN_RE = re.compile('(::|\.\.|\(\)|[/.:\[\]\(\)@=!])|' | |
31 '([^/:\[\]\(\)@=!\s]+)|' | |
32 '\s+') | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
33 _QUOTES = (("'", "'"), ('"', '"')) |
1 | 34 |
35 def __init__(self, text): | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
36 """Create the path object from a string. |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
37 |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
38 @param text: the path expression |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
39 """ |
1 | 40 self.source = text |
41 | |
42 steps = [] | |
43 cur_op = '' | |
44 cur_tag = '' | |
45 in_predicate = False | |
46 for op, tag in self._TOKEN_RE.findall(text): | |
47 if op: | |
48 if op == '[': | |
49 in_predicate = True | |
50 elif op == ']': | |
51 in_predicate = False | |
52 elif op.startswith('('): | |
53 if cur_tag == 'text': | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
54 steps[-1] = (False, self._FunctionText(), []) |
1 | 55 else: |
56 raise NotImplementedError('XPath function "%s" not ' | |
57 'supported' % cur_tag) | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
58 elif op == '.': |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
59 steps.append([False, self._CurrentElement(), []]) |
1 | 60 else: |
61 cur_op += op | |
62 cur_tag = '' | |
63 else: | |
64 closure = cur_op in ('', '//') | |
65 if cur_op == '@': | |
66 if tag == '*': | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
67 node_test = self._AnyAttribute() |
1 | 68 else: |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
69 node_test = self._AttributeByName(tag) |
1 | 70 else: |
71 if tag == '*': | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
72 node_test = self._AnyChildElement() |
1 | 73 elif in_predicate: |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
74 if len(tag) > 1 and (tag[0], tag[-1]) in self._QUOTES: |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
75 node_test = self._LiteralString(tag[1:-1]) |
1 | 76 if cur_op == '=': |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
77 node_test = self._OperatorEq(steps[-1][2][-1], |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
78 node_test) |
1 | 79 steps[-1][2].pop() |
80 elif cur_op == '!=': | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
81 node_test = self._OperatorNeq(steps[-1][2][-1], |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
82 node_test) |
1 | 83 steps[-1][2].pop() |
84 else: | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
85 node_test = self._ChildElementByName(tag) |
1 | 86 if in_predicate: |
87 steps[-1][2].append(node_test) | |
88 else: | |
89 steps.append([closure, node_test, []]) | |
90 cur_op = '' | |
91 cur_tag = tag | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
92 |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
93 self.steps = [] |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
94 for step in steps: |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
95 self.steps.append(tuple(step)) |
1 | 96 |
97 def __repr__(self): | |
98 return '<%s "%s">' % (self.__class__.__name__, self.source) | |
99 | |
100 def select(self, stream): | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
101 """Returns a substream of the given stream that matches the path. |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
102 |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
103 If there are no matches, this method returns an empty stream. |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
104 |
33 | 105 >>> from markup.input import XML |
106 >>> xml = XML('<root><elem><child>Text</child></elem></root>') | |
61 | 107 |
33 | 108 >>> print Path('child').select(xml) |
109 <child>Text</child> | |
110 | |
111 >>> print Path('child/text()').select(xml) | |
112 Text | |
113 | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
114 @param stream: the stream to select from |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
115 @return: the substream matching the path, or an empty stream |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
116 """ |
1 | 117 stream = iter(stream) |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
118 def _generate(): |
1 | 119 test = self.test() |
120 for kind, data, pos in stream: | |
121 result = test(kind, data, pos) | |
122 if result is True: | |
123 yield kind, data, pos | |
124 depth = 1 | |
125 while depth > 0: | |
126 ev = stream.next() | |
69 | 127 depth += {START: 1, END: -1}.get(ev[0], 0) |
1 | 128 yield ev |
129 test(*ev) | |
130 elif result: | |
131 yield result | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
132 return Stream(_generate()) |
1 | 133 |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
134 def test(self, ignore_context=False): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
135 """Returns a function that can be used to track whether the path matches |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
136 a specific stream event. |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
137 |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
138 The function returned expects the positional arguments `kind`, `data`, |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
139 and `pos`, i.e. basically an unpacked stream event. If the path matches |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
140 the event, the function returns the match (for example, a `START` or |
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
141 `TEXT` event.) Otherwise, it returns `None` or `False`. |
33 | 142 |
143 >>> from markup.input import XML | |
144 >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>') | |
145 >>> test = Path('child').test() | |
146 >>> for kind, data, pos in xml: | |
147 ... if test(kind, data, pos): | |
148 ... print kind, data | |
149 START (u'child', [(u'id', u'1')]) | |
150 START (u'child', [(u'id', u'2')]) | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
151 """ |
69 | 152 from markup.core import END, START |
1 | 153 stack = [0] # stack of cursors into the location path |
154 | |
155 def _test(kind, data, pos): | |
156 if not stack: | |
157 return False | |
158 | |
69 | 159 elif kind is END: |
1 | 160 stack.pop() |
161 return None | |
162 | |
69 | 163 elif kind is START: |
1 | 164 stack.append(stack[-1]) |
165 | |
166 matched = False | |
167 closure, node_test, predicates = self.steps[stack[-1]] | |
168 | |
169 matched = node_test(kind, data, pos) | |
170 if matched and predicates: | |
171 for predicate in predicates: | |
172 if not predicate(kind, data, pos): | |
173 matched = None | |
174 break | |
175 | |
176 if matched: | |
177 if stack[-1] == len(self.steps) - 1: | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
178 if ignore_context or len(stack) > 2 \ |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
179 or node_test.axis != 'child': |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
180 return matched |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
181 else: |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
182 stack[-1] += 1 |
1 | 183 |
69 | 184 elif kind is START and not closure: |
24 | 185 # If this step is not a closure, it cannot be matched until the |
186 # current element is closed... so we need to move the cursor | |
187 # back to the last closure and retest that against the current | |
188 # element | |
1 | 189 closures = [step for step in self.steps[:stack[-1]] if step[0]] |
25 | 190 closures.reverse() |
1 | 191 for closure, node_test, predicates in closures: |
192 stack[-1] -= 1 | |
193 if closure: | |
194 matched = node_test(kind, data, pos) | |
195 if matched: | |
196 stack[-1] += 1 | |
197 break | |
198 | |
199 return None | |
200 | |
201 return _test | |
202 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
203 class _NodeTest(object): |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
204 """Abstract node test.""" |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
205 axis = None |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
206 def __repr__(self): |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
207 return '<%s>' % self.__class__.__name__ |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
208 |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
209 class _CurrentElement(_NodeTest): |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
210 """Node test that matches the context node.""" |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
211 axis = 'self' |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
212 def __call__(self, kind, *_): |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
213 if kind is START: |
1 | 214 return True |
215 return None | |
216 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
217 class _AnyChildElement(_NodeTest): |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
218 """Node test that matches any child element.""" |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
219 axis = 'child' |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
220 def __call__(self, kind, *_): |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
221 if kind is START: |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
222 return True |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
223 return None |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
224 |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
225 class _ChildElementByName(_NodeTest): |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
226 """Node test that matches a child element with a specific tag name.""" |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
227 axis = 'child' |
1 | 228 def __init__(self, name): |
229 self.name = QName(name) | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
230 def __call__(self, kind, data, _): |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
231 if kind is START: |
1 | 232 return data[0].localname == self.name |
233 return None | |
234 def __repr__(self): | |
235 return '<%s "%s">' % (self.__class__.__name__, self.name) | |
236 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
237 class _AnyAttribute(_NodeTest): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
238 """Node test that matches any attribute.""" |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
239 axis = 'attribute' |
1 | 240 def __call__(self, kind, data, pos): |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
241 if kind is START: |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
242 text = ''.join([val for _, val in data[1]]) |
1 | 243 if text: |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
244 return TEXT, text, pos |
1 | 245 return None |
246 return None | |
247 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
248 class _AttributeByName(_NodeTest): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
249 """Node test that matches an attribute with a specific name.""" |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
250 axis = 'attribute' |
1 | 251 def __init__(self, name): |
252 self.name = QName(name) | |
253 def __call__(self, kind, data, pos): | |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
254 if kind is START: |
1 | 255 if self.name in data[1]: |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
256 return TEXT, data[1].get(self.name), pos |
1 | 257 return None |
258 return None | |
259 def __repr__(self): | |
260 return '<%s "%s">' % (self.__class__.__name__, self.name) | |
261 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
262 class _Function(_NodeTest): |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
263 """Abstract node test representing a function.""" |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
264 |
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
265 class _FunctionText(_Function): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
266 """Function that returns text content.""" |
1 | 267 def __call__(self, kind, data, pos): |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
268 if kind is TEXT: |
1 | 269 return kind, data, pos |
270 return None | |
271 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
272 class _LiteralString(_NodeTest): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
273 """Always returns a literal string.""" |
1 | 274 def __init__(self, value): |
275 self.value = value | |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
276 def __call__(self, *_): |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
277 return TEXT, self.value, (-1, -1) |
1 | 278 |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
279 class _OperatorEq(_NodeTest): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
280 """Equality comparison operator.""" |
1 | 281 def __init__(self, lval, rval): |
282 self.lval = lval | |
283 self.rval = rval | |
284 def __call__(self, kind, data, pos): | |
285 lval = self.lval(kind, data, pos) | |
286 rval = self.rval(kind, data, pos) | |
287 return (lval and lval[1]) == (rval and rval[1]) | |
288 def __repr__(self): | |
289 return '<%s %r = %r>' % (self.__class__.__name__, self.lval, | |
290 self.rval) | |
291 | |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
292 class _OperatorNeq(_NodeTest): |
26
3c1a022be04c
* Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents:
25
diff
changeset
|
293 """Inequality comparison operator.""" |
1 | 294 def __init__(self, lval, rval): |
295 self.lval = lval | |
296 self.rval = rval | |
297 def __call__(self, kind, data, pos): | |
298 lval = self.lval(kind, data, pos) | |
299 rval = self.rval(kind, data, pos) | |
300 return (lval and lval[1]) != (rval and rval[1]) | |
301 def __repr__(self): | |
302 return '<%s %r != %r>' % (self.__class__.__name__, self.lval, | |
303 self.rval) |