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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
1 # -*- coding: utf-8 -*-
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
4 # All rights reserved.
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
5 #
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
6 # This software is licensed as described in the file COPYING, which
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
9 #
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
10 # This software consists of voluntary contributions made by many
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
13
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
14 """Basic support for evaluating XPath expressions against streams."""
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
15
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
16 import re
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
19
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
20 __all__ = ['Path']
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
21
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
22
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
29 """
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
30 _TOKEN_RE = re.compile('(::|\.\.|\(\)|[/.:\[\]\(\)@=!])|'
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
31 '([^/:\[\]\(\)@=!\s]+)|'
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
32 '\s+')
26
3c1a022be04c * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
33 _QUOTES = (("'", "'"), ('"', '"'))
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
34
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
40 self.source = text
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
41
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
42 steps = []
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
43 cur_op = ''
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
44 cur_tag = ''
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
45 in_predicate = False
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
46 for op, tag in self._TOKEN_RE.findall(text):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
47 if op:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
48 if op == '[':
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
49 in_predicate = True
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
50 elif op == ']':
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
51 in_predicate = False
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
52 elif op.startswith('('):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
55 else:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
56 raise NotImplementedError('XPath function "%s" not '
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
60 else:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
61 cur_op += op
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
62 cur_tag = ''
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
63 else:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
64 closure = cur_op in ('', '//')
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
65 if cur_op == '@':
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
70 else:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
79 steps[-1][2].pop()
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
83 steps[-1][2].pop()
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
86 if in_predicate:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
87 steps[-1][2].append(node_test)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
88 else:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
89 steps.append([closure, node_test, []])
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
90 cur_op = ''
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
96
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
97 def __repr__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
98 return '<%s "%s">' % (self.__class__.__name__, self.source)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
99
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
105 >>> from markup.input import XML
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
106 >>> xml = XML('<root><elem><child>Text</child></elem></root>')
61
448792ab1303 Use a different namespace than Kid uses.
cmlenz
parents: 38
diff changeset
107
33
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
108 >>> print Path('child').select(xml)
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
109 <child>Text</child>
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
110
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
111 >>> print Path('child/text()').select(xml)
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
112 Text
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
119 test = self.test()
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
120 for kind, data, pos in stream:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
121 result = test(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
122 if result is True:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
123 yield kind, data, pos
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
124 depth = 1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
125 while depth > 0:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
126 ev = stream.next()
69
c40a5dcd2b55 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
127 depth += {START: 1, END: -1}.get(ev[0], 0)
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
128 yield ev
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
129 test(*ev)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
130 elif result:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
142
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
143 >>> from markup.input import XML
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
144 >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>')
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
145 >>> test = Path('child').test()
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
146 >>> for kind, data, pos in xml:
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
147 ... if test(kind, data, pos):
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
148 ... print kind, data
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
149 START (u'child', [(u'id', u'1')])
1fdb7054fb42 Add doctests for path module.
cmlenz
parents: 27
diff changeset
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
c40a5dcd2b55 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
152 from markup.core import END, START
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
153 stack = [0] # stack of cursors into the location path
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
154
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
155 def _test(kind, data, pos):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
156 if not stack:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
157 return False
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
158
69
c40a5dcd2b55 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
159 elif kind is END:
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
160 stack.pop()
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
161 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
162
69
c40a5dcd2b55 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
163 elif kind is START:
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
164 stack.append(stack[-1])
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
165
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
166 matched = False
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
167 closure, node_test, predicates = self.steps[stack[-1]]
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
168
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
169 matched = node_test(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
170 if matched and predicates:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
171 for predicate in predicates:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
172 if not predicate(kind, data, pos):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
173 matched = None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
174 break
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
175
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
176 if matched:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
183
69
c40a5dcd2b55 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
184 elif kind is START and not closure:
24
5a4d45703319 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
185 # If this step is not a closure, it cannot be matched until the
5a4d45703319 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
186 # current element is closed... so we need to move the cursor
5a4d45703319 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
187 # back to the last closure and retest that against the current
5a4d45703319 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
188 # element
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
189 closures = [step for step in self.steps[:stack[-1]] if step[0]]
25
e3be27f5bcf5 Oops. Fix typo in [25].
cmlenz
parents: 24
diff changeset
190 closures.reverse()
1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
191 for closure, node_test, predicates in closures:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
192 stack[-1] -= 1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
193 if closure:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
194 matched = node_test(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
195 if matched:
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
196 stack[-1] += 1
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
197 break
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
198
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
199 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
200
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
201 return _test
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
214 return True
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
215 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
228 def __init__(self, name):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
232 return data[0].localname == self.name
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
233 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
234 def __repr__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
235 return '<%s "%s">' % (self.__class__.__name__, self.name)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
245 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
246 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
251 def __init__(self, name):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
252 self.name = QName(name)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
257 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
258 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
259 def __repr__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
260 return '<%s "%s">' % (self.__class__.__name__, self.name)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
269 return kind, data, pos
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
270 return None
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
274 def __init__(self, value):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
281 def __init__(self, lval, rval):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
282 self.lval = lval
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
283 self.rval = rval
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
284 def __call__(self, kind, data, pos):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
285 lval = self.lval(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
286 rval = self.rval(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
287 return (lval and lval[1]) == (rval and rval[1])
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
288 def __repr__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
289 return '<%s %r = %r>' % (self.__class__.__name__, self.lval,
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
290 self.rval)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
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
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
294 def __init__(self, lval, rval):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
295 self.lval = lval
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
296 self.rval = rval
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
297 def __call__(self, kind, data, pos):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
298 lval = self.lval(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
299 rval = self.rval(kind, data, pos)
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
300 return (lval and lval[1]) != (rval and rval[1])
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
301 def __repr__(self):
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
302 return '<%s %r != %r>' % (self.__class__.__name__, self.lval,
5479aae32f5a Initial import.
cmlenz
parents:
diff changeset
303 self.rval)
Copyright (C) 2012-2017 Edgewall Software