Edgewall Software

source: trunk/genshi/path.py

Last change on this file was 1269, checked in by hodgestar, 10 years ago

Return correct value and properly namespaced attribute name when matching namespaced attributes with XPath expressions (fixes #572; thanks to Olemis Lang <olemis+trac@…> for bug report and suggestion for fix).

  • Property svn:eol-style set to native
File size: 55.8 KB
RevLine 
[2]1# -*- coding: utf-8 -*-
2#
[1077]3# Copyright (C) 2006-2009 Edgewall Software
[2]4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
[287]8# are also available at http://genshi.edgewall.org/wiki/License.
[2]9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
[287]12# history and logs, available at http://genshi.edgewall.org/log/.
[2]13
[129]14"""Basic support for evaluating XPath expressions against streams.
[2]15
[287]16>>> from genshi.input import XML
[129]17>>> doc = XML('''<doc>
[620]18...  <items count="4">
[129]19...       <item status="new">
20...         <summary>Foo</summary>
21...       </item>
22...       <item status="closed">
23...         <summary>Bar</summary>
24...       </item>
[620]25...       <item status="closed" resolution="invalid">
26...         <summary>Baz</summary>
27...       </item>
28...       <item status="closed" resolution="fixed">
29...         <summary>Waz</summary>
30...       </item>
[129]31...   </items>
32... </doc>''')
[1076]33>>> print(doc.select('items/item[@status="closed" and '
34...     '(@resolution="invalid" or not(@resolution))]/summary/text()'))
[620]35BarBaz
[129]36
37Because the XPath engine operates on markup streams (as opposed to tree
38structures), it only implements a subset of the full XPath 1.0 language.
39"""
40
[1025]41from collections import deque
[970]42try:
[1079]43    reduce # builtin in Python < 3
44except NameError:
[970]45    from functools import reduce
[197]46from math import ceil, floor
[707]47import operator
[2]48import re
[1025]49from itertools import chain
[2]50
[287]51from genshi.core import Stream, Attrs, Namespace, QName
[754]52from genshi.core import START, END, TEXT, START_NS, END_NS, COMMENT, PI, \
53                        START_CDATA, END_CDATA
[2]54
[123]55__all__ = ['Path', 'PathSyntaxError']
[517]56__docformat__ = 'restructuredtext en'
[2]57
58
[133]59class Axis(object):
60    """Defines constants for the various supported XPath axes."""
61
62    ATTRIBUTE = 'attribute'
63    CHILD = 'child'
64    DESCENDANT = 'descendant'
65    DESCENDANT_OR_SELF = 'descendant-or-self'
66    SELF = 'self'
67
[1031]68    @classmethod
[133]69    def forname(cls, name):
70        """Return the axis constant for the given name, or `None` if no such
71        axis was defined.
72        """
73        return getattr(cls, name.upper().replace('-', '_'), None)
74
75
76ATTRIBUTE = Axis.ATTRIBUTE
77CHILD = Axis.CHILD
78DESCENDANT = Axis.DESCENDANT
79DESCENDANT_OR_SELF = Axis.DESCENDANT_OR_SELF
80SELF = Axis.SELF
81
82
[1025]83class GenericStrategy(object):
84
85    @classmethod
86    def supports(cls, path):
87        return True
88
89    def __init__(self, path):
90        self.path = path
91
92    def test(self, ignore_context):
93        p = self.path
94        if ignore_context:
95            if p[0][0] is ATTRIBUTE:
96                steps = [_DOTSLASHSLASH] + p
97            else:
98                steps = [(DESCENDANT_OR_SELF, p[0][1], p[0][2])] + p[1:]
99        elif p[0][0] is CHILD or p[0][0] is ATTRIBUTE \
100                or p[0][0] is DESCENDANT:
101            steps = [_DOTSLASH] + p
102        else:
103            steps = p
104
105        # for node it contains all positions of xpath expression
106        # where its child should start checking for matches
107        # with list of corresponding context counters
108        # there can be many of them, because position that is from
109        # descendant-like axis can be achieved from different nodes
110        # for example <a><a><b/></a></a> should match both //a//b[1]
111        # and //a//b[2]
112        # positions always form increasing sequence (invariant)
113        stack = [[(0, [[]])]]
114
115        def _test(event, namespaces, variables, updateonly=False):
116            kind, data, pos = event[:3]
117            retval = None
118
119            # Manage the stack that tells us "where we are" in the stream
120            if kind is END:
121                if stack:
122                    stack.pop()
123                return None
124            if kind is START_NS or kind is END_NS \
125                    or kind is START_CDATA or kind is END_CDATA:
126                # should we make namespaces work?
127                return None
128
129            pos_queue = deque([(pos, cou, []) for pos, cou in stack[-1]])
130            next_pos = []
131
132            # length of real part of path - we omit attribute axis
133            real_len = len(steps) - ((steps[-1][0] == ATTRIBUTE) or 1 and 0)
134            last_checked = -1
135
136            # places where we have to check for match, are these
137            # provided by parent
138            while pos_queue:
139                x, pcou, mcou = pos_queue.popleft()
140                axis, nodetest, predicates = steps[x]
141
142                # we need to push descendant-like positions from parent
143                # further
144                if (axis is DESCENDANT or axis is DESCENDANT_OR_SELF) and pcou:
145                    if next_pos and next_pos[-1][0] == x:
146                        next_pos[-1][1].extend(pcou)
147                    else:
148                        next_pos.append((x, pcou))
149
150                # nodetest first
151                if not nodetest(kind, data, pos, namespaces, variables):
152                    continue
153
154                # counters packs that were already bad
155                missed = set()
156                counters_len = len(pcou) + len(mcou)
157
158                # number of counters - we have to create one
159                # for every context position based predicate
160                cnum = 0
161
162                # tells if we have match with position x
163                matched = True
164
165                if predicates:
166                    for predicate in predicates:
167                        pretval = predicate(kind, data, pos,
168                                            namespaces,
169                                            variables)
170                        if type(pretval) is float: # FIXME <- need to check
171                                                   # this for other types that
172                                                   # can be coerced to float
173
174                            # each counter pack needs to be checked
175                            for i, cou in enumerate(chain(pcou, mcou)):
176                                # it was bad before
177                                if i in missed:
178                                    continue
179
180                                if len(cou) < cnum + 1:
181                                    cou.append(0)
182                                cou[cnum] += 1 
183
184                                # it is bad now
185                                if cou[cnum] != int(pretval):
186                                    missed.add(i)
187
188                            # none of counters pack was good
189                            if len(missed) == counters_len:
190                                pretval = False
191                            cnum += 1
192
193                        if not pretval:
194                             matched = False
195                             break
196
197                if not matched:
198                    continue
199
200                # counter for next position with current node as context node
201                child_counter = []
202
203                if x + 1 == real_len:
204                    # we reached end of expression, because x + 1
205                    # is equal to the length of expression
206                    matched = True
207                    axis, nodetest, predicates = steps[-1]
208                    if axis is ATTRIBUTE:
209                        matched = nodetest(kind, data, pos, namespaces,
210                                           variables)
211                    if matched:
212                        retval = matched
213                else:
214                    next_axis = steps[x + 1][0]
215
216                    # if next axis allows matching self we have
217                    # to add next position to our queue
218                    if next_axis is DESCENDANT_OR_SELF or next_axis is SELF:
219                        if not pos_queue or pos_queue[0][0] > x + 1:
220                            pos_queue.appendleft((x + 1, [], [child_counter]))
221                        else:
222                            pos_queue[0][2].append(child_counter)
223
224                    # if axis is not self we have to add it to child's list
225                    if next_axis is not SELF:
226                        next_pos.append((x + 1, [child_counter]))
227
228            if kind is START:
229                stack.append(next_pos)
230
231            return retval
232
233        return _test
234
235
236class SimplePathStrategy(object):
237    """Strategy for path with only local names, attributes and text nodes."""
238
239    @classmethod
240    def supports(cls, path):
241        if path[0][0] is ATTRIBUTE:
242            return False
243        allowed_tests = (LocalNameTest, CommentNodeTest, TextNodeTest)
244        for _, nodetest, predicates in path:
245            if predicates:
246                return False
247            if not isinstance(nodetest, allowed_tests):
248                return False
249        return True
250
251    def __init__(self, path):
252        # fragments is list of tuples (fragment, pi, attr, self_beginning)
253        # fragment is list of nodetests for fragment of path with only
254        # child:: axes between
255        # pi is KMP partial match table for this fragment
256        # attr is attribute nodetest if fragment ends with @ and None otherwise
257        # self_beginning is True if axis for first fragment element
258        # was self (first fragment) or descendant-or-self (farther fragment)
259        self.fragments = []
260
261        self_beginning = False
262        fragment = []
263
264        def nodes_equal(node1, node2):
265            """Tests if two node tests are equal"""
[1083]266            if type(node1) is not type(node2):
[1025]267                return False
[1083]268            if type(node1) == LocalNameTest:
[1025]269                return node1.name == node2.name
270            return True
271
272        def calculate_pi(f):
273            """KMP prefix calculation for table"""
274            # the indexes in prefix table are shifted by one
275            # in comparision with common implementations
276            # pi[i] = NORMAL_PI[i + 1]
277            if len(f) == 0:
278                return []
279            pi = [0]
280            s = 0
[1077]281            for i in range(1, len(f)):
[1025]282                while s > 0 and not nodes_equal(f[s], f[i]):
283                    s = pi[s-1]
284                if nodes_equal(f[s], f[i]):
285                    s += 1
286                pi.append(s)
287            return pi
288
289        for axis in path:
290            if axis[0] is SELF:
291                if len(fragment) != 0:
292                    # if element is not first in fragment it has to be
293                    # the same as previous one
294                    # for example child::a/self::b is always wrong
295                    if axis[1] != fragment[-1][1]:
296                        self.fragments = None
297                        return
298                else:
299                    self_beginning = True
300                    fragment.append(axis[1])
301            elif axis[0] is CHILD:
302                fragment.append(axis[1])
303            elif axis[0] is ATTRIBUTE:
304                pi = calculate_pi(fragment)
305                self.fragments.append((fragment, pi, axis[1], self_beginning))
306                # attribute has always to be at the end, so we can jump out
307                return
308            else:
309                pi = calculate_pi(fragment)
310                self.fragments.append((fragment, pi, None, self_beginning))
311                fragment = [axis[1]]
312                if axis[0] is DESCENDANT:
313                    self_beginning = False
314                else: # DESCENDANT_OR_SELF
315                    self_beginning = True
316        pi = calculate_pi(fragment)
317        self.fragments.append((fragment, pi, None, self_beginning))
318
319    def test(self, ignore_context):
320        # stack of triples (fid, p, ic)
321        # fid is index of current fragment
322        # p is position in this fragment
323        # ic is if we ignore context in this fragment
324        stack = []
325        stack_push = stack.append
326        stack_pop = stack.pop
327        frags = self.fragments
328        frags_len = len(frags)
329
330        def _test(event, namespaces, variables, updateonly=False):
331            # expression found impossible during init
332            if frags is None:
333                return None
334
335            kind, data, pos = event[:3]
336
337            # skip events we don't care about
338            if kind is END:
339                if stack:
340                    stack_pop()
341                return None
342            if kind is START_NS or kind is END_NS \
343                    or kind is START_CDATA or kind is END_CDATA:
344                return None
345
346            if not stack:
347                # root node, nothing on stack, special case
348                fid = 0
349                # skip empty fragments (there can be actually only one)
350                while not frags[fid][0]:
351                    fid += 1
352                p = 0
353                # empty fragment means descendant node at beginning
354                ic = ignore_context or (fid > 0)
355
356                # expression can match first node, if first axis is self::,
357                # descendant-or-self:: or if ignore_context is True and
358                # axis is not descendant::
359                if not frags[fid][3] and (not ignore_context or fid > 0):
360                    # axis is not self-beggining, we have to skip this node
361                    stack_push((fid, p, ic))
362                    return None
363            else:
364                # take position of parent
365                fid, p, ic = stack[-1]
366
367            if fid is not None and not ic:
368                # fragment not ignoring context - we can't jump back
369                frag, pi, attrib, _ = frags[fid]
370                frag_len = len(frag)
371
372                if p == frag_len:
373                    # that probably means empty first fragment
374                    pass
375                elif frag[p](kind, data, pos, namespaces, variables):
376                    # match, so we can go further
377                    p += 1
378                else:
379                    # not matched, so there will be no match in subtree
380                    fid, p = None, None
381
382                if p == frag_len and fid + 1 != frags_len:
383                    # we made it to end of fragment, we can go to following
384                    fid += 1
385                    p = 0
386                    ic = True
387
388            if fid is None:
389                # there was no match in fragment not ignoring context
390                if kind is START:
391                    stack_push((fid, p, ic))
392                return None
393
394            if ic:
395                # we are in fragment ignoring context
396                while True:
397                    frag, pi, attrib, _ = frags[fid]
398                    frag_len = len(frag)
399
400                    # KMP new "character"
401                    while p > 0 and (p >= frag_len or not \
402                            frag[p](kind, data, pos, namespaces, variables)):
403                        p = pi[p-1]
404                    if frag[p](kind, data, pos, namespaces, variables):
405                        p += 1
406
407                    if p == frag_len:
408                        # end of fragment reached
409                        if fid + 1 == frags_len:
410                            # that was last fragment
411                            break
412                        else:
413                            fid += 1
414                            p = 0
415                            ic = True
416                            if not frags[fid][3]:
417                                # next fragment not self-beginning
418                                break
419                    else:
420                        break
421
422            if kind is START:
423                # we have to put new position on stack, for children
424
425                if not ic and fid + 1 == frags_len and p == frag_len:
426                    # it is end of the only, not context ignoring fragment
427                    # so there will be no matches in subtree
428                    stack_push((None, None, ic))
429                else:
430                    stack_push((fid, p, ic))
431
432            # have we reached the end of the last fragment?
433            if fid + 1 == frags_len and p == frag_len:
434                if attrib: # attribute ended path, return value
435                    return attrib(kind, data, pos, namespaces, variables)
436                return True
437
438            return None
439
440        return _test
441
442
443class SingleStepStrategy(object):
444
445    @classmethod
446    def supports(cls, path):
447        return len(path) == 1
448
449    def __init__(self, path):
450        self.path = path
451
452    def test(self, ignore_context):
453        steps = self.path
454        if steps[0][0] is ATTRIBUTE:
455            steps = [_DOTSLASH] + steps
456        select_attr = steps[-1][0] is ATTRIBUTE and steps[-1][1] or None
457
458        # for every position in expression stores counters' list
459        # it is used for position based predicates
460        counters = []
461        depth = [0]
462
463        def _test(event, namespaces, variables, updateonly=False):
464            kind, data, pos = event[:3]
465
466            # Manage the stack that tells us "where we are" in the stream
467            if kind is END:
468                if not ignore_context:
469                    depth[0] -= 1
470                return None
471            elif kind is START_NS or kind is END_NS \
472                    or kind is START_CDATA or kind is END_CDATA:
473                # should we make namespaces work?
474                return None
475
476            if not ignore_context:
477                outside = (steps[0][0] is SELF and depth[0] != 0) \
478                       or (steps[0][0] is CHILD and depth[0] != 1) \
479                       or (steps[0][0] is DESCENDANT and depth[0] < 1)
480                if kind is START:
481                    depth[0] += 1
482                if outside:
483                    return None
484
485            axis, nodetest, predicates = steps[0]
486            if not nodetest(kind, data, pos, namespaces, variables):
487                return None
488
489            if predicates:
490                cnum = 0
491                for predicate in predicates:
492                    pretval = predicate(kind, data, pos, namespaces, variables)
493                    if type(pretval) is float: # FIXME <- need to check this
494                                               # for other types that can be
495                                               # coerced to float
496                        if len(counters) < cnum + 1:
497                            counters.append(0)
498                        counters[cnum] += 1 
499                        if counters[cnum] != int(pretval):
500                            pretval = False
501                        cnum += 1
502                    if not pretval:
503                         return None
504
505            if select_attr:
506                return select_attr(kind, data, pos, namespaces, variables)
507
508            return True
509
510        return _test
511
512
[2]513class Path(object):
[27]514    """Implements basic XPath support on streams.
[2]515   
[1025]516    Instances of this class represent a "compiled" XPath expression, and
517    provide methods for testing the path against a stream, as well as
518    extracting a substream matching that path.
[2]519    """
520
[1025]521    STRATEGIES = (SingleStepStrategy, SimplePathStrategy, GenericStrategy)
522
[178]523    def __init__(self, text, filename=None, lineno=-1):
[27]524        """Create the path object from a string.
525       
[517]526        :param text: the path expression
[600]527        :param filename: the name of the file in which the path expression was
528                         found (used in error messages)
529        :param lineno: the line on which the expression was found
[27]530        """
[2]531        self.source = text
[178]532        self.paths = PathParser(text, filename, lineno).parse()
[1025]533        self.strategies = []
534        for path in self.paths:
535            for strategy_class in self.STRATEGIES:
536                if strategy_class.supports(path):
537                    self.strategies.append(strategy_class(path))
538                    break
539            else:
[1077]540                raise NotImplemented('No strategy found for path')
[2]541
542    def __repr__(self):
[176]543        paths = []
544        for path in self.paths:
545            steps = []
546            for axis, nodetest, predicates in path:
547                steps.append('%s::%s' % (axis, nodetest))
548                for predicate in predicates:
[281]549                    steps[-1] += '[%s]' % predicate
[176]550            paths.append('/'.join(steps))
[1083]551        return '<%s "%s">' % (type(self).__name__, '|'.join(paths))
[2]552
[281]553    def select(self, stream, namespaces=None, variables=None):
[27]554        """Returns a substream of the given stream that matches the path.
555       
556        If there are no matches, this method returns an empty stream.
557       
[287]558        >>> from genshi.input import XML
[34]559        >>> xml = XML('<root><elem><child>Text</child></elem></root>')
[66]560       
[1076]561        >>> print(Path('.//child').select(xml))
[34]562        <child>Text</child>
563       
[1076]564        >>> print(Path('.//child/text()').select(xml))
[34]565        Text
566       
[517]567        :param stream: the stream to select from
568        :param namespaces: (optional) a mapping of namespace prefixes to URIs
569        :param variables: (optional) a mapping of variable names to values
570        :return: the substream matching the path, or an empty stream
[600]571        :rtype: `Stream`
[27]572        """
[281]573        if namespaces is None:
574            namespaces = {}
575        if variables is None:
576            variables = {}
[2]577        stream = iter(stream)
[1025]578        def _generate(stream=stream, ns=namespaces, vs=variables):
579            next = stream.next
[2]580            test = self.test()
[378]581            for event in stream:
[1025]582                result = test(event, ns, vs)
[2]583                if result is True:
[378]584                    yield event
[407]585                    if event[0] is START:
586                        depth = 1
587                        while depth > 0:
[1025]588                            subevent = next()
[407]589                            if subevent[0] is START:
590                                depth += 1
591                            elif subevent[0] is END:
592                                depth -= 1
593                            yield subevent
[1025]594                            test(subevent, ns, vs, updateonly=True)
[2]595                elif result:
596                    yield result
[721]597        return Stream(_generate(),
598                      serializer=getattr(stream, 'serializer', None))
[2]599
[39]600    def test(self, ignore_context=False):
[27]601        """Returns a function that can be used to track whether the path matches
602        a specific stream event.
603       
[517]604        The function returned expects the positional arguments ``event``,
605        ``namespaces`` and ``variables``. The first is a stream event, while the
[378]606        latter two are a mapping of namespace prefixes to URIs, and a mapping
[379]607        of variable names to values, respectively. In addition, the function
[517]608        accepts an ``updateonly`` keyword argument that default to ``False``. If
609        it is set to ``True``, the function only updates its internal state,
[379]610        but does not perform any tests or return a result.
[34]611       
[281]612        If the path matches the event, the function returns the match (for
[517]613        example, a `START` or `TEXT` event.) Otherwise, it returns ``None``.
[281]614       
[287]615        >>> from genshi.input import XML
[34]616        >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>')
617        >>> test = Path('child').test()
[1025]618        >>> namespaces, variables = {}, {}
[378]619        >>> for event in xml:
[1025]620        ...     if test(event, namespaces, variables):
[1076]621        ...         print('%s %r' % (event[0], event[1]))
[1080]622        START (QName('child'), Attrs([(QName('id'), u'2')]))
[600]623       
624        :param ignore_context: if `True`, the path is interpreted like a pattern
625                               in XSLT, meaning for example that it will match
626                               at any depth
627        :return: a function that can be used to test individual events in a
628                 stream against the path
629        :rtype: ``function``
[27]630        """
[1025]631        tests = [s.test(ignore_context) for s in self.strategies]
632        if len(tests) == 1:
633            return tests[0]
[2]634
[1025]635        def _multi(event, namespaces, variables, updateonly=False):
[322]636            retval = None
[1025]637            for test in tests:
638                val = test(event, namespaces, variables, updateonly=updateonly)
639                if retval is None:
640                    retval = val
[322]641            return retval
[1025]642        return _multi
[2]643
644
[123]645class PathSyntaxError(Exception):
646    """Exception raised when an XPath expression is syntactically incorrect."""
647
648    def __init__(self, message, filename=None, lineno=-1, offset=-1):
649        if filename:
650            message = '%s (%s, line %d)' % (message, filename, lineno)
651        Exception.__init__(self, message)
652        self.filename = filename
653        self.lineno = lineno
654        self.offset = offset
655
656
[176]657class PathParser(object):
[123]658    """Tokenizes and parses an XPath expression."""
659
660    _QUOTES = (("'", "'"), ('"', '"'))
661    _TOKENS = ('::', ':', '..', '.', '//', '/', '[', ']', '()', '(', ')', '@',
[224]662               '=', '!=', '!', '|', ',', '>=', '>', '<=', '<', '$')
[206]663    _tokenize = re.compile('("[^"]*")|(\'[^\']*\')|((?:\d+)?\.\d+)|(%s)|([^%s\s]+)|\s+' % (
[123]664                           '|'.join([re.escape(t) for t in _TOKENS]),
665                           ''.join([re.escape(t[0]) for t in _TOKENS]))).findall
666
[178]667    def __init__(self, text, filename=None, lineno=-1):
668        self.filename = filename
669        self.lineno = lineno
[1077]670        self.tokens = [t for t in [dqstr or sqstr or number or token or name
671                                   for dqstr, sqstr, number, token, name in
672                                   self._tokenize(text)] if t]
[123]673        self.pos = 0
674
675    # Tokenizer
676
[1031]677    @property
678    def at_end(self):
679        return self.pos == len(self.tokens) - 1
[123]680
[1031]681    @property
682    def cur_token(self):
683        return self.tokens[self.pos]
684
[123]685    def next_token(self):
686        self.pos += 1
687        return self.tokens[self.pos]
688
689    def peek_token(self):
690        if not self.at_end:
691            return self.tokens[self.pos + 1]
692        return None
693
694    # Recursive descent parser
695
696    def parse(self):
697        """Parses the XPath expression and returns a list of location path
698        tests.
699       
700        For union expressions (such as `*|text()`), this function returns one
701        test for each operand in the union. For patch expressions that don't
702        use the union operator, the function always returns a list of size 1.
703       
704        Each path test in turn is a sequence of tests that correspond to the
[129]705        location steps, each tuples of the form `(axis, testfunc, predicates)`
[123]706        """
707        paths = [self._location_path()]
708        while self.cur_token == '|':
709            self.next_token()
710            paths.append(self._location_path())
711        if not self.at_end:
712            raise PathSyntaxError('Unexpected token %r after end of expression'
[178]713                                  % self.cur_token, self.filename, self.lineno)
[123]714        return paths
715
716    def _location_path(self):
717        steps = []
718        while True:
[271]719            if self.cur_token.startswith('/'):
[1025]720                if not steps:
721                    if self.cur_token == '//':
722                        # hack to make //* match every node - also root
723                        self.next_token()
724                        axis, nodetest, predicates = self._location_step()
725                        steps.append((DESCENDANT_OR_SELF, nodetest, 
726                                      predicates))
727                        if self.at_end or not self.cur_token.startswith('/'):
728                            break
729                        continue
730                    else:
731                        raise PathSyntaxError('Absolute location paths not '
732                                              'supported', self.filename,
733                                              self.lineno)
734                elif self.cur_token == '//':
[271]735                    steps.append((DESCENDANT_OR_SELF, NodeTest(), []))
[129]736                self.next_token()
737
[176]738            axis, nodetest, predicates = self._location_step()
739            if not axis:
[186]740                axis = CHILD
[176]741            steps.append((axis, nodetest, predicates))
[129]742            if self.at_end or not self.cur_token.startswith('/'):
[123]743                break
[129]744
[123]745        return steps
746
747    def _location_step(self):
748        if self.cur_token == '@':
[133]749            axis = ATTRIBUTE
[123]750            self.next_token()
[129]751        elif self.cur_token == '.':
[133]752            axis = SELF
[176]753        elif self.cur_token == '..':
[178]754            raise PathSyntaxError('Unsupported axis "parent"', self.filename,
755                                  self.lineno)
[129]756        elif self.peek_token() == '::':
[133]757            axis = Axis.forname(self.cur_token)
758            if axis is None:
[178]759                raise PathSyntaxError('Unsupport axis "%s"' % axis,
760                                      self.filename, self.lineno)
[129]761            self.next_token()
762            self.next_token()
[123]763        else:
[176]764            axis = None
765        nodetest = self._node_test(axis or CHILD)
[129]766        predicates = []
[123]767        while self.cur_token == '[':
[129]768            predicates.append(self._predicate())
[176]769        return axis, nodetest, predicates
[123]770
771    def _node_test(self, axis=None):
[281]772        test = prefix = None
773        next_token = self.peek_token()
774        if next_token in ('(', '()'): # Node type test
[123]775            test = self._node_type()
776
[281]777        elif next_token == ':': # Namespace prefix
778            prefix = self.cur_token
779            self.next_token()
780            localname = self.next_token()
781            if localname == '*':
782                test = QualifiedPrincipalTypeTest(axis, prefix)
783            else:
784                test = QualifiedNameTest(axis, prefix, localname)
785
[123]786        else: # Name test
[176]787            if self.cur_token == '*':
788                test = PrincipalTypeTest(axis)
789            elif self.cur_token == '.':
790                test = NodeTest()
[123]791            else:
[176]792                test = LocalNameTest(axis, self.cur_token)
[123]793
794        if not self.at_end:
795            self.next_token()
796        return test
797
798    def _node_type(self):
799        name = self.cur_token
800        self.next_token()
[176]801
802        args = []
803        if self.cur_token != '()':
804            # The processing-instruction() function optionally accepts the
805            # name of the PI as argument, which must be a literal string
806            self.next_token() # (
807            if self.cur_token != ')':
808                string = self.cur_token
809                if (string[0], string[-1]) in self._QUOTES:
810                    string = string[1:-1]
811                args.append(string)
812
813        cls = _nodetest_map.get(name)
814        if not cls:
[178]815            raise PathSyntaxError('%s() not allowed here' % name, self.filename,
816                                  self.lineno)
[176]817        return cls(*args)
[123]818
819    def _predicate(self):
820        assert self.cur_token == '['
821        self.next_token()
[129]822        expr = self._or_expr()
[150]823        if self.cur_token != ']':
824            raise PathSyntaxError('Expected "]" to close predicate, '
[178]825                                  'but found "%s"' % self.cur_token,
826                                  self.filename, self.lineno)
[129]827        if not self.at_end:
828            self.next_token()
829        return expr
[123]830
831    def _or_expr(self):
832        expr = self._and_expr()
833        while self.cur_token == 'or':
834            self.next_token()
[176]835            expr = OrOperator(expr, self._and_expr())
[123]836        return expr
837
838    def _and_expr(self):
839        expr = self._equality_expr()
840        while self.cur_token == 'and':
841            self.next_token()
[176]842            expr = AndOperator(expr, self._equality_expr())
[123]843        return expr
844
845    def _equality_expr(self):
[204]846        expr = self._relational_expr()
[123]847        while self.cur_token in ('=', '!='):
[204]848            op = _operator_map[self.cur_token]
[123]849            self.next_token()
[204]850            expr = op(expr, self._relational_expr())
851        return expr
852
853    def _relational_expr(self):
[620]854        expr = self._sub_expr()
[204]855        while self.cur_token in ('>', '>=', '<', '>='):
856            op = _operator_map[self.cur_token]
857            self.next_token()
[620]858            expr = op(expr, self._sub_expr())
[123]859        return expr
860
[620]861    def _sub_expr(self):
862        token = self.cur_token
863        if token != '(':
864            return self._primary_expr()
865        self.next_token()
866        expr = self._or_expr()
867        if self.cur_token != ')':
868            raise PathSyntaxError('Expected ")" to close sub-expression, '
869                                  'but found "%s"' % self.cur_token,
870                                  self.filename, self.lineno)
871        self.next_token()
872        return expr
873
[123]874    def _primary_expr(self):
875        token = self.cur_token
876        if len(token) > 1 and (token[0], token[-1]) in self._QUOTES:
877            self.next_token()
[176]878            return StringLiteral(token[1:-1])
[206]879        elif token[0].isdigit() or token[0] == '.':
[123]880            self.next_token()
[622]881            return NumberLiteral(as_float(token))
[224]882        elif token == '$':
883            token = self.next_token()
884            self.next_token()
885            return VariableReference(token)
[150]886        elif not self.at_end and self.peek_token().startswith('('):
[197]887            return self._function_call()
[123]888        else:
889            axis = None
890            if token == '@':
[133]891                axis = ATTRIBUTE
[123]892                self.next_token()
893            return self._node_test(axis)
[176]894
[197]895    def _function_call(self):
896        name = self.cur_token
897        if self.next_token() == '()':
898            args = []
899        else:
900            assert self.cur_token == '('
901            self.next_token()
902            args = [self._or_expr()]
903            while self.cur_token == ',':
904                self.next_token()
905                args.append(self._or_expr())
906            if not self.cur_token == ')':
907                raise PathSyntaxError('Expected ")" to close function argument '
908                                      'list, but found "%s"' % self.cur_token,
909                                      self.filename, self.lineno)
910        self.next_token()
911        cls = _function_map.get(name)
912        if not cls:
913            raise PathSyntaxError('Unsupported function "%s"' % name,
914                                  self.filename, self.lineno)
915        return cls(*args)
[176]916
[197]917
[622]918# Type coercion
919
920def as_scalar(value):
921    """Convert value to a scalar. If a single element Attrs() object is passed
922    the value of the single attribute will be returned."""
923    if isinstance(value, Attrs):
924        assert len(value) == 1
925        return value[0][1]
926    else:
927        return value
928
929def as_float(value):
930    # FIXME - if value is a bool it will be coerced to 0.0 and consequently
931    # compared as a float. This is probably not ideal.
932    return float(as_scalar(value))
933
934def as_long(value):
935    return long(as_scalar(value))
936
937def as_string(value):
938    value = as_scalar(value)
939    if value is False:
[1075]940        return ''
[622]941    return unicode(value)
942
943def as_bool(value):
944    return bool(as_scalar(value))
945
946
[176]947# Node tests
948
949class PrincipalTypeTest(object):
[203]950    """Node test that matches any event with the given principal type."""
[176]951    __slots__ = ['principal_type']
952    def __init__(self, principal_type):
953        self.principal_type = principal_type
[281]954    def __call__(self, kind, data, pos, namespaces, variables):
[176]955        if kind is START:
956            if self.principal_type is ATTRIBUTE:
957                return data[1] or None
958            else:
959                return True
960    def __repr__(self):
961        return '*'
962
[281]963class QualifiedPrincipalTypeTest(object):
964    """Node test that matches any event with the given principal type in a
965    specific namespace."""
966    __slots__ = ['principal_type', 'prefix']
967    def __init__(self, principal_type, prefix):
968        self.principal_type = principal_type
969        self.prefix = prefix
970    def __call__(self, kind, data, pos, namespaces, variables):
971        namespace = Namespace(namespaces.get(self.prefix))
972        if kind is START:
973            if self.principal_type is ATTRIBUTE and data[1]:
974                return Attrs([(name, value) for name, value in data[1]
975                              if name in namespace]) or None
976            else:
977                return data[0] in namespace
978    def __repr__(self):
979        return '%s:*' % self.prefix
980
[176]981class LocalNameTest(object):
[444]982    """Node test that matches any event with the given principal type and
[203]983    local name.
984    """
[176]985    __slots__ = ['principal_type', 'name']
986    def __init__(self, principal_type, name):
987        self.principal_type = principal_type
988        self.name = name
[281]989    def __call__(self, kind, data, pos, namespaces, variables):
[176]990        if kind is START:
991            if self.principal_type is ATTRIBUTE and self.name in data[1]:
[622]992                return Attrs([(self.name, data[1].get(self.name))])
[176]993            else:
994                return data[0].localname == self.name
995    def __repr__(self):
996        return self.name
997
[281]998class QualifiedNameTest(object):
[444]999    """Node test that matches any event with the given principal type and
[281]1000    qualified name.
1001    """
1002    __slots__ = ['principal_type', 'prefix', 'name']
1003    def __init__(self, principal_type, prefix, name):
1004        self.principal_type = principal_type
1005        self.prefix = prefix
1006        self.name = name
1007    def __call__(self, kind, data, pos, namespaces, variables):
1008        qname = QName('%s}%s' % (namespaces.get(self.prefix), self.name))
1009        if kind is START:
1010            if self.principal_type is ATTRIBUTE and qname in data[1]:
[1269]1011                return Attrs([(qname, data[1].get(qname))])
[281]1012            else:
1013                return data[0] == qname
1014    def __repr__(self):
1015        return '%s:%s' % (self.prefix, self.name)
1016
[176]1017class CommentNodeTest(object):
[203]1018    """Node test that matches any comment events."""
[176]1019    __slots__ = []
[281]1020    def __call__(self, kind, data, pos, namespaces, variables):
[407]1021        return kind is COMMENT
[176]1022    def __repr__(self):
1023        return 'comment()'
1024
1025class NodeTest(object):
[203]1026    """Node test that matches any node."""
[176]1027    __slots__ = []
[281]1028    def __call__(self, kind, data, pos, namespaces, variables):
[176]1029        if kind is START:
1030            return True
1031        return kind, data, pos
1032    def __repr__(self):
1033        return 'node()'
1034
1035class ProcessingInstructionNodeTest(object):
[203]1036    """Node test that matches any processing instruction event."""
[176]1037    __slots__ = ['target']
1038    def __init__(self, target=None):
1039        self.target = target
[281]1040    def __call__(self, kind, data, pos, namespaces, variables):
[407]1041        return kind is PI and (not self.target or data[0] == self.target)
[176]1042    def __repr__(self):
1043        arg = ''
1044        if self.target:
1045            arg = '"' + self.target + '"'
1046        return 'processing-instruction(%s)' % arg
1047
1048class TextNodeTest(object):
[203]1049    """Node test that matches any text event."""
[176]1050    __slots__ = []
[281]1051    def __call__(self, kind, data, pos, namespaces, variables):
[407]1052        return kind is TEXT
[176]1053    def __repr__(self):
1054        return 'text()'
1055
1056_nodetest_map = {'comment': CommentNodeTest, 'node': NodeTest,
1057                 'processing-instruction': ProcessingInstructionNodeTest,
1058                 'text': TextNodeTest}
1059
1060# Functions
1061
[197]1062class Function(object):
1063    """Base class for function nodes in XPath expressions."""
1064
1065class BooleanFunction(Function):
[203]1066    """The `boolean` function, which converts its argument to a boolean
1067    value.
1068    """
[197]1069    __slots__ = ['expr']
[1025]1070    _return_type = bool
[197]1071    def __init__(self, expr):
1072        self.expr = expr
[281]1073    def __call__(self, kind, data, pos, namespaces, variables):
1074        val = self.expr(kind, data, pos, namespaces, variables)
[622]1075        return as_bool(val)
[197]1076    def __repr__(self):
1077        return 'boolean(%r)' % self.expr
1078
1079class CeilingFunction(Function):
[203]1080    """The `ceiling` function, which returns the nearest lower integer number
1081    for the given number.
1082    """
[197]1083    __slots__ = ['number']
1084    def __init__(self, number):
1085        self.number = number
[281]1086    def __call__(self, kind, data, pos, namespaces, variables):
1087        number = self.number(kind, data, pos, namespaces, variables)
[622]1088        return ceil(as_float(number))
[197]1089    def __repr__(self):
1090        return 'ceiling(%r)' % self.number
1091
1092class ConcatFunction(Function):
[203]1093    """The `concat` function, which concatenates (joins) the variable number of
1094    strings it gets as arguments.
1095    """
[197]1096    __slots__ = ['exprs']
1097    def __init__(self, *exprs):
1098        self.exprs = exprs
[281]1099    def __call__(self, kind, data, pos, namespaces, variables):
[197]1100        strings = []
[281]1101        for item in [expr(kind, data, pos, namespaces, variables)
1102                     for expr in self.exprs]:
[622]1103            strings.append(as_string(item))
[1075]1104        return ''.join(strings)
[197]1105    def __repr__(self):
[213]1106        return 'concat(%s)' % ', '.join([repr(expr) for expr in self.exprs])
[197]1107
1108class ContainsFunction(Function):
[203]1109    """The `contains` function, which returns whether a string contains a given
1110    substring.
1111    """
1112    __slots__ = ['string1', 'string2']
[197]1113    def __init__(self, string1, string2):
1114        self.string1 = string1
1115        self.string2 = string2
[281]1116    def __call__(self, kind, data, pos, namespaces, variables):
1117        string1 = self.string1(kind, data, pos, namespaces, variables)
1118        string2 = self.string2(kind, data, pos, namespaces, variables)
[622]1119        return as_string(string2) in as_string(string1)
[197]1120    def __repr__(self):
1121        return 'contains(%r, %r)' % (self.string1, self.string2)
1122
[642]1123class MatchesFunction(Function):
1124    """The `matches` function, which returns whether a string matches a regular
1125    expression.
1126    """
1127    __slots__ = ['string1', 'string2']
1128    flag_mapping = {'s': re.S, 'm': re.M, 'i': re.I, 'x': re.X}
1129
1130    def __init__(self, string1, string2, flags=''):
1131        self.string1 = string1
1132        self.string2 = string2
1133        self.flags = self._map_flags(flags)
1134    def __call__(self, kind, data, pos, namespaces, variables):
1135        string1 = as_string(self.string1(kind, data, pos, namespaces, variables))
1136        string2 = as_string(self.string2(kind, data, pos, namespaces, variables))
1137        return re.search(string2, string1, self.flags)
1138    def _map_flags(self, flags):
[707]1139        return reduce(operator.or_,
[642]1140                      [self.flag_map[flag] for flag in flags], re.U)
1141    def __repr__(self):
1142        return 'contains(%r, %r)' % (self.string1, self.string2)
1143
[197]1144class FalseFunction(Function):
[203]1145    """The `false` function, which always returns the boolean `false` value."""
[176]1146    __slots__ = []
[281]1147    def __call__(self, kind, data, pos, namespaces, variables):
[197]1148        return False
1149    def __repr__(self):
1150        return 'false()'
1151
1152class FloorFunction(Function):
[203]1153    """The `ceiling` function, which returns the nearest higher integer number
1154    for the given number.
1155    """
[197]1156    __slots__ = ['number']
1157    def __init__(self, number):
1158        self.number = number
[281]1159    def __call__(self, kind, data, pos, namespaces, variables):
1160        number = self.number(kind, data, pos, namespaces, variables)
[622]1161        return floor(as_float(number))
[197]1162    def __repr__(self):
1163        return 'floor(%r)' % self.number
1164
1165class LocalNameFunction(Function):
[203]1166    """The `local-name` function, which returns the local name of the current
1167    element.
1168    """
[197]1169    __slots__ = []
[281]1170    def __call__(self, kind, data, pos, namespaces, variables):
[176]1171        if kind is START:
[293]1172            return data[0].localname
[176]1173    def __repr__(self):
1174        return 'local-name()'
1175
[197]1176class NameFunction(Function):
[203]1177    """The `name` function, which returns the qualified name of the current
1178    element.
1179    """
[176]1180    __slots__ = []
[281]1181    def __call__(self, kind, data, pos, namespaces, variables):
[176]1182        if kind is START:
[293]1183            return data[0]
[176]1184    def __repr__(self):
1185        return 'name()'
1186
[197]1187class NamespaceUriFunction(Function):
[203]1188    """The `namespace-uri` function, which returns the namespace URI of the
1189    current element.
1190    """
[176]1191    __slots__ = []
[281]1192    def __call__(self, kind, data, pos, namespaces, variables):
[176]1193        if kind is START:
[293]1194            return data[0].namespace
[176]1195    def __repr__(self):
1196        return 'namespace-uri()'
1197
[197]1198class NotFunction(Function):
[203]1199    """The `not` function, which returns the negated boolean value of its
1200    argument.
1201    """
[176]1202    __slots__ = ['expr']
1203    def __init__(self, expr):
1204        self.expr = expr
[281]1205    def __call__(self, kind, data, pos, namespaces, variables):
[622]1206        return not as_bool(self.expr(kind, data, pos, namespaces, variables))
[176]1207    def __repr__(self):
1208        return 'not(%s)' % self.expr
1209
[197]1210class NormalizeSpaceFunction(Function):
[203]1211    """The `normalize-space` function, which removes leading and trailing
1212    whitespace in the given string, and replaces multiple adjacent whitespace
1213    characters inside the string with a single space.
1214    """
[197]1215    __slots__ = ['expr']
1216    _normalize = re.compile(r'\s{2,}').sub
1217    def __init__(self, expr):
1218        self.expr = expr
[281]1219    def __call__(self, kind, data, pos, namespaces, variables):
1220        string = self.expr(kind, data, pos, namespaces, variables)
[622]1221        return self._normalize(' ', as_string(string).strip())
[197]1222    def __repr__(self):
1223        return 'normalize-space(%s)' % repr(self.expr)
[176]1224
[197]1225class NumberFunction(Function):
[203]1226    """The `number` function that converts its argument to a number."""
[197]1227    __slots__ = ['expr']
1228    def __init__(self, expr):
1229        self.expr = expr
[281]1230    def __call__(self, kind, data, pos, namespaces, variables):
1231        val = self.expr(kind, data, pos, namespaces, variables)
[622]1232        return as_float(val)
[197]1233    def __repr__(self):
1234        return 'number(%r)' % self.expr
1235
[204]1236class RoundFunction(Function):
1237    """The `round` function, which returns the nearest integer number for the
1238    given number.
1239    """
1240    __slots__ = ['number']
1241    def __init__(self, number):
1242        self.number = number
[281]1243    def __call__(self, kind, data, pos, namespaces, variables):
1244        number = self.number(kind, data, pos, namespaces, variables)
[622]1245        return round(as_float(number))
[204]1246    def __repr__(self):
1247        return 'round(%r)' % self.number
1248
[197]1249class StartsWithFunction(Function):
[203]1250    """The `starts-with` function that returns whether one string starts with
1251    a given substring.
1252    """
[197]1253    __slots__ = ['string1', 'string2']
1254    def __init__(self, string1, string2):
[347]1255        self.string1 = string1
[197]1256        self.string2 = string2
[281]1257    def __call__(self, kind, data, pos, namespaces, variables):
1258        string1 = self.string1(kind, data, pos, namespaces, variables)
1259        string2 = self.string2(kind, data, pos, namespaces, variables)
[622]1260        return as_string(string1).startswith(as_string(string2))
[197]1261    def __repr__(self):
1262        return 'starts-with(%r, %r)' % (self.string1, self.string2)
1263
1264class StringLengthFunction(Function):
[203]1265    """The `string-length` function that returns the length of the given
1266    string.
1267    """
[197]1268    __slots__ = ['expr']
1269    def __init__(self, expr):
1270        self.expr = expr
[281]1271    def __call__(self, kind, data, pos, namespaces, variables):
1272        string = self.expr(kind, data, pos, namespaces, variables)
[622]1273        return len(as_string(string))
[197]1274    def __repr__(self):
1275        return 'string-length(%r)' % self.expr
1276
1277class SubstringFunction(Function):
[203]1278    """The `substring` function that returns the part of a string that starts
1279    at the given offset, and optionally limited to the given length.
1280    """
[197]1281    __slots__ = ['string', 'start', 'length']
1282    def __init__(self, string, start, length=None):
1283        self.string = string
1284        self.start = start
1285        self.length = length
[281]1286    def __call__(self, kind, data, pos, namespaces, variables):
1287        string = self.string(kind, data, pos, namespaces, variables)
1288        start = self.start(kind, data, pos, namespaces, variables)
[197]1289        length = 0
1290        if self.length is not None:
[281]1291            length = self.length(kind, data, pos, namespaces, variables)
[622]1292        return string[as_long(start):len(as_string(string)) - as_long(length)]
[197]1293    def __repr__(self):
1294        if self.length is not None:
1295            return 'substring(%r, %r, %r)' % (self.string, self.start,
1296                                              self.length)
1297        else:
1298            return 'substring(%r, %r)' % (self.string, self.start)
1299
1300class SubstringAfterFunction(Function):
[203]1301    """The `substring-after` function that returns the part of a string that
1302    is found after the given substring.
1303    """
[197]1304    __slots__ = ['string1', 'string2']
1305    def __init__(self, string1, string2):
1306        self.string1 = string1
1307        self.string2 = string2
[281]1308    def __call__(self, kind, data, pos, namespaces, variables):
[622]1309        string1 = as_string(self.string1(kind, data, pos, namespaces, variables))
1310        string2 = as_string(self.string2(kind, data, pos, namespaces, variables))
[197]1311        index = string1.find(string2)
1312        if index >= 0:
1313            return string1[index + len(string2):]
[1075]1314        return ''
[197]1315    def __repr__(self):
1316        return 'substring-after(%r, %r)' % (self.string1, self.string2)
1317
1318class SubstringBeforeFunction(Function):
[203]1319    """The `substring-before` function that returns the part of a string that
1320    is found before the given substring.
1321    """
[197]1322    __slots__ = ['string1', 'string2']
1323    def __init__(self, string1, string2):
1324        self.string1 = string1
1325        self.string2 = string2
[281]1326    def __call__(self, kind, data, pos, namespaces, variables):
[622]1327        string1 = as_string(self.string1(kind, data, pos, namespaces, variables))
1328        string2 = as_string(self.string2(kind, data, pos, namespaces, variables))
[197]1329        index = string1.find(string2)
1330        if index >= 0:
1331            return string1[:index]
[1075]1332        return ''
[197]1333    def __repr__(self):
1334        return 'substring-after(%r, %r)' % (self.string1, self.string2)
1335
1336class TranslateFunction(Function):
[203]1337    """The `translate` function that translates a set of characters in a
1338    string to target set of characters.
1339    """
[197]1340    __slots__ = ['string', 'fromchars', 'tochars']
1341    def __init__(self, string, fromchars, tochars):
1342        self.string = string
1343        self.fromchars = fromchars
1344        self.tochars = tochars
[281]1345    def __call__(self, kind, data, pos, namespaces, variables):
[622]1346        string = as_string(self.string(kind, data, pos, namespaces, variables))
1347        fromchars = as_string(self.fromchars(kind, data, pos, namespaces, variables))
1348        tochars = as_string(self.tochars(kind, data, pos, namespaces, variables))
[197]1349        table = dict(zip([ord(c) for c in fromchars],
1350                         [ord(c) for c in tochars]))
1351        return string.translate(table)
1352    def __repr__(self):
1353        return 'translate(%r, %r, %r)' % (self.string, self.fromchars,
1354                                          self.tochars)
1355
1356class TrueFunction(Function):
[203]1357    """The `true` function, which always returns the boolean `true` value."""
[197]1358    __slots__ = []
[281]1359    def __call__(self, kind, data, pos, namespaces, variables):
[197]1360        return True
1361    def __repr__(self):
1362        return 'true()'
1363
1364_function_map = {'boolean': BooleanFunction, 'ceiling': CeilingFunction,
1365                 'concat': ConcatFunction, 'contains': ContainsFunction,
[642]1366                 'matches': MatchesFunction, 'false': FalseFunction, 'floor':
1367                 FloorFunction, 'local-name': LocalNameFunction, 'name':
1368                 NameFunction, 'namespace-uri': NamespaceUriFunction,
[197]1369                 'normalize-space': NormalizeSpaceFunction, 'not': NotFunction,
[204]1370                 'number': NumberFunction, 'round': RoundFunction,
[642]1371                 'starts-with': StartsWithFunction, 'string-length':
1372                 StringLengthFunction, 'substring': SubstringFunction,
1373                 'substring-after': SubstringAfterFunction, 'substring-before':
1374                 SubstringBeforeFunction, 'translate': TranslateFunction,
1375                 'true': TrueFunction}
[197]1376
[224]1377# Literals & Variables
[176]1378
[197]1379class Literal(object):
1380    """Abstract base class for literal nodes."""
1381
1382class StringLiteral(Literal):
[203]1383    """A string literal node."""
[176]1384    __slots__ = ['text']
1385    def __init__(self, text):
1386        self.text = text
[281]1387    def __call__(self, kind, data, pos, namespaces, variables):
[285]1388        return self.text
[176]1389    def __repr__(self):
1390        return '"%s"' % self.text
1391
[197]1392class NumberLiteral(Literal):
[203]1393    """A number literal node."""
[176]1394    __slots__ = ['number']
1395    def __init__(self, number):
1396        self.number = number
[281]1397    def __call__(self, kind, data, pos, namespaces, variables):
[285]1398        return self.number
[176]1399    def __repr__(self):
1400        return str(self.number)
1401
[224]1402class VariableReference(Literal):
1403    """A variable reference node."""
1404    __slots__ = ['name']
1405    def __init__(self, name):
1406        self.name = name
[281]1407    def __call__(self, kind, data, pos, namespaces, variables):
[285]1408        return variables.get(self.name)
[224]1409    def __repr__(self):
[271]1410        return str(self.name)
[224]1411
[176]1412# Operators
1413
1414class AndOperator(object):
[203]1415    """The boolean operator `and`."""
[176]1416    __slots__ = ['lval', 'rval']
1417    def __init__(self, lval, rval):
1418        self.lval = lval
1419        self.rval = rval
[281]1420    def __call__(self, kind, data, pos, namespaces, variables):
[622]1421        lval = as_bool(self.lval(kind, data, pos, namespaces, variables))
[203]1422        if not lval:
[176]1423            return False
[281]1424        rval = self.rval(kind, data, pos, namespaces, variables)
[622]1425        return as_bool(rval)
[176]1426    def __repr__(self):
1427        return '%s and %s' % (self.lval, self.rval)
1428
1429class EqualsOperator(object):
[203]1430    """The equality operator `=`."""
[176]1431    __slots__ = ['lval', 'rval']
1432    def __init__(self, lval, rval):
1433        self.lval = lval
1434        self.rval = rval
[281]1435    def __call__(self, kind, data, pos, namespaces, variables):
[622]1436        lval = as_scalar(self.lval(kind, data, pos, namespaces, variables))
1437        rval = as_scalar(self.rval(kind, data, pos, namespaces, variables))
[203]1438        return lval == rval
[176]1439    def __repr__(self):
1440        return '%s=%s' % (self.lval, self.rval)
1441
1442class NotEqualsOperator(object):
[203]1443    """The equality operator `!=`."""
[176]1444    __slots__ = ['lval', 'rval']
1445    def __init__(self, lval, rval):
1446        self.lval = lval
1447        self.rval = rval
[281]1448    def __call__(self, kind, data, pos, namespaces, variables):
[622]1449        lval = as_scalar(self.lval(kind, data, pos, namespaces, variables))
1450        rval = as_scalar(self.rval(kind, data, pos, namespaces, variables))
[203]1451        return lval != rval
[176]1452    def __repr__(self):
1453        return '%s!=%s' % (self.lval, self.rval)
1454
1455class OrOperator(object):
[203]1456    """The boolean operator `or`."""
[176]1457    __slots__ = ['lval', 'rval']
1458    def __init__(self, lval, rval):
1459        self.lval = lval
1460        self.rval = rval
[281]1461    def __call__(self, kind, data, pos, namespaces, variables):
[622]1462        lval = as_bool(self.lval(kind, data, pos, namespaces, variables))
[203]1463        if lval:
[176]1464            return True
[281]1465        rval = self.rval(kind, data, pos, namespaces, variables)
[622]1466        return as_bool(rval)
[176]1467    def __repr__(self):
1468        return '%s or %s' % (self.lval, self.rval)
1469
[204]1470class GreaterThanOperator(object):
1471    """The relational operator `>` (greater than)."""
1472    __slots__ = ['lval', 'rval']
1473    def __init__(self, lval, rval):
1474        self.lval = lval
1475        self.rval = rval
[281]1476    def __call__(self, kind, data, pos, namespaces, variables):
1477        lval = self.lval(kind, data, pos, namespaces, variables)
1478        rval = self.rval(kind, data, pos, namespaces, variables)
[622]1479        return as_float(lval) > as_float(rval)
[204]1480    def __repr__(self):
1481        return '%s>%s' % (self.lval, self.rval)
1482
1483class GreaterThanOrEqualOperator(object):
1484    """The relational operator `>=` (greater than or equal)."""
1485    __slots__ = ['lval', 'rval']
1486    def __init__(self, lval, rval):
1487        self.lval = lval
1488        self.rval = rval
[281]1489    def __call__(self, kind, data, pos, namespaces, variables):
1490        lval = self.lval(kind, data, pos, namespaces, variables)
1491        rval = self.rval(kind, data, pos, namespaces, variables)
[622]1492        return as_float(lval) >= as_float(rval)
[204]1493    def __repr__(self):
1494        return '%s>=%s' % (self.lval, self.rval)
1495
1496class LessThanOperator(object):
1497    """The relational operator `<` (less than)."""
1498    __slots__ = ['lval', 'rval']
1499    def __init__(self, lval, rval):
1500        self.lval = lval
1501        self.rval = rval
[281]1502    def __call__(self, kind, data, pos, namespaces, variables):
1503        lval = self.lval(kind, data, pos, namespaces, variables)
1504        rval = self.rval(kind, data, pos, namespaces, variables)
[622]1505        return as_float(lval) < as_float(rval)
[204]1506    def __repr__(self):
1507        return '%s<%s' % (self.lval, self.rval)
1508
1509class LessThanOrEqualOperator(object):
1510    """The relational operator `<=` (less than or equal)."""
1511    __slots__ = ['lval', 'rval']
1512    def __init__(self, lval, rval):
1513        self.lval = lval
1514        self.rval = rval
[281]1515    def __call__(self, kind, data, pos, namespaces, variables):
1516        lval = self.lval(kind, data, pos, namespaces, variables)
1517        rval = self.rval(kind, data, pos, namespaces, variables)
[622]1518        return as_float(lval) <= as_float(rval)
[204]1519    def __repr__(self):
1520        return '%s<=%s' % (self.lval, self.rval)
1521
1522_operator_map = {'=': EqualsOperator, '!=': NotEqualsOperator,
1523                 '>': GreaterThanOperator, '>=': GreaterThanOrEqualOperator,
1524                 '<': LessThanOperator, '>=': LessThanOrEqualOperator}
[410]1525
1526
1527_DOTSLASHSLASH = (DESCENDANT_OR_SELF, PrincipalTypeTest(None), ())
[1025]1528_DOTSLASH = (SELF, PrincipalTypeTest(None), ())
Note: See TracBrowser for help on using the repository browser.