Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/genshi/path.py: 29%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

971 statements  

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 

42from functools import reduce 

43from math import ceil, floor 

44import operator 

45import re 

46from itertools import chain 

47 

48from genshi.compat import text_type, IS_PYTHON2 

49from genshi.core import Stream, Attrs, Namespace, QName 

50from genshi.core import START, END, TEXT, START_NS, END_NS, COMMENT, PI, \ 

51 START_CDATA, END_CDATA 

52 

53__all__ = ['Path', 'PathSyntaxError'] 

54__docformat__ = 'restructuredtext en' 

55 

56 

57class Axis(object): 

58 """Defines constants for the various supported XPath axes.""" 

59 

60 ATTRIBUTE = 'attribute' 

61 CHILD = 'child' 

62 DESCENDANT = 'descendant' 

63 DESCENDANT_OR_SELF = 'descendant-or-self' 

64 SELF = 'self' 

65 

66 @classmethod 

67 def forname(cls, name): 

68 """Return the axis constant for the given name, or `None` if no such 

69 axis was defined. 

70 """ 

71 return getattr(cls, name.upper().replace('-', '_'), None) 

72 

73 

74ATTRIBUTE = Axis.ATTRIBUTE 

75CHILD = Axis.CHILD 

76DESCENDANT = Axis.DESCENDANT 

77DESCENDANT_OR_SELF = Axis.DESCENDANT_OR_SELF 

78SELF = Axis.SELF 

79 

80 

81class GenericStrategy(object): 

82 

83 @classmethod 

84 def supports(cls, path): 

85 return True 

86 

87 def __init__(self, path): 

88 self.path = path 

89 

90 def test(self, ignore_context): 

91 p = self.path 

92 if ignore_context: 

93 if p[0][0] is ATTRIBUTE: 

94 steps = [_DOTSLASHSLASH] + p 

95 else: 

96 steps = [(DESCENDANT_OR_SELF, p[0][1], p[0][2])] + p[1:] 

97 elif p[0][0] is CHILD or p[0][0] is ATTRIBUTE \ 

98 or p[0][0] is DESCENDANT: 

99 steps = [_DOTSLASH] + p 

100 else: 

101 steps = p 

102 

103 # for node it contains all positions of xpath expression 

104 # where its child should start checking for matches 

105 # with list of corresponding context counters 

106 # there can be many of them, because position that is from 

107 # descendant-like axis can be achieved from different nodes 

108 # for example <a><a><b/></a></a> should match both //a//b[1] 

109 # and //a//b[2] 

110 # positions always form increasing sequence (invariant) 

111 stack = [[(0, [[]])]] 

112 

113 def _test(event, namespaces, variables, updateonly=False): 

114 kind, data, pos = event[:3] 

115 retval = None 

116 

117 # Manage the stack that tells us "where we are" in the stream 

118 if kind is END: 

119 if stack: 

120 stack.pop() 

121 return None 

122 if kind is START_NS or kind is END_NS \ 

123 or kind is START_CDATA or kind is END_CDATA: 

124 # should we make namespaces work? 

125 return None 

126 

127 pos_queue = deque([(pos, cou, []) for pos, cou in stack[-1]]) 

128 next_pos = [] 

129 

130 # length of real part of path - we omit attribute axis 

131 real_len = len(steps) - ((steps[-1][0] == ATTRIBUTE) or 1 and 0) 

132 last_checked = -1 

133 

134 # places where we have to check for match, are these 

135 # provided by parent 

136 while pos_queue: 

137 x, pcou, mcou = pos_queue.popleft() 

138 axis, nodetest, predicates = steps[x] 

139 

140 # we need to push descendant-like positions from parent 

141 # further 

142 if (axis is DESCENDANT or axis is DESCENDANT_OR_SELF) and pcou: 

143 if next_pos and next_pos[-1][0] == x: 

144 next_pos[-1][1].extend(pcou) 

145 else: 

146 next_pos.append((x, pcou)) 

147 

148 # nodetest first 

149 if not nodetest(kind, data, pos, namespaces, variables): 

150 continue 

151 

152 # counters packs that were already bad 

153 missed = set() 

154 counters_len = len(pcou) + len(mcou) 

155 

156 # number of counters - we have to create one 

157 # for every context position based predicate 

158 cnum = 0 

159 

160 # tells if we have match with position x 

161 matched = True 

162 

163 if predicates: 

164 for predicate in predicates: 

165 pretval = predicate(kind, data, pos, 

166 namespaces, 

167 variables) 

168 if type(pretval) is float: # FIXME <- need to check 

169 # this for other types that 

170 # can be coerced to float 

171 

172 # each counter pack needs to be checked 

173 for i, cou in enumerate(chain(pcou, mcou)): 

174 # it was bad before 

175 if i in missed: 

176 continue 

177 

178 if len(cou) < cnum + 1: 

179 cou.append(0) 

180 cou[cnum] += 1 

181 

182 # it is bad now 

183 if cou[cnum] != int(pretval): 

184 missed.add(i) 

185 

186 # none of counters pack was good 

187 if len(missed) == counters_len: 

188 pretval = False 

189 cnum += 1 

190 

191 if not pretval: 

192 matched = False 

193 break 

194 

195 if not matched: 

196 continue 

197 

198 # counter for next position with current node as context node 

199 child_counter = [] 

200 

201 if x + 1 == real_len: 

202 # we reached end of expression, because x + 1 

203 # is equal to the length of expression 

204 matched = True 

205 axis, nodetest, predicates = steps[-1] 

206 if axis is ATTRIBUTE: 

207 matched = nodetest(kind, data, pos, namespaces, 

208 variables) 

209 if matched: 

210 retval = matched 

211 else: 

212 next_axis = steps[x + 1][0] 

213 

214 # if next axis allows matching self we have 

215 # to add next position to our queue 

216 if next_axis is DESCENDANT_OR_SELF or next_axis is SELF: 

217 if not pos_queue or pos_queue[0][0] > x + 1: 

218 pos_queue.appendleft((x + 1, [], [child_counter])) 

219 else: 

220 pos_queue[0][2].append(child_counter) 

221 

222 # if axis is not self we have to add it to child's list 

223 if next_axis is not SELF: 

224 next_pos.append((x + 1, [child_counter])) 

225 

226 if kind is START: 

227 stack.append(next_pos) 

228 

229 return retval 

230 

231 return _test 

232 

233 

234class SimplePathStrategy(object): 

235 """Strategy for path with only local names, attributes and text nodes.""" 

236 

237 @classmethod 

238 def supports(cls, path): 

239 if path[0][0] is ATTRIBUTE: 

240 return False 

241 allowed_tests = (LocalNameTest, CommentNodeTest, TextNodeTest) 

242 for _, nodetest, predicates in path: 

243 if predicates: 

244 return False 

245 if not isinstance(nodetest, allowed_tests): 

246 return False 

247 return True 

248 

249 def __init__(self, path): 

250 # fragments is list of tuples (fragment, pi, attr, self_beginning) 

