1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2006-2009 Edgewall Software
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
8# are also available at http://genshi.edgewall.org/wiki/License.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at http://genshi.edgewall.org/log/.
13
14"""Basic support for evaluating XPath expressions against streams.
15
16>>> from genshi.input import XML
17>>> doc = XML('''<doc>
18...  <items count="4">
19...       <item status="new">
20...         <summary>Foo</summary>
21...       </item>
22...       <item status="closed">
23...         <summary>Bar</summary>
24...       </item>
25...       <item status="closed" resolution="invalid">
26...         <summary>Baz</summary>
27...       </item>
28...       <item status="closed" resolution="fixed">
29...         <summary>Waz</summary>
30...       </item>
31...   </items>
32... </doc>''')
33>>> print(doc.select('items/item[@status="closed" and '
34...     '(@resolution="invalid" or not(@resolution))]/summary/text()'))
35BarBaz
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
41from collections import deque
42try:
43    reduce # builtin in Python < 3
44except NameError:
45    from functools import reduce
46from math import ceil, floor
47import operator
48import re
49from itertools import chain
50
51from genshi.core import Stream, Attrs, Namespace, QName
52from genshi.core import START, END, TEXT, START_NS, END_NS, COMMENT, PI, \
53                        START_CDATA, END_CDATA
54
55__all__ = ['Path', 'PathSyntaxError']
56__docformat__ = 'restructuredtext en'
57
58
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
68    @classmethod
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
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"""
266            if type(node1) is not type(node2):
267                return False
268            if type(node1) == LocalNameTest:
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
281            for i in range(1, len(f)):
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
513class Path(object):
514    """Implements basic XPath support on streams.
515
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.
519    """
520
521    STRATEGIES = (SingleStepStrategy, SimplePathStrategy, GenericStrategy)
522
523    def __init__(self, text, filename=None, lineno=-1):
524        """Create the path object from a string.
525
526        :param text: the path expression
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
530        """
531        self.source = text
532        self.paths = PathParser(text, filename, lineno).parse()
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:
540                raise NotImplemented('No strategy found for path')
541
542    def __repr__(self):
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:
549                    steps[-1] += '[%s]' % predicate
550            paths.append('/'.join(steps))
551        return '<%s "%s">' % (type(self).__name__, '|'.join(paths))
552
553    def select(self, stream, namespaces=None, variables=None):
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
558        >>> from genshi.input import XML
559        >>> xml = XML('<root><elem><child>Text</child></elem></root>')
560
561        >>> print(Path('.//child').select(xml))
562        <child>Text</child>
563
564        >>> print(Path('.//child/text()').select(xml))
565        Text
566
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
571        :rtype: `Stream`
572        """
573        if namespaces is None:
574            namespaces = {}
575        if variables is None:
576            variables = {}
577        stream = iter(stream)
578        def _generate(stream=stream, ns=namespaces, vs=variables):
579            next = stream.next
580            test = self.test()
581            for event in stream:
582                result = test(event, ns, vs)
583                if result is True:
584                    yield event
585                    if event[0] is START:
586                        depth = 1
587                        while depth > 0:
588                            subevent = next()
589                            if subevent[0] is START:
590                                depth += 1
591                            elif subevent[0] is END:
592                                depth -= 1
593                            yield subevent
594                            test(subevent, ns, vs, updateonly=True)
595                elif result:
596                    yield result
597        return Stream(_generate(),
598                      serializer=getattr(stream, 'serializer', None))
599
600    def test(self, ignore_context=False):
601        """Returns a function that can be used to track whether the path matches
602        a specific stream event.
603
604        The function returned expects the positional arguments ``event``,
605        ``namespaces`` and ``variables``. The first is a stream event, while the
606        latter two are a mapping of namespace prefixes to URIs, and a mapping
607        of variable names to values, respectively. In addition, the function
608        accepts an ``updateonly`` keyword argument that default to ``False``. If
609        it is set to ``True``, the function only updates its internal state,
610        but does not perform any tests or return a result.
611
612        If the path matches the event, the function returns the match (for
613        example, a `START` or `TEXT` event.) Otherwise, it returns ``None``.
614
615        >>> from genshi.input import XML
616        >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>')
617        >>> test = Path('child').test()
618        >>> namespaces, variables = {}, {}
619        >>> for event in xml:
620        ...     if test(event, namespaces, variables):
621        ...         print('%s %r' % (event[0], event[1]))
622        START (QName('child'), Attrs([(QName('id'), u'2')]))
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``
630        """
631        tests = [s.test(ignore_context) for s in self.strategies]
632        if len(tests) == 1:
633            return tests[0]
634
635        def _multi(event, namespaces, variables, updateonly=False):
636            retval = None
637            for test in tests:
638                val = test(event, namespaces, variables, updateonly=updateonly)
639                if retval is None:
640                    retval = val
641            return retval
642        return _multi
643
644
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
657class PathParser(object):
658    """Tokenizes and parses an XPath expression."""
659
660    _QUOTES = (("'", "'"), ('"', '"'))
661    _TOKENS = ('::', ':', '..', '.', '//', '/', '[', ']', '()', '(', ')', '@',
662               '=', '!=', '!', '|', ',', '>=', '>', '<=', '<', '$')
663    _tokenize = re.compile('("[^"]*")|(\'[^\']*\')|((?:\d+)?\.\d+)|(%s)|([^%s\s]+)|\s+' % (
664                           '|'.join([re.escape(t) for t in _TOKENS]),
665                           ''.join([re.escape(t[0]) for t in _TOKENS]))).findall
666
667    def __init__(self, text, filename=None, lineno=-1):
668        self.filename = filename
669        self.lineno = lineno
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]
673        self.pos = 0
674
675    # Tokenizer
676
677    @property
678    def at_end(self):
679        return self.pos == len(self.tokens) - 1
680
681    @property
682    def cur_token(self):
683        return self.tokens[self.pos]
684
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
705        location steps, each tuples of the form `(axis, testfunc, predicates)`
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'
713                                  % self.cur_token, self.filename, self.lineno)
714        return paths
715
716    def _location_path(self):
717        steps = []
718        while True:
719            if self.cur_token.startswith('/'):
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 == '//':
735                    steps.append((DESCENDANT_OR_SELF, NodeTest(), []))
736                self.next_token()
737
738            axis, nodetest, predicates = self._location_step()
739            if not axis:
740                axis = CHILD
741            steps.append((axis, nodetest, predicates))
742            if self.at_end or not self.cur_token.startswith('/'):
743                break
744
745        return steps
746
747    def _location_step(self):
748        if self.cur_token == '@':
749            axis = ATTRIBUTE
750            self.next_token()
751        elif self.cur_token == '.':
752            axis = SELF
753        elif self.cur_token == '..':
754            raise PathSyntaxError('Unsupported axis "parent"', self.filename,
755                                  self.lineno)
756        elif self.peek_token() == '::':
757            axis = Axis.forname(self.cur_token)
758            if axis is None:
759                raise PathSyntaxError('Unsupport axis "%s"' % axis,
760                                      self.filename, self.lineno)
761            self.next_token()
762            self.next_token()
763        else:
764            axis = None
765        nodetest = self._node_test(axis or CHILD)
766        predicates = []
767        while self.cur_token == '[':
768            predicates.append(self._predicate())
769        return axis, nodetest, predicates
770
771    def _node_test(self, axis=None):
772        test = prefix = None
773        next_token = self.peek_token()
774        if next_token in ('(', '()'): # Node type test
775            test = self._node_type()
776
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
786        else: # Name test
787            if self.cur_token == '*':
788                test = PrincipalTypeTest(axis)
789            elif self.cur_token == '.':
790                test = NodeTest()
791            else:
792                test = LocalNameTest(axis, self.cur_token)
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()
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:
815            raise PathSyntaxError('%s() not allowed here' % name, self.filename,
816                                  self.lineno)
817        return cls(*args)
818
819    def _predicate(self):
820        assert self.cur_token == '['
821        self.next_token()
822        expr = self._or_expr()
823        if self.cur_token != ']':
824            raise PathSyntaxError('Expected "]" to close predicate, '
825                                  'but found "%s"' % self.cur_token,
826                                  self.filename, self.lineno)
827        if not self.at_end:
828            self.next_token()
829        return expr
830
831    def _or_expr(self):
832        expr = self._and_expr()
833        while self.cur_token == 'or':
834            self.next_token()
835            expr = OrOperator(expr, self._and_expr())
836        return expr
837
838    def _and_expr(self):
839        expr = self._equality_expr()
840        while self.cur_token == 'and':
841            self.next_token()
842            expr = AndOperator(expr, self._equality_expr())
843        return expr
844
845    def _equality_expr(self):
846        expr = self._relational_expr()
847        while self.cur_token in ('=', '!='):
848            op = _operator_map[self.cur_token]
849            self.next_token()
850            expr = op(expr, self._relational_expr())
851        return expr
852
853    def _relational_expr(self):
854        expr = self._sub_expr()
855        while self.cur_token in ('>', '>=', '<', '>='):
856            op = _operator_map[self.cur_token]
857            self.next_token()
858            expr = op(expr, self._sub_expr())
859        return expr
860
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
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()
878            return StringLiteral(token[1:-1])
879        elif token[0].isdigit() or token[0] == '.':
880            self.next_token()
881            return NumberLiteral(as_float(token))
882        elif token == '$':
883            token = self.next_token()
884            self.next_token()
885            return VariableReference(token)
886        elif not self.at_end and self.peek_token().startswith('('):
887            return self._function_call()
888        else:
889            axis = None
890            if token == '@':
891                axis = ATTRIBUTE
892                self.next_token()
893            return self._node_test(axis)
894
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)
916
917
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:
940        return ''
941    return unicode(value)
942
943def as_bool(value):
944    return bool(as_scalar(value))
945
946
947# Node tests
948
949class PrincipalTypeTest(object):
950    """Node test that matches any event with the given principal type."""
951    __slots__ = ['principal_type']
952    def __init__(self, principal_type):
953        self.principal_type = principal_type
954    def __call__(self, kind, data, pos, namespaces, variables):
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
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
981class LocalNameTest(object):
982    """Node test that matches any event with the given principal type and
983    local name.
984    """
985    __slots__ = ['principal_type', 'name']
986    def __init__(self, principal_type, name):
987        self.principal_type = principal_type
988        self.name = name
989    def __call__(self, kind, data, pos, namespaces, variables):
990        if kind is START:
991            if self.principal_type is ATTRIBUTE and self.name in data[1]:
992                return Attrs([(self.name, data[1].get(self.name))])
993            else:
994                return data[0].localname == self.name
995    def __repr__(self):
996        return self.name
997
998class QualifiedNameTest(object):
999    """Node test that matches any event with the given principal type and
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]:
1011                return Attrs([(qname, data[1].get(qname))])
1012            else:
1013                return data[0] == qname
1014    def __repr__(self):
1015        return '%s:%s' % (self.prefix, self.name)
1016
1017class CommentNodeTest(object):
1018    """Node test that matches any comment events."""
1019    __slots__ = []
1020    def __call__(self, kind, data, pos, namespaces, variables):
1021        return kind is COMMENT
1022    def __repr__(self):
1023        return 'comment()'
1024
1025class NodeTest(object):
1026    """Node test that matches any node."""
1027    __slots__ = []
1028    def __call__(self, kind, data, pos, namespaces, variables):
1029        if kind is START:
1030            return True
1031        return kind, data, pos
1032    def __repr__(self):
1033        return 'node()'
1034
1035class ProcessingInstructionNodeTest(object):
1036    """Node test that matches any processing instruction event."""
1037    __slots__ = ['target']
1038    def __init__(self, target=None):
1039        self.target = target
1040    def __call__(self, kind, data, pos, namespaces, variables):
1041        return kind is PI and (not self.target or data[0] == self.target)
1042    def __repr__(self):
1043        arg = ''
1044        if self.target:
1045            arg = '"' + self.target + '"'
1046        return 'processing-instruction(%s)' % arg
1047
1048class TextNodeTest(object):
1049    """Node test that matches any text event."""
1050    __slots__ = []
1051    def __call__(self, kind, data, pos, namespaces, variables):
1052        return kind is TEXT
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
1062class Function(object):
1063    """Base class for function nodes in XPath expressions."""
1064
1065class BooleanFunction(Function):
1066    """The `boolean` function, which converts its argument to a boolean
1067    value.
1068    """
1069    __slots__ = ['expr']
1070    _return_type = bool
1071    def __init__(self, expr):
1072        self.expr = expr
1073    def __call__(self, kind, data, pos, namespaces, variables):
1074        val = self.expr(kind, data, pos, namespaces, variables)
1075        return as_bool(val)
1076    def __repr__(self):
1077        return 'boolean(%r)' % self.expr
1078
1079class CeilingFunction(Function):
1080    """The `ceiling` function, which returns the nearest lower integer number
1081    for the given number.
1082    """
1083    __slots__ = ['number']
1084    def __init__(self, number):
1085        self.number = number
1086    def __call__(self, kind, data, pos, namespaces, variables):
1087        number = self.number(kind, data, pos, namespaces, variables)
1088        return ceil(as_float(number))
1089    def __repr__(self):
1090        return 'ceiling(%r)' % self.number
1091
1092class ConcatFunction(Function):
1093    """The `concat` function, which concatenates (joins) the variable number of
1094    strings it gets as arguments.
1095    """
1096    __slots__ = ['exprs']
1097    def __init__(self, *exprs):
1098        self.exprs = exprs
1099    def __call__(self, kind, data, pos, namespaces, variables):
1100        strings = []
1101        for item in [expr(kind, data, pos, namespaces, variables)
1102                     for expr in self.exprs]:
1103            strings.append(as_string(item))
1104        return ''.join(strings)
1105    def __repr__(self):
1106        return 'concat(%s)' % ', '.join([repr(expr) for expr in self.exprs])
1107
1108class ContainsFunction(Function):
1109    """The `contains` function, which returns whether a string contains a given
1110    substring.
1111    """
1112    __slots__ = ['string1', 'string2']
1113    def __init__(self, string1, string2):
1114        self.string1 = string1
1115        self.string2 = string2
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)
1119        return as_string(string2) in as_string(string1)
1120    def __repr__(self):
1121        return 'contains(%r, %r)' % (self.string1, self.string2)
1122
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):
1139        return reduce(operator.or_,
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
1144class FalseFunction(Function):
1145    """The `false` function, which always returns the boolean `false` value."""
1146    __slots__ = []
1147    def __call__(self, kind, data, pos, namespaces, variables):
1148        return False
1149    def __repr__(self):
1150        return 'false()'
1151
1152class FloorFunction(Function):
1153    """The `ceiling` function, which returns the nearest higher integer number
1154    for the given number.
1155    """
1156    __slots__ = ['number']
1157    def __init__(self, number):
1158        self.number = number
1159    def __call__(self, kind, data, pos, namespaces, variables):
1160        number = self.number(kind, data, pos, namespaces, variables)
1161        return floor(as_float(number))
1162    def __repr__(self):
1163        return 'floor(%r)' % self.number
1164
1165class LocalNameFunction(Function):
1166    """The `local-name` function, which returns the local name of the current
1167    element.
1168    """
1169    __slots__ = []
1170    def __call__(self, kind, data, pos, namespaces, variables):
1171        if kind is START:
1172            return data[0].localname
1173    def __repr__(self):
1174        return 'local-name()'
1175
1176class NameFunction(Function):
1177    """The `name` function, which returns the qualified name of the current
1178    element.
1179    """
1180    __slots__ = []
1181    def __call__(self, kind, data, pos, namespaces, variables):
1182        if kind is START:
1183            return data[0]
1184    def __repr__(self):
1185        return 'name()'
1186
1187class NamespaceUriFunction(Function):
1188    """The `namespace-uri` function, which returns the namespace URI of the
1189    current element.
1190    """
1191    __slots__ = []
1192    def __call__(self, kind, data, pos, namespaces, variables):
1193        if kind is START:
1194            return data[0].namespace
1195    def __repr__(self):
1196        return 'namespace-uri()'
1197
1198class NotFunction(Function):
1199    """The `not` function, which returns the negated boolean value of its
1200    argument.
1201    """
1202    __slots__ = ['expr']
1203    def __init__(self, expr):
1204        self.expr = expr
1205    def __call__(self, kind, data, pos, namespaces, variables):
1206        return not as_bool(self.expr(kind, data, pos, namespaces, variables))
1207    def __repr__(self):
1208        return 'not(%s)' % self.expr
1209
1210class NormalizeSpaceFunction(Function):
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    """
1215    __slots__ = ['expr']
1216    _normalize = re.compile(r'\s{2,}').sub
1217    def __init__(self, expr):
1218        self.expr = expr
1219    def __call__(self, kind, data, pos, namespaces, variables):
1220        string = self.expr(kind, data, pos, namespaces, variables)
1221        return self._normalize(' ', as_string(string).strip())
1222    def __repr__(self):
1223        return 'normalize-space(%s)' % repr(self.expr)
1224
1225class NumberFunction(Function):
1226    """The `number` function that converts its argument to a number."""
1227    __slots__ = ['expr']
1228    def __init__(self, expr):
1229        self.expr = expr
1230    def __call__(self, kind, data, pos, namespaces, variables):
1231        val = self.expr(kind, data, pos, namespaces, variables)
1232        return as_float(val)
1233    def __repr__(self):
1234        return 'number(%r)' % self.expr
1235
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
1243    def __call__(self, kind, data, pos, namespaces, variables):
1244        number = self.number(kind, data, pos, namespaces, variables)
1245        return round(as_float(number))
1246    def __repr__(self):
1247        return 'round(%r)' % self.number
1248
1249class StartsWithFunction(Function):
1250    """The `starts-with` function that returns whether one string starts with
1251    a given substring.
1252    """
1253    __slots__ = ['string1', 'string2']
1254    def __init__(self, string1, string2):
1255        self.string1 = string1
1256        self.string2 = string2
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)
1260        return as_string(string1).startswith(as_string(string2))
1261    def __repr__(self):
1262        return 'starts-with(%r, %r)' % (self.string1, self.string2)
1263
1264class StringLengthFunction(Function):
1265    """The `string-length` function that returns the length of the given
1266    string.
1267    """
1268    __slots__ = ['expr']
1269    def __init__(self, expr):
1270        self.expr = expr
1271    def __call__(self, kind, data, pos, namespaces, variables):
1272        string = self.expr(kind, data, pos, namespaces, variables)
1273        return len(as_string(string))
1274    def __repr__(self):
1275        return 'string-length(%r)' % self.expr
1276
1277class SubstringFunction(Function):
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    """
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
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)
1289        length = 0
1290        if self.length is not None:
1291            length = self.length(kind, data, pos, namespaces, variables)
1292        return string[as_long(start):len(as_string(string)) - as_long(length)]
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):
1301    """The `substring-after` function that returns the part of a string that
1302    is found after the given substring.
1303    """
1304    __slots__ = ['string1', 'string2']
1305    def __init__(self, string1, string2):
1306        self.string1 = string1
1307        self.string2 = string2
1308    def __call__(self, kind, data, pos, namespaces, variables):
1309        string1 = as_string(self.string1(kind, data, pos, namespaces, variables))
1310        string2 = as_string(self.string2(kind, data, pos, namespaces, variables))
1311        index = string1.find(string2)
1312        if index >= 0:
1313            return string1[index + len(string2):]
1314        return ''
1315    def __repr__(self):
1316        return 'substring-after(%r, %r)' % (self.string1, self.string2)
1317
1318class SubstringBeforeFunction(Function):
1319    """The `substring-before` function that returns the part of a string that
1320    is found before the given substring.
1321    """
1322    __slots__ = ['string1', 'string2']
1323    def __init__(self, string1, string2):
1324        self.string1 = string1
1325        self.string2 = string2
1326    def __call__(self, kind, data, pos, namespaces, variables):
1327        string1 = as_string(self.string1(kind, data, pos, namespaces, variables))
1328        string2 = as_string(self.string2(kind, data, pos, namespaces, variables))
1329        index = string1.find(string2)
1330        if index >= 0:
1331            return string1[:index]
1332        return ''
1333    def __repr__(self):
1334        return 'substring-after(%r, %r)' % (self.string1, self.string2)
1335
1336class TranslateFunction(Function):
1337    """The `translate` function that translates a set of characters in a
1338    string to target set of characters.
1339    """
1340    __slots__ = ['string', 'fromchars', 'tochars']
1341    def __init__(self, string, fromchars, tochars):
1342        self.string = string
1343        self.fromchars = fromchars
1344        self.tochars = tochars
1345    def __call__(self, kind, data, pos, namespaces, variables):
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))
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):
1357    """The `true` function, which always returns the boolean `true` value."""
1358    __slots__ = []
1359    def __call__(self, kind, data, pos, namespaces, variables):
1360        return True
1361    def __repr__(self):
1362        return 'true()'
1363
1364_function_map = {'boolean': BooleanFunction, 'ceiling': CeilingFunction,
1365                 'concat': ConcatFunction, 'contains': ContainsFunction,
1366                 'matches': MatchesFunction, 'false': FalseFunction, 'floor':
1367                 FloorFunction, 'local-name': LocalNameFunction, 'name':
1368                 NameFunction, 'namespace-uri': NamespaceUriFunction,
1369                 'normalize-space': NormalizeSpaceFunction, 'not': NotFunction,
1370                 'number': NumberFunction, 'round': RoundFunction,
1371                 'starts-with': StartsWithFunction, 'string-length':
1372                 StringLengthFunction, 'substring': SubstringFunction,
1373                 'substring-after': SubstringAfterFunction, 'substring-before':
1374                 SubstringBeforeFunction, 'translate': TranslateFunction,
1375                 'true': TrueFunction}
1376
1377# Literals & Variables
1378
1379class Literal(object):
1380    """Abstract base class for literal nodes."""
1381
1382class StringLiteral(Literal):
1383    """A string literal node."""
1384    __slots__ = ['text']
1385    def __init__(self, text):
1386        self.text = text
1387    def __call__(self, kind, data, pos, namespaces, variables):
1388        return self.text
1389    def __repr__(self):
1390        return '"%s"' % self.text
1391
1392class NumberLiteral(Literal):
1393    """A number literal node."""
1394    __slots__ = ['number']
1395    def __init__(self, number):
1396        self.number = number
1397    def __call__(self, kind, data, pos, namespaces, variables):
1398        return self.number
1399    def __repr__(self):
1400        return str(self.number)
1401
1402class VariableReference(Literal):
1403    """A variable reference node."""
1404    __slots__ = ['name']
1405    def __init__(self, name):
1406        self.name = name
1407    def __call__(self, kind, data, pos, namespaces, variables):
1408        return variables.get(self.name)
1409    def __repr__(self):
1410        return str(self.name)
1411
1412# Operators
1413
1414class AndOperator(object):
1415    """The boolean operator `and`."""
1416    __slots__ = ['lval', 'rval']
1417    def __init__(self, lval, rval):
1418        self.lval = lval
1419        self.rval = rval
1420    def __call__(self, kind, data, pos, namespaces, variables):
1421        lval = as_bool(self.lval(kind, data, pos, namespaces, variables))
1422        if not lval:
1423            return False
1424        rval = self.rval(kind, data, pos, namespaces, variables)
1425        return as_bool(rval)
1426    def __repr__(self):
1427        return '%s and %s' % (self.lval, self.rval)
1428
1429class EqualsOperator(object):
1430    """The equality operator `=`."""
1431    __slots__ = ['lval', 'rval']
1432    def __init__(self, lval, rval):
1433        self.lval = lval
1434        self.rval = rval
1435    def __call__(self, kind, data, pos, namespaces, variables):
1436        lval = as_scalar(self.lval(kind, data, pos, namespaces, variables))
1437        rval = as_scalar(self.rval(kind, data, pos, namespaces, variables))
1438        return lval == rval
1439    def __repr__(self):
1440        return '%s=%s' % (self.lval, self.rval)
1441
1442class NotEqualsOperator(object):
1443    """The equality operator `!=`."""
1444    __slots__ = ['lval', 'rval']
1445    def __init__(self, lval, rval):
1446        self.lval = lval
1447        self.rval = rval
1448    def __call__(self, kind, data, pos, namespaces, variables):
1449        lval = as_scalar(self.lval(kind, data, pos, namespaces, variables))
1450        rval = as_scalar(self.rval(kind, data, pos, namespaces, variables))
1451        return lval != rval
1452    def __repr__(self):
1453        return '%s!=%s' % (self.lval, self.rval)
1454
1455class OrOperator(object):
1456    """The boolean operator `or`."""
1457    __slots__ = ['lval', 'rval']
1458    def __init__(self, lval, rval):
1459        self.lval = lval
1460        self.rval = rval
1461    def __call__(self, kind, data, pos, namespaces, variables):
1462        lval = as_bool(self.lval(kind, data, pos, namespaces, variables))
1463        if lval:
1464            return True
1465        rval = self.rval(kind, data, pos, namespaces, variables)
1466        return as_bool(rval)
1467    def __repr__(self):
1468        return '%s or %s' % (self.lval, self.rval)
1469
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
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)
1479        return as_float(lval) > as_float(rval)
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
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)
1492        return as_float(lval) >= as_float(rval)
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
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)
1505        return as_float(lval) < as_float(rval)
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
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)
1518        return as_float(lval) <= as_float(rval)
1519    def __repr__(self):
1520        return '%s<=%s' % (self.lval, self.rval)
1521
1522_operator_map = {'=': EqualsOperator, '!=': NotEqualsOperator,
1523                 '>': GreaterThanOperator, '>=': GreaterThanOrEqualOperator,
1524                 '<': LessThanOperator, '>=': LessThanOrEqualOperator}
1525
1526
1527_DOTSLASHSLASH = (DESCENDANT_OR_SELF, PrincipalTypeTest(None), ())
1528_DOTSLASH = (SELF, PrincipalTypeTest(None), ())
1529