annotate markup/path.py @ 145:56d534eb53f9

* Fix error in expression evaluation when the expression evaluates to an iterable that does not produce event tuples. * The first location step in path expressions longer assumes the `descendant::` axis, but rather the `child::` axis. * Import cleanups.
author cmlenz
date Tue, 15 Aug 2006 09:52:47 +0000
parents 54131cbb91a5
children 50d4b08017df
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
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
14 """Basic support for evaluating XPath expressions against streams.
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
15
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
16 >>> from markup.input import XML
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
17 >>> doc = XML('''<doc>
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
18 ... <items count="2">
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
19 ... <item status="new">
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
20 ... <summary>Foo</summary>
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
21 ... </item>
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
22 ... <item status="closed">
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
23 ... <summary>Bar</summary>
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
24 ... </item>
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
25 ... </items>
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
26 ... </doc>''')
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
27 >>> print doc.select('items/item[@status="closed"]/summary/text()')
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
28 Bar
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
29
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
30 Because the XPath engine operates on markup streams (as opposed to tree
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
31 structures), it only implements a subset of the full XPath 1.0 language.
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
32 """
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
33
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
34 import re
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
35
145
56d534eb53f9 * Fix error in expression evaluation when the expression evaluates to an iterable that does not produce event tuples.
cmlenz
parents: 139
diff changeset
36 from markup.core import Stream, START, END, TEXT, COMMENT, PI
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
37
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
38 __all__ = ['Path', 'PathSyntaxError']
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
39
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
40
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
41 class Axis(object):
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
42 """Defines constants for the various supported XPath axes."""
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
43
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
44 ATTRIBUTE = 'attribute'
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
45 CHILD = 'child'
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
46 DESCENDANT = 'descendant'
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
47 DESCENDANT_OR_SELF = 'descendant-or-self'
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
48 NAMESPACE = 'namespace'
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
49 SELF = 'self'
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
50
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
51 def forname(cls, name):
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
52 """Return the axis constant for the given name, or `None` if no such
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
53 axis was defined.
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
54 """
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
55 return getattr(cls, name.upper().replace('-', '_'), None)
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
56 forname = classmethod(forname)
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
57
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
58
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
59 ATTRIBUTE = Axis.ATTRIBUTE
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
60 CHILD = Axis.CHILD
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
61 DESCENDANT = Axis.DESCENDANT
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
62 DESCENDANT_OR_SELF = Axis.DESCENDANT_OR_SELF
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
63 NAMESPACE = Axis.NAMESPACE
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
64 SELF = Axis.SELF
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
65
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
66
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
67 class Path(object):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
68 """Implements basic XPath support on streams.
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
69
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
70 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
71 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
72 substream matching that path.
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
73 """
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
74
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
75 def __init__(self, text, filename=None, lineno=-1):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
76 """Create the path object from a string.
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
77
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
78 @param text: the path expression
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
79 """
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
80 self.source = text
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
81 self.paths = PathParser(text, filename, lineno).parse()
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
82
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
83 def __repr__(self):
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
84 paths = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
85 for path in self.paths:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
86 steps = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
87 for axis, nodetest, predicates in path:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
88 steps.append('%s::%s' % (axis, nodetest))
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
89 for predicate in predicates:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
90 steps.append('[%s]' % predicate)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
91 paths.append('/'.join(steps))
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
92 return '<%s "%s">' % (self.__class__.__name__, '|'.join(paths))
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
93
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
94 def select(self, stream):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
95 """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
96
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
97 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
98
33
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
99 >>> from markup.input import XML
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
100 >>> xml = XML('<root><elem><child>Text</child></elem></root>')
61
33c2702cf6da Use a different namespace than Kid uses.
cmlenz
parents: 38
diff changeset
101
33
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
102 >>> print Path('child').select(xml)
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
103 <child>Text</child>
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
104
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
105 >>> print Path('child/text()').select(xml)
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
106 Text
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
107
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
108 @param stream: the stream to select from
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
109 @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
110 """
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
111 stream = iter(stream)
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
112 def _generate():
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
113 test = self.test()
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
114 for kind, data, pos in stream:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
115 result = test(kind, data, pos)
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
116 if result is True:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
117 yield kind, data, pos
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
118 depth = 1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
119 while depth > 0:
73
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
120 subkind, subdata, subpos = stream.next()
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
121 if subkind is START:
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
122 depth += 1
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
123 elif subkind is END:
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
124 depth -= 1
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
125 yield subkind, subdata, subpos
b0fd16111f2e Some more performance tweaks.
cmlenz
parents: 70
diff changeset
126 test(subkind, subdata, subpos)
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
127 elif result:
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
128 yield result
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
129 return Stream(_generate())
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
130
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
131 def test(self, ignore_context=False):
26
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
132 """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
133 a specific stream event.
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
134
039fc5b87405 * Split out the XPath tests into a separate `unittest`-based file.
cmlenz
parents: 25
diff changeset
135 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
136 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
137 the event, the function returns the match (for example, a `START` or
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
138 `TEXT` event.) Otherwise, it returns `None`.
33
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
139
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
140 >>> from markup.input import XML
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
141 >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>')
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
142 >>> test = Path('child').test()
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
143 >>> for kind, data, pos in xml:
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
144 ... if test(kind, data, pos):
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
145 ... print kind, data
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
146 START (u'child', [(u'id', u'1')])
0e1fc0211416 Add doctests for path module.
cmlenz
parents: 27
diff changeset
147 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
148 """
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
149 paths = [(steps, len(steps), [0]) for steps in self.paths]
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
150
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
151 def _test(kind, data, pos):
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
152 for steps, size, stack in paths:
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
153 if not stack:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
154 continue
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
155 cursor = stack[-1]
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
156
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
157 if kind is END:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
158 stack.pop()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
159 continue
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
160 elif kind is START:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
161 stack.append(cursor)
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
162
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
163 while 1:
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
164 axis, nodetest, predicates = steps[cursor]
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
165
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
166 matched = nodetest(kind, data, pos)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
167 if matched and predicates:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
168 for predicate in predicates:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
169 if not predicate(kind, data, pos):
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
170 matched = None
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
171 break
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
172
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
173 if matched:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
174 if cursor + 1 == size: # the last location step
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
175 if ignore_context or \
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
176 kind is not START or \
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
177 axis in (ATTRIBUTE, NAMESPACE, SELF) or \
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
178 len(stack) > 2:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
179 return matched
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
180 else:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
181 cursor += 1
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
182 stack[-1] = cursor
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
183
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
184 if axis is not SELF:
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
185 break
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
186
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
187 if not matched and kind is START \
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
188 and axis not in (DESCENDANT, DESCENDANT_OR_SELF):
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
189 # If this step is not a closure, it cannot be matched until
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
190 # the current element is closed... so we need to move the
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
191 # cursor back to the previous closure and retest that
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
192 # against the current element
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
193 backsteps = [step for step in steps[:cursor]
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
194 if step[0] in (DESCENDANT, DESCENDANT_OR_SELF)]
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
195 backsteps.reverse()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
196 for axis, nodetest, predicates in backsteps:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
197 matched = nodetest(kind, data, pos)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
198 if not matched:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
199 cursor -= 1
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
200 break
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
201 stack[-1] = cursor
1
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
202
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
203 return None
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
204
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
205 return _test
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
206
821114ec4f69 Initial import.
cmlenz
parents:
diff changeset
207
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
208 class PathSyntaxError(Exception):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
209 """Exception raised when an XPath expression is syntactically incorrect."""
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
210
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
211 def __init__(self, message, filename=None, lineno=-1, offset=-1):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
212 if filename:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
213 message = '%s (%s, line %d)' % (message, filename, lineno)
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
214 Exception.__init__(self, message)
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
215 self.filename = filename
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
216 self.lineno = lineno
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
217 self.offset = offset
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
218
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
219
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
220 class PathParser(object):
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
221 """Tokenizes and parses an XPath expression."""
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
222
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
223 _QUOTES = (("'", "'"), ('"', '"'))
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
224 _TOKENS = ('::', ':', '..', '.', '//', '/', '[', ']', '()', '(', ')', '@',
121
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
225 '=', '!=', '!', '|', ',')
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
226 _tokenize = re.compile('(%s)|([^%s\s]+)|\s+' % (
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
227 '|'.join([re.escape(t) for t in _TOKENS]),
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
228 ''.join([re.escape(t[0]) for t in _TOKENS]))).findall
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
229
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
230 def __init__(self, text, filename=None, lineno=-1):
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
231 self.filename = filename
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
232 self.lineno = lineno
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
233 self.tokens = filter(None, [a or b for a, b in self._tokenize(text)])
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
234 self.pos = 0
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
235
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
236 # Tokenizer
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
237
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
238 at_end = property(lambda self: self.pos == len(self.tokens) - 1)
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
239 cur_token = property(lambda self: self.tokens[self.pos])
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
240
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
241 def next_token(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
242 self.pos += 1
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
243 return self.tokens[self.pos]
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
244
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
245 def peek_token(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
246 if not self.at_end:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
247 return self.tokens[self.pos + 1]
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
248 return None
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
249
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
250 # Recursive descent parser
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
251
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
252 def parse(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
253 """Parses the XPath expression and returns a list of location path
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
254 tests.
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
255
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
256 For union expressions (such as `*|text()`), this function returns one
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
257 test for each operand in the union. For patch expressions that don't
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
258 use the union operator, the function always returns a list of size 1.
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
259
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
260 Each path test in turn is a sequence of tests that correspond to the
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
261 location steps, each tuples of the form `(axis, testfunc, predicates)`
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
262 """
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
263 paths = [self._location_path()]
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
264 while self.cur_token == '|':
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
265 self.next_token()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
266 paths.append(self._location_path())
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
267 if not self.at_end:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
268 raise PathSyntaxError('Unexpected token %r after end of expression'
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
269 % self.cur_token, self.filename, self.lineno)
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
270 return paths
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
271
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
272 def _location_path(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
273 steps = []
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
274 while True:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
275 if self.cur_token == '//':
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
276 steps.append((DESCENDANT_OR_SELF, NodeTest(), []))
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
277 self.next_token()
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
278 elif self.cur_token == '/' and not steps:
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
279 raise PathSyntaxError('Absolute location paths not supported',
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
280 self.filename, self.lineno)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
281
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
282 axis, nodetest, predicates = self._location_step()
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
283 if not axis:
145
56d534eb53f9 * Fix error in expression evaluation when the expression evaluates to an iterable that does not produce event tuples.
cmlenz
parents: 139
diff changeset
284 axis = CHILD
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
285 steps.append((axis, nodetest, predicates))
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
286
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
287 if self.at_end or not self.cur_token.startswith('/'):
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
288 break
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
289 self.next_token()
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
290
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
291 return steps
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
292
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
293 def _location_step(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
294 if self.cur_token == '@':
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
295 axis = ATTRIBUTE
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
296 self.next_token()
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
297 elif self.cur_token == '.':
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
298 axis = SELF
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
299 elif self.cur_token == '..':
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
300 raise PathSyntaxError('Unsupported axis "parent"', self.filename,
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
301 self.lineno)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
302 elif self.peek_token() == '::':
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
303 axis = Axis.forname(self.cur_token)
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
304 if axis is None:
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
305 raise PathSyntaxError('Unsupport axis "%s"' % axis,
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
306 self.filename, self.lineno)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
307 self.next_token()
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
308 self.next_token()
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
309 else:
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
310 axis = None
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
311 nodetest = self._node_test(axis or CHILD)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
312 predicates = []
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
313 while self.cur_token == '[':
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
314 predicates.append(self._predicate())
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
315 return axis, nodetest, predicates
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
316
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
317 def _node_test(self, axis=None):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
318 test = None
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
319 if self.peek_token() in ('(', '()'): # Node type test
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
320 test = self._node_type()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
321
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
322 else: # Name test
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
323 if self.cur_token == '*':
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
324 test = PrincipalTypeTest(axis)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
325 elif self.cur_token == '.':
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
326 test = NodeTest()
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
327 else:
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
328 test = LocalNameTest(axis, self.cur_token)
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
329
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
330 if not self.at_end:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
331 self.next_token()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
332 return test
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
333
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
334 def _node_type(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
335 name = self.cur_token
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
336 self.next_token()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
337
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
338 args = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
339 if self.cur_token != '()':
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
340 # The processing-instruction() function optionally accepts the
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
341 # name of the PI as argument, which must be a literal string
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
342 self.next_token() # (
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
343 if self.cur_token != ')':
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
344 string = self.cur_token
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
345 if (string[0], string[-1]) in self._QUOTES:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
346 string = string[1:-1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
347 args.append(string)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
348
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
349 cls = _nodetest_map.get(name)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
350 if not cls:
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
351 raise PathSyntaxError('%s() not allowed here' % name, self.filename,
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
352 self.lineno)
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
353 return cls(*args)
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
354
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
355 def _predicate(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
356 assert self.cur_token == '['
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
357 self.next_token()
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
358 expr = self._or_expr()
121
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
359 if self.cur_token != ']':
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
360 raise PathSyntaxError('Expected "]" to close predicate, '
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
361 'but found "%s"' % self.cur_token,
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
362 self.filename, self.lineno)
111
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
363 if not self.at_end:
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
364 self.next_token()
8a4d9064f363 Some fixes and more unit tests for the XPath engine.
cmlenz
parents: 106
diff changeset
365 return expr
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
366
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
367 def _or_expr(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
368 expr = self._and_expr()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
369 while self.cur_token == 'or':
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
370 self.next_token()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
371 expr = OrOperator(expr, self._and_expr())
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
372 return expr
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
373
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
374 def _and_expr(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
375 expr = self._equality_expr()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
376 while self.cur_token == 'and':
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
377 self.next_token()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
378 expr = AndOperator(expr, self._equality_expr())
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
379 return expr
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
380
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
381 def _equality_expr(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
382 expr = self._primary_expr()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
383 while self.cur_token in ('=', '!='):
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
384 op = _operator_map.get(self.cur_token)
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
385 self.next_token()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
386 expr = op(expr, self._primary_expr())
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
387 return expr
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
388
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
389 def _primary_expr(self):
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
390 token = self.cur_token
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
391 if len(token) > 1 and (token[0], token[-1]) in self._QUOTES:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
392 self.next_token()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
393 return StringLiteral(token[1:-1])
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
394 elif token[0].isdigit():
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
395 self.next_token()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
396 return NumberLiteral(float(token))
121
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
397 elif not self.at_end and self.peek_token().startswith('('):
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
398 if self.next_token() == '()':
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
399 args = []
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
400 else:
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
401 self.next_token()
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
402 args = [self._or_expr()]
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
403 while self.cur_token not in (',', ')'):
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
404 args.append(self._or_expr())
22a7080ed242 Added support for the XPath functions `name()`, `namespace-uri()`, `local-name()`, and `not()`.
cmlenz
parents: 114
diff changeset
405 self.next_token()
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
406 cls = _function_map.get(token)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
407 if not cls:
139
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
408 raise PathSyntaxError('Unsupported function "%s"' % token,
54131cbb91a5 Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents: 137
diff changeset
409 self.filename, self.lineno)
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
410 return cls(*args)
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
411 else:
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
412 axis = None
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
413 if token == '@':
114
8f53c3ad385c Use constants for axes in XPath engine.
cmlenz
parents: 111
diff changeset
414 axis = ATTRIBUTE
106
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
415 self.next_token()
61fa4cadb766 Complete rewrite of the XPath parsing, which was a mess before. Closes #19.
cmlenz
parents: 77
diff changeset
416 return self._node_test(axis)
137
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
417
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
418
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
419 # Node tests
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
420
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
421 class PrincipalTypeTest(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
422 __slots__ = ['principal_type']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
423 def __init__(self, principal_type):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
424 self.principal_type = principal_type
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
425 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
426 if kind is START:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
427 if self.principal_type is ATTRIBUTE:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
428 return data[1] or None
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
429 else:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
430 return True
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
431 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
432 return '*'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
433
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
434 class LocalNameTest(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
435 __slots__ = ['principal_type', 'name']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
436 def __init__(self, principal_type, name):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
437 self.principal_type = principal_type
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
438 self.name = name
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
439 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
440 if kind is START:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
441 if self.principal_type is ATTRIBUTE and self.name in data[1]:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
442 return TEXT, data[1].get(self.name), pos
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
443 else:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
444 return data[0].localname == self.name
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
445 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
446 return self.name
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
447
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
448 class CommentNodeTest(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
449 __slots__ = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
450 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
451 return kind is COMMENT and (kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
452 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
453 return 'comment()'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
454
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
455 class NodeTest(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
456 __slots__ = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
457 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
458 if kind is START:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
459 return True
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
460 return kind, data, pos
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
461 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
462 return 'node()'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
463
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
464 class ProcessingInstructionNodeTest(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
465 __slots__ = ['target']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
466 def __init__(self, target=None):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
467 self.target = target
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
468 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
469 if kind is PI and (not self.target or data[0] == self.target):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
470 return (kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
471 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
472 arg = ''
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
473 if self.target:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
474 arg = '"' + self.target + '"'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
475 return 'processing-instruction(%s)' % arg
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
476
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
477 class TextNodeTest(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
478 __slots__ = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
479 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
480 return kind is TEXT and (kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
481 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
482 return 'text()'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
483
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
484 _nodetest_map = {'comment': CommentNodeTest, 'node': NodeTest,
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
485 'processing-instruction': ProcessingInstructionNodeTest,
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
486 'text': TextNodeTest}
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
487
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
488 # Functions
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
489
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
490 class LocalNameFunction(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
491 __slots__ = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
492 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
493 if kind is START:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
494 return TEXT, data[0].localname, pos
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
495 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
496 return 'local-name()'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
497
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
498 class NameFunction(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
499 __slots__ = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
500 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
501 if kind is START:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
502 return TEXT, data[0], pos
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
503 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
504 return 'name()'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
505
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
506 class NamespaceUriFunction(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
507 __slots__ = []
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
508 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
509 if kind is START:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
510 return TEXT, data[0].namespace, pos
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
511 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
512 return 'namespace-uri()'
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
513
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
514 class NotFunction(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
515 __slots__ = ['expr']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
516 def __init__(self, expr):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
517 self.expr = expr
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
518 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
519 return not self.expr(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
520 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
521 return 'not(%s)' % self.expr
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
522
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
523 _function_map = {'local-name': LocalNameFunction, 'name': NameFunction,
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
524 'namespace-uri': NamespaceUriFunction, 'not': NotFunction}
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
525
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
526 # Literals
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
527
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
528 class StringLiteral(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
529 __slots__ = ['text']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
530 def __init__(self, text):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
531 self.text = text
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
532 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
533 return TEXT, self.text, (None, -1, -1)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
534 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
535 return '"%s"' % self.text
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
536
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
537 class NumberLiteral(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
538 __slots__ = ['number']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
539 def __init__(self, number):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
540 self.number = number
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
541 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
542 return TEXT, unicode(self.number), (None, -1, -1)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
543 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
544 return str(self.number)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
545
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
546 # Operators
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
547
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
548 class AndOperator(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
549 __slots__ = ['lval', 'rval']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
550 def __init__(self, lval, rval):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
551 self.lval = lval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
552 self.rval = rval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
553 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
554 lv = self.lval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
555 if type(lv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
556 lv = lv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
557 if not lv:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
558 return False
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
559 rv = self.rval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
560 if type(rv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
561 rv = rv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
562 return bool(rv)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
563 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
564 return '%s and %s' % (self.lval, self.rval)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
565
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
566 class EqualsOperator(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
567 __slots__ = ['lval', 'rval']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
568 def __init__(self, lval, rval):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
569 self.lval = lval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
570 self.rval = rval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
571 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
572 lv = self.lval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
573 if type(lv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
574 lv = lv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
575 rv = self.rval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
576 if type(rv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
577 rv = rv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
578 return lv == rv
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
579 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
580 return '%s=%s' % (self.lval, self.rval)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
581
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
582 class NotEqualsOperator(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
583 __slots__ = ['lval', 'rval']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
584 def __init__(self, lval, rval):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
585 self.lval = lval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
586 self.rval = rval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
587 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
588 lv = self.lval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
589 if type(lv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
590 lv = lv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
591 rv = self.rval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
592 if type(rv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
593 rv = rv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
594 return lv != rv
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
595 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
596 return '%s!=%s' % (self.lval, self.rval)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
597
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
598 class OrOperator(object):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
599 __slots__ = ['lval', 'rval']
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
600 def __init__(self, lval, rval):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
601 self.lval = lval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
602 self.rval = rval
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
603 def __call__(self, kind, data, pos):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
604 lv = self.lval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
605 if type(lv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
606 lv = lv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
607 if lv:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
608 return True
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
609 rv = self.rval(kind, data, pos)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
610 if type(rv) is tuple:
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
611 rv = rv[1]
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
612 return bool(rv)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
613 def __repr__(self):
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
614 return '%s or %s' % (self.lval, self.rval)
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
615
38ddb21b6fa4 Further cleanup of XPath engine.
cmlenz
parents: 122
diff changeset
616 _operator_map = {'=': EqualsOperator, '!=': NotEqualsOperator}
Copyright (C) 2012-2017 Edgewall Software