251 # fragment is list of nodetests for fragment of path with only 

252 # child:: axes between 

253 # pi is KMP partial match table for this fragment 

254 # attr is attribute nodetest if fragment ends with @ and None otherwise 

255 # self_beginning is True if axis for first fragment element 

256 # was self (first fragment) or descendant-or-self (farther fragment) 

257 self.fragments = [] 

258 

259 self_beginning = False 

260 fragment = [] 

261 

262 def nodes_equal(node1, node2): 

263 """Tests if two node tests are equal""" 

264 if type(node1) is not type(node2): 

265 return False 

266 if type(node1) == LocalNameTest: 

267 return node1.name == node2.name 

268 return True 

269 

270 def calculate_pi(f): 

271 """KMP prefix calculation for table""" 

272 # the indexes in prefix table are shifted by one 

273 # in comparison with common implementations 

274 # pi[i] = NORMAL_PI[i + 1] 

275 if len(f) == 0: 

276 return [] 

277 pi = [0] 

278 s = 0 

279 for i in range(1, len(f)): 

280 while s > 0 and not nodes_equal(f[s], f[i]): 

281 s = pi[s-1] 

282 if nodes_equal(f[s], f[i]): 

283 s += 1 

284 pi.append(s) 

285 return pi 

286 

287 for axis in path: 

288 if axis[0] is SELF: 

289 if len(fragment) != 0: 

290 # if element is not first in fragment it has to be 

291 # the same as previous one 

292 # for example child::a/self::b is always wrong 

293 if axis[1] != fragment[-1][1]: 

294 self.fragments = None 

295 return 

296 else: 

297 self_beginning = True 

298 fragment.append(axis[1]) 

299 elif axis[0] is CHILD: 

300 fragment.append(axis[1]) 

301 elif axis[0] is ATTRIBUTE: 

302 pi = calculate_pi(fragment) 

303 self.fragments.append((fragment, pi, axis[1], self_beginning)) 

304 # attribute has always to be at the end, so we can jump out 

305 return 

306 else: 

307 pi = calculate_pi(fragment) 

308 self.fragments.append((fragment, pi, None, self_beginning)) 

309 fragment = [axis[1]] 

310 if axis[0] is DESCENDANT: 

311 self_beginning = False 

312 else: # DESCENDANT_OR_SELF 

313 self_beginning = True 

314 pi = calculate_pi(fragment) 

315 self.fragments.append((fragment, pi, None, self_beginning)) 

316 

317 def test(self, ignore_context): 

318 # stack of triples (fid, p, ic) 

319 # fid is index of current fragment 

320 # p is position in this fragment 

321 # ic is if we ignore context in this fragment 

322 stack = [] 

323 stack_push = stack.append 

324 stack_pop = stack.pop 

325 frags = self.fragments 

326 frags_len = len(frags) 

327 

328 def _test(event, namespaces, variables, updateonly=False): 

329 # expression found impossible during init 

330 if frags is None: 

331 return None 

332 

333 kind, data, pos = event[:3] 

334 

335 # skip events we don't care about 

336 if kind is END: 

337 if stack: 

338 stack_pop() 

339 return None 

340 if kind is START_NS or kind is END_NS \ 

341 or kind is START_CDATA or kind is END_CDATA: 

342 return None 

343 

344 if not stack: 

345 # root node, nothing on stack, special case 

346 fid = 0 

347 # skip empty fragments (there can be actually only one) 

348 while not frags[fid][0]: 

349 fid += 1 

350 p = 0 

351 # empty fragment means descendant node at beginning 

352 ic = ignore_context or (fid > 0) 

353 

354 # expression can match first node, if first axis is self::, 

355 # descendant-or-self:: or if ignore_context is True and 

356 # axis is not descendant:: 

357 if not frags[fid][3] and (not ignore_context or fid > 0): 

358 # axis is not self-beggining, we have to skip this node 

359 stack_push((fid, p, ic)) 

360 return None 

361 else: 

362 # take position of parent 

363 fid, p, ic = stack[-1] 

364 

365 if fid is not None and not ic: 

366 # fragment not ignoring context - we can't jump back 

367 frag, pi, attrib, _ = frags[fid] 

368 frag_len = len(frag) 

369 

370 if p == frag_len: 

371 # that probably means empty first fragment 

372 pass 

373 elif frag[p](kind, data, pos, namespaces, variables): 

374 # match, so we can go further 

375 p += 1 

376 else: 

377 # not matched, so there will be no match in subtree 

378 fid, p = None, None 

379 

380 if p == frag_len and fid + 1 != frags_len: 

381 # we made it to end of fragment, we can go to following 

382 fid += 1 

383 p = 0 

384 ic = True 

385 

386 if fid is None: 

387 # there was no match in fragment not ignoring context 

388 if kind is START: 

389 stack_push((fid, p, ic)) 

390 return None 

391 

392 if ic: 

393 # we are in fragment ignoring context 

394 while True: 

395 frag, pi, attrib, _ = frags[fid] 

396 frag_len = len(frag) 

397 

398 # KMP new "character" 

399 while p > 0 and (p >= frag_len or not \ 

400 frag[p](kind, data, pos, namespaces, variables)): 

401 p = pi[p-1] 

402 if frag[p](kind, data, pos, namespaces, variables): 

403 p += 1 

404 

405 if p == frag_len: 

406 # end of fragment reached 

407 if fid + 1 == frags_len: 

408 # that was last fragment 

409 break 

410 else: 

411 fid += 1 

412 p = 0 

413 ic = True 

414 if not frags[fid][3]: 

415 # next fragment not self-beginning 

416 break 

417 else: 

418 break 

419 

420 if kind is START: 

421 # we have to put new position on stack, for children 

422 

423 if not ic and fid + 1 == frags_len and p == frag_len: 

424 # it is end of the only, not context ignoring fragment 

425 # so there will be no matches in subtree 

426 stack_push((None, None, ic)) 

427 else: 

428 stack_push((fid, p, ic)) 

429 

430 # have we reached the end of the last fragment? 

431 if fid + 1 == frags_len and p == frag_len: 

432 if attrib: # attribute ended path, return value 

433 return attrib(kind, data, pos, namespaces, variables) 

434 return True 

435 

436 return None 

437 

438 return _test 

439 

440 

441class SingleStepStrategy(object): 

442 

443 @classmethod 

444 def supports(cls, path): 

445 return len(path) == 1 

446 

447 def __init__(self, path): 

448 self.path = path 

449 

450 def test(self, ignore_context): 

451 steps = self.path 

452 if steps[0][0] is ATTRIBUTE: 

453 steps = [_DOTSLASH] + steps 

454 select_attr = steps[-1][0] is ATTRIBUTE and steps[-1][1] or None 

455 

456 # for every position in expression stores counters' list 

457 # it is used for position based predicates 

458 counters = [] 

459 depth = [0] 

460 

461 def _test(event, namespaces, variables, updateonly=False): 

