annotate markup/path.py @ 77:f1aa49c759b2

* 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 b0fd16111f2e
children 61fa4cadb766
rev   line source
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
1 # -*- coding: utf-8 -*-
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
2 #
66
822089ae65ce Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents: 61
diff changeset
3 # Copyright (C) 2006 Edgewall Software
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
4 # All rights reserved.
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
5 #
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
6 # This software is licensed as described in the file COPYING, which
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
7 # you should have received as part of this distribution. The terms
66
822089ae65ce 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
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
9 #
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
10 # This software consists of voluntary contributions made by many
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
11 # individuals. For the exact contribution history, see the revision
66
822089ae65ce 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
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
13
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
14 """Basic support for evaluating XPath expressions against streams."""
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
15
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
16 import re
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
17
70
0498da8e5de7 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
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
19
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
20 __all__ = ['Path']
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
21
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
22
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
23 class Path(object):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
24 """Implements basic XPath support on streams.
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
25
26
039fc5b87405 * 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
039fc5b87405 * 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
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
28 substream matching that path.
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
29 """
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
30 _TOKEN_RE = re.compile('(::|\.\.|\(\)|[/.:\[\]\(\)@=!])|'
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
31 '([^/:\[\]\(\)@=!\s]+)|'
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
32 '\s+')
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
33 _QUOTES = (("'", "'"), ('"', '"'))
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
34
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
35 def __init__(self, text):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
36 """Create the path object from a string.
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
37
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
38 @param text: the path expression
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
39 """
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
40 self.source = text
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
41
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
42 steps = []
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
43 cur_op = ''
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
44 cur_tag = ''
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
45 in_predicate = False
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
46 for op, tag in self._TOKEN_RE.findall(text):
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
47 if op:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
48 if op == '[':
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
49 in_predicate = True
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
50 elif op == ']':
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
51 in_predicate = False
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
52 elif op.startswith('('):
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
53 if cur_tag == 'text':
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
54 steps[-1] = (False, self._function_text(), [])
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
55 else:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
56 raise NotImplementedError('XPath function "%s" not '
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
57 'supported' % cur_tag)
38
fec9f4897415 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 == '.':
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
59 steps.append([False, self._node_test_current_element(), []])
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
60 else:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
61 cur_op += op
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
62 cur_tag = ''
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
63 else:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
64 closure = cur_op in ('', '//')
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
65 if cur_op == '@':
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
66 if tag == '*':
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
67 node_test = self._node_test_any_attribute()
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
68 else:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
69 node_test = self._node_test_attribute_by_name(tag)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
70 else:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
71 if tag == '*':
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
72 node_test = self._node_test_any_child_element()
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
73 elif in_predicate:
26
039fc5b87405 * 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:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
75 node_test = self._literal_string(tag[1:-1])
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
76 if cur_op == '=':
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
77 node_test = self._operator_eq(steps[-1][2][-1],
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
78 node_test)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
79 steps[-1][2].pop()
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
80 elif cur_op == '!=':
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
81 node_test = self._operator_neq(steps[-1][2][-1],
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
82 node_test)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
83 steps[-1][2].pop()
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
84 else:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
85 node_test = self._node_test_child_element_by_name(tag)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
86 if in_predicate:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
87 steps[-1][2].append(node_test)
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
88 else:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
89 steps.append([closure, node_test, []])
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
90 cur_op = ''
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
91 cur_tag = tag
38
fec9f4897415 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
fec9f4897415 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 = []
fec9f4897415 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:
fec9f4897415 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
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
96
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
97 def __repr__(self):
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
98 return '<%s "%s">' % (self.__class__.__name__, self.source)
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
99
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
100 def select(self, stream):
26
039fc5b87405 * 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.
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
102
039fc5b87405 * 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.
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
104
33
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
105 >>> from markup.input import XML
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
106 >>> xml = XML('<root><elem><child>Text</child></elem></root>')
61
33c2702cf6da Use a different namespace than Kid uses.
cmlenz
parents: 38
diff changeset
107
33
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
108 >>> print Path('child').select(xml)
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
109 <child>Text</child>
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
110
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
111 >>> print Path('child/text()').select(xml)
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
112 Text
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
113
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
114 @param stream: the stream to select from
039fc5b87405 * 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
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
116 """
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
117 stream = iter(stream)
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
118 def _generate():
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
119 test = self.test()
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
120 for kind, data, pos in stream:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
121 result = test(kind, data, pos)
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
122 if result is True:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
123 yield kind, data, pos
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
124 depth = 1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
125 while depth > 0:
73
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
126 subkind, subdata, subpos = stream.next()
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
127 if subkind is START:
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
128 depth += 1
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
129 elif subkind is END:
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
130 depth -= 1
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
131 yield subkind, subdata, subpos
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
132 test(subkind, subdata, subpos)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
133 elif result:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
134 yield result
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
135 return Stream(_generate())
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
136
38
fec9f4897415 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
137 def test(self, ignore_context=False):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
138 """Returns a function that can be used to track whether the path matches
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
139 a specific stream event.
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
140
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
141 The function returned expects the positional arguments `kind`, `data`,
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
142 and `pos`, i.e. basically an unpacked stream event. If the path matches
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
143 the event, the function returns the match (for example, a `START` or
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
144 `TEXT` event.) Otherwise, it returns `None` or `False`.
33
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
145
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
146 >>> from markup.input import XML
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
147 >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>')
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
148 >>> test = Path('child').test()
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
149 >>> for kind, data, pos in xml:
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
150 ... if test(kind, data, pos):
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
151 ... print kind, data
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
152 START (u'child', [(u'id', u'1')])
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
153 START (u'child', [(u'id', u'2')])
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
154 """
69
e9a3930f8823 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
155 from markup.core import END, START
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
156 stack = [0] # stack of cursors into the location path
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
157
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
158 def _test(kind, data, pos):
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
159 if not stack:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
160 return False
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
161 cursor = stack[-1]
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
162
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
163 if kind is END:
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
164 stack.pop()
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
165 return None
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
166
69
e9a3930f8823 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
167 elif kind is START:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
168 stack.append(cursor)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
169
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
170 matched = False
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
171 closure, node_test, predicates = self.steps[cursor]
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
172
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
173 matched = node_test(kind, data, pos)
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
174 if matched and predicates:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
175 for predicate in predicates:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
176 if not predicate(kind, data, pos):
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
177 matched = None
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
178 break
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
179
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
180 if matched:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
181 if cursor == len(self.steps) - 1:
38
fec9f4897415 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 if ignore_context or len(stack) > 2 \
fec9f4897415 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
183 or node_test.axis != 'child':
fec9f4897415 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
184 return matched
fec9f4897415 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
185 else:
fec9f4897415 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
186 stack[-1] += 1
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
187
69
e9a3930f8823 A couple of minor performance improvements.
cmlenz
parents: 66
diff changeset
188 elif kind is START and not closure:
24
547e36f7ec94 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
189 # If this step is not a closure, it cannot be matched until the
547e36f7ec94 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
190 # current element is closed... so we need to move the cursor
547e36f7ec94 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
191 # back to the last closure and retest that against the current
547e36f7ec94 Cosmetic (mostly whitespace) changes.
cmlenz
parents: 23
diff changeset
192 # element
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
193 closures = [step for step in self.steps[:cursor] if step[0]]
25
c4201b794ab0 Oops. Fix typo in [25].
cmlenz
parents: 24
diff changeset
194 closures.reverse()
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
195 for closure, node_test, predicates in closures:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
196 cursor -= 1
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
197 if closure:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
198 matched = node_test(kind, data, pos)
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
199 if matched:
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
200 cursor += 1
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
201 break
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
202 stack[-1] = cursor
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
203
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
204 return None
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
205
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
206 return _test
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
207
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
208 def _node_test_current_element(self):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
209 def _test(kind, *_):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
210 return kind is START
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
211 _test.axis = 'self'
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
212 return _test
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
213
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
214 def _node_test_any_child_element(self):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
215 def _test(kind, *_):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
216 return kind is START
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
217 _test.axis = 'child'
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
218 return _test
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
219
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
220 def _node_test_child_element_by_name(self, name):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
221 def _test(kind, data, _):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
222 return kind is START and data[0].localname == name
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
223 _test.axis = 'child'
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
224 return _test
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
225
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
226 def _node_test_any_attribute(self):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
227 def _test(kind, data, _):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
228 if kind is START and data[1]:
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
229 return data[1]
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
230 _test.axis = 'attribute'
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
231 return _test
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
232
77
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
233 def _node_test_attribute_by_name(self, name):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
234 def _test(kind, data, pos):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
235 if kind is START and name in data[1]:
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
236 return TEXT, data[1].get(name), pos
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
237 _test.axis = 'attribute'
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
238 return _test
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
239
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
240 def _function_text(self):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
241 def _test(kind, data, pos):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
242 return kind is TEXT and (kind, data, pos)
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
243 _test.axis = None
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
244 return _test
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
245
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
246 def _literal_string(self, text):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
247 def _test(*_):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
248 return TEXT, text, (None, -1, -1)
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
249 _test.axis = None
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
250 return _test
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
251
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
252 def _operator_eq(self, lval, rval):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
253 def _test(kind, data, pos):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
254 lv = lval(kind, data, pos)
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
255 rv = rval(kind, data, pos)
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
256 return (lv and lv[1]) == (rv and rv[1])
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
257 _test.axis = None
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
258 return _test
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
259
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
260 def _operator_neq(self, lval, rval):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
261 def _test(kind, data, pos):
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
262 lv = lval(kind, data, pos)
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
263 rv = rval(kind, data, pos)
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
264 return (lv and lv[1]) != (rv and rv[1])
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
265 _test.axis = None
f1aa49c759b2 * Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents: 73
diff changeset
266 return _test
Copyright (C) 2012-2017 Edgewall Software