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