462 kind, data, pos = event[:3] 

463 

464 # Manage the stack that tells us "where we are" in the stream 

465 if kind is END: 

466 if not ignore_context: 

467 depth[0] -= 1 

468 return None 

469 elif kind is START_NS or kind is END_NS \ 

470 or kind is START_CDATA or kind is END_CDATA: 

471 # should we make namespaces work? 

472 return None 

473 

474 if not ignore_context: 

475 outside = (steps[0][0] is SELF and depth[0] != 0) \ 

476 or (steps[0][0] is CHILD and depth[0] != 1) \ 

477 or (steps[0][0] is DESCENDANT and depth[0] < 1) 

478 if kind is START: 

479 depth[0] += 1 

480 if outside: 

481 return None 

482 

483 axis, nodetest, predicates = steps[0] 

484 if not nodetest(kind, data, pos, namespaces, variables): 

485 return None 

486 

487 if predicates: 

488 cnum = 0 

489 for predicate in predicates: 

490 pretval = predicate(kind, data, pos, namespaces, variables) 

491 if type(pretval) is float: # FIXME <- need to check this 

492 # for other types that can be 

493 # coerced to float 

494 if len(counters) < cnum + 1: 

495 counters.append(0) 

496 counters[cnum] += 1 

497 if counters[cnum] != int(pretval): 

498 pretval = False 

499 cnum += 1 

500 if not pretval: 

501 return None 

502 

503 if select_attr: 

504 return select_attr(kind, data, pos, namespaces, variables) 

505 

506 return True 

507 

508 return _test 

509 

510 

511class Path(object): 

512 """Implements basic XPath support on streams. 

513  

514 Instances of this class represent a "compiled" XPath expression, and 

515 provide methods for testing the path against a stream, as well as 

516 extracting a substream matching that path. 

517 """ 

518 

519 STRATEGIES = (SingleStepStrategy, SimplePathStrategy, GenericStrategy) 

520 

521 def __init__(self, text, filename=None, lineno=-1): 

522 """Create the path object from a string. 

523  

524 :param text: the path expression 

525 :param filename: the name of the file in which the path expression was 

526 found (used in error messages) 

527 :param lineno: the line on which the expression was found 

528 """ 

529 self.source = text 

530 self.paths = PathParser(text, filename, lineno).parse() 

531 self.strategies = [] 

532 for path in self.paths: 

533 for strategy_class in self.STRATEGIES: 

534 if strategy_class.supports(path): 

535 self.strategies.append(strategy_class(path)) 

536 break 

537 else: 

538 raise NotImplementedError('No strategy found for path') 

539 

540 def __repr__(self): 

541 paths = [] 

542 for path in self.paths: 

543 steps = [] 

544 for axis, nodetest, predicates in path: 

545 steps.append('%s::%s' % (axis, nodetest)) 

546 for predicate in predicates: 

547 steps[-1] += '[%s]' % predicate 

548 paths.append('/'.join(steps)) 

549 return '<%s "%s">' % (type(self).__name__, '|'.join(paths)) 

550 

551 def select(self, stream, namespaces=None, variables=None): 

552 """Returns a substream of the given stream that matches the path. 

553  

554 If there are no matches, this method returns an empty stream. 

555  

556 >>> from genshi.input import XML 

557 >>> xml = XML('<root><elem><child>Text</child></elem></root>') 

558  

559 >>> print(Path('.//child').select(xml)) 

560 <child>Text</child> 

561  

562 >>> print(Path('.//child/text()').select(xml)) 

563 Text 

564  

565 :param stream: the stream to select from 

566 :param namespaces: (optional) a mapping of namespace prefixes to URIs 

567 :param variables: (optional) a mapping of variable names to values 

568 :return: the substream matching the path, or an empty stream 

569 :rtype: `Stream` 

570 """ 

571 if namespaces is None: 

572 namespaces = {} 

573 if variables is None: 

574 variables = {} 

575 stream = iter(stream) 

576 def _generate(stream=stream, ns=namespaces, vs=variables): 

577 _next = lambda: next(stream) 

578 test = self.test() 

579 for event in stream: 

580 result = test(event, ns, vs) 

581 if result is True: 

582 yield event 

583 if event[0] is START: 

584 depth = 1 

585 while depth > 0: 

586 subevent = _next() 

587 if subevent[0] is START: 

588 depth += 1 

589 elif subevent[0] is END: 

590 depth -= 1 

591 yield subevent 

592 test(subevent, ns, vs, updateonly=True) 

593 elif result: 

594 yield result 

595 return Stream(_generate(), 

596 serializer=getattr(stream, 'serializer', None)) 

597 

598 def test(self, ignore_context=False): 

599 """Returns a function that can be used to track whether the path matches 

600 a specific stream event. 

601  

602 The function returned expects the positional arguments ``event``, 

603 ``namespaces`` and ``variables``. The first is a stream event, while the 

604 latter two are a mapping of namespace prefixes to URIs, and a mapping 

605 of variable names to values, respectively. In addition, the function 

606 accepts an ``updateonly`` keyword argument that default to ``False``. If 

607 it is set to ``True``, the function only updates its internal state, 

608 but does not perform any tests or return a result. 

609  

610 If the path matches the event, the function returns the match (for 

611 example, a `START` or `TEXT` event.) Otherwise, it returns ``None``. 

612  

613 >>> from genshi.input import XML 

614 >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>') 

615 >>> test = Path('child').test() 

616 >>> namespaces, variables = {}, {} 

617 >>> for event in xml: 

618 ... if test(event, namespaces, variables): 

619 ... print('%s %r' % (event[0], event[1])) 

620 START (QName('child'), Attrs([(QName('id'), '2')])) 

621  

622 :param ignore_context: if `True`, the path is interpreted like a pattern 

623 in XSLT, meaning for example that it will match 

624 at any depth 

625 :return: a function that can be used to test individual events in a 

626 stream against the path 

627 :rtype: ``function`` 

628 """ 

629 tests = [s.test(ignore_context) for s in self.strategies] 

630 if len(tests) == 1: 

631 return tests[0] 

632 

633 def _multi(event, namespaces, variables, updateonly=False): 

634 retval = None 

635 for test in tests: 

636 val = test(event, namespaces, variables, updateonly=updateonly) 

637 if retval is None: 

638 retval = val 

639 return retval 

640 return _multi 

641 

642 

643class PathSyntaxError(Exception): 

644 """Exception raised when an XPath expression is syntactically incorrect.""" 

645 

646 def __init__(self, message, filename=None, lineno=-1, offset=-1): 

647 if filename: 

648 message = '%s (%s, line %d)' % (message, filename, lineno) 

649 Exception.__init__(self, message) 

650 self.filename = filename 

651 self.lineno = lineno 

652 self.offset = offset 

653 

654 

655class PathParser(object): 

656 """Tokenizes and parses an XPath expression.""" 

657 

658 _QUOTES = (("'", "'"), ('"', '"')) 

659 _TOKENS = ('::', ':', '..', '.', '//', '/', '[', ']', '()', '(', ')', '@', 

660 '=', '!=', '!', '|', ',', '>=', '>', '<=', '<', '$') 

661 _tokenize = re.compile(r'("[^"]*")|(\'[^\']*\')|((?:\d+)?\.\d+)|(%s)|([^%s\s]+)|\s+' % ( 

662 '|'.join([re.escape(t) for t in _TOKENS]), 

663 ''.join([re.escape(t[0]) for t in _TOKENS]))).findall 

664 

665 def __init__(self, text, filename=None, lineno=-1): 

666 self.filename = filename 

667 self.lineno = lineno 

668 self.tokens = [t for t in [dqstr or sqstr or number or token or name 

669 for dqstr, sqstr, number, token, name in 

670 self._tokenize(text)] if t] 

671 self.pos = 0 

672 

673 # Tokenizer 

674 

675 @property 

676 def at_end(self): 

677 return self.pos == len(self.tokens) - 1 

678 

679 @property 

680 def cur_token(self): 

681 return self.tokens[self.pos] 

682 

683 def next_token(self): 

684 self.pos += 1 

685 return self.tokens[self.pos] 

686 

687 def peek_token(self): 

688 if not self.at_end: 

689 return self.tokens[self.pos + 1] 

690 return None 

691 

692 # Recursive descent parser 

693 

694 def parse(self): 

695 """Parses the XPath expression and returns a list of location path 

696 tests. 

697  

698 For union expressions (such as `*|text()`), this function returns one 

699 test for each operand in the union. For patch expressions that don't 

700 use the union operator, the function always returns a list of size 1. 

701  

702 Each path test in turn is a sequence of tests that correspond to the 

703 location steps, each tuples of the form `(axis, testfunc, predicates)` 

704 """ 

705 paths = [self._location_path()] 

706 while self.cur_token == '|': 

707 self.next_token() 

708 paths.append(self._location_path()) 

709 if not self.at_end: 

710 raise PathSyntaxError('Unexpected token %r after end of expression' 

711 % self.cur_token, self.filename, self.lineno) 

712 return paths 

713 

714 def _location_path(self): 

715 steps = [] 

716 while True: 

717 if self.cur_token.startswith('/'): 

718 if not steps: 

719 if self.cur_token == '//': 

720 # hack to make //* match every node - also root 

721 self.next_token() 

722 axis, nodetest, predicates = self._location_step() 

723 steps.append((DESCENDANT_OR_SELF, nodetest, 

724 predicates)) 

725 if self.at_end or not self.cur_token.startswith('/'): 

726 break 

727 continue 

728 else: 

729 raise PathSyntaxError('Absolute location paths not ' 

730 'supported', self.filename, 

731 self.lineno) 

732 elif self.cur_token == '//': 

733 steps.append((DESCENDANT_OR_SELF, NodeTest(), [])) 

734 self.next_token() 

735 

736 axis, nodetest, predicates = self._location_step() 

737 if not axis: 

738 axis = CHILD 

739 steps.append((axis, nodetest, predicates)) 

740 if self.at_end or not self.cur_token.startswith('/'): 

741 break 

742 

743 return steps 

744 

745 def _location_step(self): 

746 if self.cur_token == '@': 

747 axis = ATTRIBUTE 

748 self.next_token() 

749 elif self.cur_token == '.': 

750 axis = SELF 

751 elif self.cur_token == '..': 

752 raise PathSyntaxError('Unsupported axis "parent"', self.filename, 

753 self.lineno) 

754 elif self.peek_token() == '::': 

755 axis = Axis.forname(self.cur_token) 

756 if axis is None: 

757 raise PathSyntaxError('Unsupport axis "%s"' % axis, 

758 self.filename, self.lineno) 

759 self.next_token() 

760 self.next_token() 

761 else: 

762 axis = None 

763 nodetest = self._node_test(axis or CHILD) 

764 predicates = [] 

765 while self.cur_token == '[': 

766 predicates.append(self._predicate()) 

767 return axis, nodetest, predicates 

768 

769 def _node_test(self, axis=None): 

770 test = prefix = None 

771 next_token = self.peek_token() 

772 if next_token in ('(', '()'): # Node type test 

773 test = self._node_type() 

774 

775 elif next_token == ':': # Namespace prefix 

776 prefix = self.cur_token 

777 self.next_token() 

778 localname = self.next_token() 

779 if localname == '*': 

780 test = QualifiedPrincipalTypeTest(axis, prefix) 

781 else: 

782 test = QualifiedNameTest(axis, prefix, localname) 

783 

784 else: # Name test 

785 if self.cur_token == '*': 

786 test = PrincipalTypeTest(axis) 

787 elif self.cur_token == '.': 

788 test = NodeTest() 

789 else: 

790 test = LocalNameTest(axis, self.cur_token) 

791 

792 if not self.at_end: 

793 self.next_token() 

794 return test 

795 

796 def _node_type(self): 

797 name = self.cur_token 

798 self.next_token() 

799 

800 args = [] 

801 if self.cur_token != '()': 

802 # The processing-instruction() function optionally accepts the 

803 # name of the PI as argument, which must be a literal string 

804 self.next_token() # ( 

805 if self.cur_token != ')': 

806 string = self.cur_token 

807 if (string[0], string[-1]) in self._QUOTES: 

808 string = string[1:-1] 

809 args.append(string) 

810 

811 cls = _nodetest_map.get(name) 

812 if not cls: 

813 raise PathSyntaxError('%s() not allowed here' % name, self.filename, 

814 self.lineno) 

815 return cls(*args) 

816 

817 def _predicate(self): 

818 assert self.cur_token == '[' 

819 self.next_token() 

820 expr = self._or_expr() 

821 if self.cur_token != ']': 

822 raise PathSyntaxError('Expected "]" to close predicate, ' 

823 'but found "%s"' % self.cur_token, 

824 self.filename, self.lineno) 

825 if not self.at_end: 

826 self.next_token() 

827 return expr 

828 

829 def _or_expr(self): 

830 expr = self._and_expr() 

831 while self.cur_token == 'or': 

832 self.next_token() 

833 expr = OrOperator(expr, self._and_expr()) 

834 return expr 

835 

836 def _and_expr(self): 

837 expr = self._equality_expr() 

838 while self.cur_token == 'and': 

839 self.next_token() 

840 expr = AndOperator(expr, self._equality_expr()) 

841 return expr 

842 

843 def _equality_expr(self): 

844 expr = self._relational_expr() 

845 while self.cur_token in ('=', '!='): 

846 op = _operator_map[self.cur_token] 

847 self.next_token() 

848 expr = op(expr, self._relational_expr()) 

849 return expr 

850 

851 def _relational_expr(self): 

852 expr = self._sub_expr() 

853 while self.cur_token in ('>', '>=', '<', '>='): 

854 op = _operator_map[self.cur_token] 

855 self.next_token() 

856 expr = op(expr, self._sub_expr()) 

857 return expr 

858 

859 def _sub_expr(self): 

860 token = self.cur_token 

861 if token != '(': 

862 return self._primary_expr() 

863 self.next_token() 

864 expr = self._or_expr() 

865 if self.cur_token != ')': 

866 raise PathSyntaxError('Expected ")" to close sub-expression, ' 

867 'but found "%s"' % self.cur_token, 

868 self.filename, self.lineno) 

869 self.next_token() 

870 return expr 

871 

872 def _primary_expr(self): 

873 token = self.cur_token 

874 if len(token) > 1 and (token[0], token[-1]) in self._QUOTES: 

875 self.next_token() 

876 return StringLiteral(token[1:-1]) 

877 elif token[0].isdigit() or token[0] == '.': 

878 self.next_token() 

879 return NumberLiteral(as_float(token)) 

880 elif token == '$': 

881 token = self.next_token() 

882 self.next_token() 

883 return VariableReference(token) 

884 elif not self.at_end and self.peek_token().startswith('('): 

885 return self._function_call() 

886 else: 

887 axis = None 

888 if token == '@': 

889 axis = ATTRIBUTE 

890 self.next_token() 

891 return self._node_test(axis) 

892 

893 def _function_call(self): 

894 name = self.cur_token 

895 if self.next_token() == '()': 

896 args = [] 

897 else: 

898 assert self.cur_token == '(' 

899 self.next_token() 

900 args = [self._or_expr()] 

901 while self.cur_token == ',': 

902 self.next_token() 

903 args.append(self._or_expr()) 

904 if not self.cur_token == ')': 

905 raise PathSyntaxError('Expected ")" to close function argument ' 

906 'list, but found "%s"' % self.cur_token, 

907 self.filename, self.lineno) 

908 self.next_token() 

909 cls = _function_map.get(name) 

910 if not cls: 

911 raise PathSyntaxError('Unsupported function "%s"' % name, 

912 self.filename, self.lineno) 

913 return cls(*args) 

914 

915 

916# Type coercion 

917 

918def as_scalar(value): 

919 """Convert value to a scalar. If a single element Attrs() object is passed 

920 the value of the single attribute will be returned.""" 

921 if isinstance(value, Attrs): 

922 assert len(value) == 1 

923 return value[0][1] 

924 else: 

925 return value 

926 

927def as_float(value): 

928 # FIXME - if value is a bool it will be coerced to 0.0 and consequently 

929 # compared as a float. This is probably not ideal. 

930 return float(as_scalar(value)) 

931 

932def as_long(value): 

933 long_cls = long if IS_PYTHON2 else int 

934 return long_cls(as_scalar(value)) 

935 

936def as_string(value): 

937 value = as_scalar(value) 

938 if value is False: 

939 return '' 

940 return text_type(value) 

941 

942def as_bool(value): 

943 return bool(as_scalar(value)) 

944 

945 

946# Node tests 

947 

948class PrincipalTypeTest(object): 

949 """Node test that matches any event with the given principal type.""" 

950 __slots__ = ['principal_type'] 

951 def __init__(self, principal_type): 

952 self.principal_type = principal_type 

953 def __call__(self, kind, data, pos, namespaces, variables): 

954 if kind is START: 

955 if self.principal_type is ATTRIBUTE: 

956 return data[1] or None 

957 else: 

958 return True 

959 def __repr__(self): 

960 return '*' 

961 

962class QualifiedPrincipalTypeTest(object): 

963 """Node test that matches any event with the given principal type in a 

964 specific namespace.""" 

965 __slots__ = ['principal_type', 'prefix'] 

966 def __init__(self, principal_type, prefix): 

967 self.principal_type = principal_type 

968 self.prefix = prefix 

969 def __call__(self, kind, data, pos, namespaces, variables): 

970 namespace = Namespace(namespaces.get(self.prefix)) 

971 if kind is START: 

972 if self.principal_type is ATTRIBUTE and data[1]: 

973 return Attrs([(name, value) for name, value in data[1] 

974 if name in namespace]) or None 

975 else: 

976 return data[0] in namespace 

977 def __repr__(self): 

978 return '%s:*' % self.prefix 

979 

980class LocalNameTest(object): 

981 """Node test that matches any event with the given principal type and 

982 local name. 

983 """ 

984 __slots__ = ['principal_type', 'name'] 

985 def __init__(self, principal_type, name): 

986 self.principal_type = principal_type 

987 self.name = name 

988 def __call__(self, kind, data, pos, namespaces, variables): 

989 if kind is START: 

990 if self.principal_type is ATTRIBUTE and self.name in data[1]: 

991 return Attrs([(self.name, data[1].get(self.name))]) 

992 else: 

993 return data[0].localname == self.name 

994 def __repr__(self): 

995 return self.name 

996 

997class QualifiedNameTest(object): 

998 """Node test that matches any event with the given principal type and 

999 qualified name. 

1000 """ 

1001 __slots__ = ['principal_type', 'prefix', 'name'] 

1002 def __init__(self, principal_type, prefix, name): 

1003 self.principal_type = principal_type 

1004 self.prefix = prefix 

1005 self.name = name 

1006 def __call__(self, kind, data, pos, namespaces, variables): 

1007 qname = QName('%s}%s' % (namespaces.get(self.prefix), self.name)) 

1008 if kind is START: 

1009 if self.principal_type is ATTRIBUTE and qname in data[1]: 

1010 return Attrs([(qname, data[1].get(qname))]) 

1011 else: 

1012 return data[0] == qname 

1013 def __repr__(self): 

1014 return '%s:%s' % (self.prefix, self.name) 

1015 

1016class CommentNodeTest(object): 

1017 """Node test that matches any comment events.""" 

1018 __slots__ = [] 

1019 def __call__(self, kind, data, pos, namespaces, variables): 

1020 return kind is COMMENT 

1021 def __repr__(self): 

1022 return 'comment()' 

1023 

1024class NodeTest(object): 

1025 """Node test that matches any node.""" 

1026 __slots__ = [] 

1027 def __call__(self, kind, data, pos, namespaces, variables): 

1028 if kind is START: 

1029 return True 

1030 return kind, data, pos 

1031 def __repr__(self): 

1032 return 'node()' 

1033 

1034class ProcessingInstructionNodeTest(object): 

1035 """Node test that matches any processing instruction event.""" 

1036 __slots__ = ['target'] 

1037 def __init__(self, target=None): 

1038 self.target = target 

1039 def __call__(self, kind, data, pos, namespaces, variables): 

1040 return kind is PI and (not self.target or data[0] == self.target) 

1041 def __repr__(self): 

1042 arg = '' 

1043 if self.target: 

1044 arg = '"' + self.target + '"' 

1045 return 'processing-instruction(%s)' % arg 

1046 

1047class TextNodeTest(object): 

1048 """Node test that matches any text event.""" 

1049 __slots__ = [] 

1050 def __call__(self, kind, data, pos, namespaces, variables): 

1051 return kind is TEXT 

1052 def __repr__(self): 

1053 return 'text()' 

1054 

1055_nodetest_map = {'comment': CommentNodeTest, 'node': NodeTest, 

1056 'processing-instruction': ProcessingInstructionNodeTest, 

1057 'text': TextNodeTest} 

1058 

1059# Functions 

1060 

1061class Function(object): 

1062 """Base class for function nodes in XPath expressions.""" 

1063 

1064class BooleanFunction(Function): 

1065 """The `boolean` function, which converts its argument to a boolean 

1066 value. 

1067 """ 

1068 __slots__ = ['expr'] 

1069 _return_type = bool 

1070 def __init__(self, expr): 

1071 self.expr = expr 

1072 def __call__(self, kind, data, pos, namespaces, variables): 

1073 val = self.expr(kind, data, pos, namespaces, variables) 

1074 return as_bool(val) 

1075 def __repr__(self): 

1076 return 'boolean(%r)' % self.expr 

1077 

1078class CeilingFunction(Function): 

1079 """The `ceiling` function, which returns the nearest lower integer number 

1080 for the given number. 

1081 """ 

1082 __slots__ = ['number'] 

1083 def __init__(self, number): 

1084 self.number = number 

1085 def __call__(self, kind, data, pos, namespaces, variables): 

1086 number = self.number(kind, data, pos, namespaces, variables) 

1087 return ceil(as_float(number)) 

1088 def __repr__(self): 

1089 return 'ceiling(%r)' % self.number 

1090 

1091class ConcatFunction(Function): 

1092 """The `concat` function, which concatenates (joins) the variable number of 

1093 strings it gets as arguments. 

1094 """ 

1095 __slots__ = ['exprs'] 

1096 def __init__(self, *exprs): 

1097 self.exprs = exprs 

1098 def __call__(self, kind, data, pos, namespaces, variables): 

1099 strings = [] 

1100 for item in [expr(kind, data, pos, namespaces, variables) 

1101 for expr in self.exprs]: 

1102 strings.append(as_string(item)) 

1103 return ''.join(strings) 

1104 def __repr__(self): 

1105 return 'concat(%s)' % ', '.join([repr(expr) for expr in self.exprs]) 

1106 

1107class ContainsFunction(Function): 

1108 """The `contains` function, which returns whether a string contains a given 

1109 substring. 

1110 """ 

1111 __slots__ = ['string1', 'string2'] 

1112 def __init__(self, string1, string2): 

1113 self.string1 = string1 

1114 self.string2 = string2 

1115 def __call__(self, kind, data, pos, namespaces, variables): 

1116 string1 = self.string1(kind, data, pos, namespaces, variables) 

1117 string2 = self.string2(kind, data, pos, namespaces, variables) 

1118 return as_string(string2) in as_string(string1) 

1119 def __repr__(self): 

1120 return 'contains(%r, %r)' % (self.string1, self.string2) 

1121 

1122class MatchesFunction(Function): 

1123 """The `matches` function, which returns whether a string matches a regular 

1124 expression. 

1125 """ 

1126 __slots__ = ['string1', 'string2'] 

1127 flag_mapping = {'s': re.S, 'm': re.M, 'i': re.I, 'x': re.X} 

1128 

1129 def __init__(self, string1, string2, flags=''): 

1130 self.string1 = string1 

1131 self.string2 = string2 

1132 self.flags = self._map_flags(flags) 

1133 def __call__(self, kind, data, pos, namespaces, variables): 

1134 string1 = as_string(self.string1(kind, data, pos, namespaces, variables)) 

1135 string2 = as_string(self.string2(kind, data, pos, namespaces, variables)) 

1136 return re.search(string2, string1, self.flags) 

1137 def _map_flags(self, flags): 

1138 return reduce(operator.or_, 

1139 [self.flag_map[flag] for flag in flags], re.U) 

1140 def __repr__(self): 

1141 return 'contains(%r, %r)' % (self.string1, self.string2) 

1142 

1143class FalseFunction(Function): 

1144 """The `false` function, which always returns the boolean `false` value.""" 

1145 __slots__ = [] 

1146 def __call__(self, kind, data, pos, namespaces, variables): 

1147 return False 

1148 def __repr__(self): 

1149 return 'false()' 

1150 

1151class FloorFunction(Function): 

1152 """The `ceiling` function, which returns the nearest higher integer number 

1153 for the given number. 

1154 """ 

1155 __slots__ = ['number'] 

1156 def __init__(self, number): 

1157 self.number = number 

1158 def __call__(self, kind, data, pos, namespaces, variables): 

1159 number = self.number(kind, data, pos, namespaces, variables) 

1160 return floor(as_float(number)) 

1161 def __repr__(self): 

1162 return 'floor(%r)' % self.number 

1163 

1164class LocalNameFunction(Function): 

1165 """The `local-name` function, which returns the local name of the current 

1166 element. 

1167 """ 

1168 __slots__ = [] 

1169 def __call__(self, kind, data, pos, namespaces, variables): 

1170 if kind is START: 

1171 return data[0].localname 

1172 def __repr__(self): 

1173 return 'local-name()' 

1174 

1175class NameFunction(Function): 

1176 """The `name` function, which returns the qualified name of the current 

1177 element. 

1178 """ 

1179 __slots__ = [] 

1180 def __call__(self, kind, data, pos, namespaces, variables): 

1181 if kind is START: 

1182 return data[0] 

1183 def __repr__(self): 

1184 return 'name()' 

1185 

1186class NamespaceUriFunction(Function): 

1187 """The `namespace-uri` function, which returns the namespace URI of the 

1188 current element. 

1189 """ 

1190 __slots__ = [] 

1191 def __call__(self, kind, data, pos, namespaces, variables): 

1192 if kind is START: 

1193 return data[0].namespace 

1194 def __repr__(self): 

1195 return 'namespace-uri()' 

1196 

1197class NotFunction(Function): 

1198 """The `not` function, which returns the negated boolean value of its 

1199 argument. 

1200 """ 

1201 __slots__ = ['expr'] 

1202 def __init__(self, expr): 

1203 self.expr = expr 

1204 def __call__(self, kind, data, pos, namespaces, variables): 

1205 return not as_bool(self.expr(kind, data, pos, namespaces, variables)) 

1206 def __repr__(self): 

1207 return 'not(%s)' % self.expr 

1208 

1209class NormalizeSpaceFunction(Function): 

1210 """The `normalize-space` function, which removes leading and trailing 

1211 whitespace in the given string, and replaces multiple adjacent whitespace 

1212 characters inside the string with a single space. 

1213 """ 

1214 __slots__ = ['expr'] 

1215 _normalize = re.compile(r'\s{2,}').sub 

1216 def __init__(self, expr): 

1217 self.expr = expr 

1218 def __call__(self, kind, data, pos, namespaces, variables): 

1219 string = self.expr(kind, data, pos, namespaces, variables) 

1220 return self._normalize(' ', as_string(string).strip()) 

1221 def __repr__(self): 

1222 return 'normalize-space(%s)' % repr(self.expr) 

1223 

1224class NumberFunction(Function): 

1225 """The `number` function that converts its argument to a number.""" 

1226 __slots__ = ['expr'] 

1227 def __init__(self, expr): 

1228 self.expr = expr 

1229 def __call__(self, kind, data, pos, namespaces, variables): 

1230 val = self.expr(kind, data, pos, namespaces, variables) 

1231 return as_float(val) 

1232 def __repr__(self): 

1233 return 'number(%r)' % self.expr 

1234 

1235class RoundFunction(Function): 

1236 """The `round` function, which returns the nearest integer number for the 

1237 given number. 

1238 """ 

1239 __slots__ = ['number'] 

1240 def __init__(self, number): 

1241 self.number = number 

1242 def __call__(self, kind, data, pos, namespaces, variables): 

1243 number = self.number(kind, data, pos, namespaces, variables) 

1244 return round(as_float(number)) 

1245 def __repr__(self): 

1246 return 'round(%r)' % self.number 

1247 

1248class StartsWithFunction(Function): 

1249 """The `starts-with` function that returns whether one string starts with 

1250 a given substring. 

1251 """ 

1252 __slots__ = ['string1', 'string2'] 

1253 def __init__(self, string1, string2): 

1254 self.string1 = string1 

1255 self.string2 = string2 

1256 def __call__(self, kind, data, pos, namespaces, variables): 

1257 string1 = self.string1(kind, data, pos, namespaces, variables) 

1258 string2 = self.string2(kind, data, pos, namespaces, variables) 

1259 return as_string(string1).startswith(as_string(string2)) 

1260 def __repr__(self): 

1261 return 'starts-with(%r, %r)' % (self.string1, self.string2) 

1262 

1263class StringLengthFunction(Function): 

1264 """The `string-length` function that returns the length of the given 

1265 string. 

1266 """ 

1267 __slots__ = ['expr'] 

1268 def __init__(self, expr): 

1269 self.expr = expr 

1270 def __call__(self, kind, data, pos, namespaces, variables): 

1271 string = self.expr(kind, data, pos, namespaces, variables) 

1272 return len(as_string(string)) 

1273 def __repr__(self): 

1274 return 'string-length(%r)' % self.expr 

1275 

1276class SubstringFunction(Function): 

1277 """The `substring` function that returns the part of a string that starts 

1278 at the given offset, and optionally limited to the given length. 

1279 """ 

1280 __slots__ = ['string', 'start', 'length'] 

1281 def __init__(self, string, start, length=None): 

1282 self.string = string 

1283 self.start = start 

1284 self.length = length 

1285 def __call__(self, kind, data, pos, namespaces, variables): 

1286 string = self.string(kind, data, pos, namespaces, variables) 

1287 start = self.start(kind, data, pos, namespaces, variables) 

1288 length = 0 

1289 if self.length is not None: 

1290 length = self.length(kind, data, pos, namespaces, variables) 

1291 return string[as_long(start):len(as_string(string)) - as_long(length)] 

1292 def __repr__(self): 

1293 if self.length is not None: 

1294 return 'substring(%r, %r, %r)' % (self.string, self.start, 

1295 self.length) 

1296 else: 

1297 return 'substring(%r, %r)' % (self.string, self.start) 

1298 

1299class SubstringAfterFunction(Function): 

1300 """The `substring-after` function that returns the part of a string that 

1301 is found after the given substring. 

1302 """ 

1303 __slots__ = ['string1', 'string2'] 

1304 def __init__(self, string1, string2): 

1305 self.string1 = string1 

1306 self.string2 = string2 

1307 def __call__(self, kind, data, pos, namespaces, variables): 

1308 string1 = as_string(self.string1(kind, data, pos, namespaces, variables)) 

1309 string2 = as_string(self.string2(kind, data, pos, namespaces, variables)) 

1310 index = string1.find(string2) 

1311 if index >= 0: 

1312 return string1[index + len(string2):] 

1313 return '' 

1314 def __repr__(self): 

1315 return 'substring-after(%r, %r)' % (self.string1, self.string2) 

1316 

1317class SubstringBeforeFunction(Function): 

1318 """The `substring-before` function that returns the part of a string that 

1319 is found before the given substring. 

1320 """ 

1321 __slots__ = ['string1', 'string2'] 

1322 def __init__(self, string1, string2): 

1323 self.string1 = string1 

1324 self.string2 = string2 

1325 def __call__(self, kind, data, pos, namespaces, variables): 

1326 string1 = as_string(self.string1(kind, data, pos, namespaces, variables)) 

1327 string2 = as_string(self.string2(kind, data, pos, namespaces, variables)) 

1328 index = string1.find(string2) 

1329 if index >= 0: 

1330 return string1[:index] 

1331 return '' 

1332 def __repr__(self): 

1333 return 'substring-after(%r, %r)' % (self.string1, self.string2) 

1334 

1335class TranslateFunction(Function): 

1336 """The `translate` function that translates a set of characters in a 

1337 string to target set of characters. 

1338 """ 

1339 __slots__ = ['string', 'fromchars', 'tochars'] 

1340 def __init__(self, string, fromchars, tochars): 

1341 self.string = string 

1342 self.fromchars = fromchars 

1343 self.tochars = tochars 

1344 def __call__(self, kind, data, pos, namespaces, variables): 

1345 string = as_string(self.string(kind, data, pos, namespaces, variables)) 

1346 fromchars = as_string(self.fromchars(kind, data, pos, namespaces, variables)) 

1347 tochars = as_string(self.tochars(kind, data, pos, namespaces, variables)) 

1348 table = dict(zip([ord(c) for c in fromchars], 

1349 [ord(c) for c in tochars])) 

1350 return string.translate(table) 

1351 def __repr__(self): 

1352 return 'translate(%r, %r, %r)' % (self.string, self.fromchars, 

1353 self.tochars) 

1354 

1355class TrueFunction(Function): 

1356 """The `true` function, which always returns the boolean `true` value.""" 

1357 __slots__ = [] 

1358 def __call__(self, kind, data, pos, namespaces, variables): 

1359 return True 

1360 def __repr__(self): 

1361 return 'true()' 

1362 

1363_function_map = {'boolean': BooleanFunction, 'ceiling': CeilingFunction, 

1364 'concat': ConcatFunction, 'contains': ContainsFunction, 

1365 'matches': MatchesFunction, 'false': FalseFunction, 'floor': 

1366 FloorFunction, 'local-name': LocalNameFunction, 'name': 

1367 NameFunction, 'namespace-uri': NamespaceUriFunction, 

1368 'normalize-space': NormalizeSpaceFunction, 'not': NotFunction, 

1369 'number': NumberFunction, 'round': RoundFunction, 

1370 'starts-with': StartsWithFunction, 'string-length': 

1371 StringLengthFunction, 'substring': SubstringFunction, 

1372 'substring-after': SubstringAfterFunction, 'substring-before': 

1373 SubstringBeforeFunction, 'translate': TranslateFunction, 

1374 'true': TrueFunction} 

1375 

1376# Literals & Variables 

1377 

1378class Literal(object): 

1379 """Abstract base class for literal nodes.""" 

1380 

1381class StringLiteral(Literal): 

1382 """A string literal node.""" 

1383 __slots__ = ['text'] 

1384 def __init__(self, text): 

1385 self.text = text 

1386 def __call__(self, kind, data, pos, namespaces, variables): 

1387 return self.text 

1388 def __repr__(self): 

1389 return '"%s"' % self.text 

1390 

1391class NumberLiteral(Literal): 

1392 """A number literal node.""" 

1393 __slots__ = ['number'] 

1394 def __init__(self, number): 

1395 self.number = number 

1396 def __call__(self, kind, data, pos, namespaces, variables): 

1397 return self.number 

1398 def __repr__(self): 

1399 return str(self.number) 

1400 

1401class VariableReference(Literal): 

1402 """A variable reference node.""" 

1403 __slots__ = ['name'] 

1404 def __init__(self, name): 

1405 self.name = name 

1406 def __call__(self, kind, data, pos, namespaces, variables): 

1407 return variables.get(self.name) 

1408 def __repr__(self): 

1409 return str(self.name) 

1410 

1411# Operators 

1412 

1413class AndOperator(object): 

1414 """The boolean operator `and`.""" 

1415 __slots__ = ['lval', 'rval'] 

1416 def __init__(self, lval, rval): 

1417 self.lval = lval 

1418 self.rval = rval 

1419 def __call__(self, kind, data, pos, namespaces, variables): 

1420 lval = as_bool(self.lval(kind, data, pos, namespaces, variables)) 

1421 if not lval: 

1422 return False 

1423 rval = self.rval(kind, data, pos, namespaces, variables) 

1424 return as_bool(rval) 

1425 def __repr__(self): 

1426 return '%s and %s' % (self.lval, self.rval) 

1427 

1428class EqualsOperator(object): 

1429 """The equality operator `=`.""" 

1430 __slots__ = ['lval', 'rval'] 

1431 def __init__(self, lval, rval): 

1432 self.lval = lval 

1433 self.rval = rval 

1434 def __call__(self, kind, data, pos, namespaces, variables): 

1435 lval = as_scalar(self.lval(kind, data, pos, namespaces, variables)) 

1436 rval = as_scalar(self.rval(kind, data, pos, namespaces, variables)) 

1437 return lval == rval 

1438 def __repr__(self): 

1439 return '%s=%s' % (self.lval, self.rval) 

1440 

1441class NotEqualsOperator(object): 

1442 """The equality operator `!=`.""" 

1443 __slots__ = ['lval', 'rval'] 

1444 def __init__(self, lval, rval): 

1445 self.lval = lval 

1446 self.rval = rval 

1447 def __call__(self, kind, data, pos, namespaces, variables): 

1448 lval = as_scalar(self.lval(kind, data, pos, namespaces, variables)) 

1449 rval = as_scalar(self.rval(kind, data, pos, namespaces, variables)) 

1450 return lval != rval 

1451 def __repr__(self): 

1452 return '%s!=%s' % (self.lval, self.rval) 

1453 

1454class OrOperator(object): 

1455 """The boolean operator `or`.""" 

1456 __slots__ = ['lval', 'rval'] 

1457 def __init__(self, lval, rval): 

1458 self.lval = lval 

1459 self.rval = rval 

1460 def __call__(self, kind, data, pos, namespaces, variables): 

1461 lval = as_bool(self.lval(kind, data, pos, namespaces, variables)) 

1462 if lval: 

1463 return True 

1464 rval = self.rval(kind, data, pos, namespaces, variables) 

1465 return as_bool(rval) 

1466 def __repr__(self): 

1467 return '%s or %s' % (self.lval, self.rval) 

1468 

1469class GreaterThanOperator(object): 

1470 """The relational operator `>` (greater than).""" 

1471 __slots__ = ['lval', 'rval'] 

1472 def __init__(self, lval, rval): 

1473 self.lval = lval 

1474 self.rval = rval 

1475 def __call__(self, kind, data, pos, namespaces, variables): 

1476 lval = self.lval(kind, data, pos, namespaces, variables) 

1477 rval = self.rval(kind, data, pos, namespaces, variables) 

1478 return as_float(lval) > as_float(rval) 

1479 def __repr__(self): 

1480 return '%s>%s' % (self.lval, self.rval) 

1481 

1482class GreaterThanOrEqualOperator(object): 

1483 """The relational operator `>=` (greater than or equal).""" 

1484 __slots__ = ['lval', 'rval'] 

1485 def __init__(self, lval, rval): 

1486 self.lval = lval 

1487 self.rval = rval 

1488 def __call__(self, kind, data, pos, namespaces, variables): 

1489 lval = self.lval(kind, data, pos, namespaces, variables) 

1490 rval = self.rval(kind, data, pos, namespaces, variables) 

1491 return as_float(lval) >= as_float(rval) 

1492 def __repr__(self): 

1493 return '%s>=%s' % (self.lval, self.rval) 

1494 

1495class LessThanOperator(object): 

1496 """The relational operator `<` (less than).""" 

1497 __slots__ = ['lval', 'rval'] 

1498 def __init__(self, lval, rval): 

1499 self.lval = lval 

1500 self.rval = rval 

1501 def __call__(self, kind, data, pos, namespaces, variables): 

1502 lval = self.lval(kind, data, pos, namespaces, variables) 

1503 rval = self.rval(kind, data, pos, namespaces, variables) 

1504 return as_float(lval) < as_float(rval) 

1505 def __repr__(self): 

1506 return '%s<%s' % (self.lval, self.rval) 

1507 

1508class LessThanOrEqualOperator(object): 

1509 """The relational operator `<=` (less than or equal).""" 

1510 __slots__ = ['lval', 'rval'] 

1511 def __init__(self, lval, rval): 

1512 self.lval = lval 

1513 self.rval = rval 

1514 def __call__(self, kind, data, pos, namespaces, variables): 

1515 lval = self.lval(kind, data, pos, namespaces, variables) 

1516 rval = self.rval(kind, data, pos, namespaces, variables) 

1517 return as_float(lval) <= as_float(rval) 

1518 def __repr__(self): 

1519 return '%s<=%s' % (self.lval, self.rval) 

1520 

1521_operator_map = {'=': EqualsOperator, '!=': NotEqualsOperator, 

1522 '>': GreaterThanOperator, '>=': GreaterThanOrEqualOperator, 

1523 '<': LessThanOperator, '>=': LessThanOrEqualOperator} 

1524 

1525 

1526_DOTSLASHSLASH = (DESCENDANT_OR_SELF, PrincipalTypeTest(None), ()) 

1527_DOTSLASH = (SELF, PrincipalTypeTest(None), ())