Changeset 1025
- Timestamp:
- Mar 11, 2009, 6:03:03 PM (15 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
- 1 copied
-
examples/bench/xpath.py (copied) (copied from branches/experimental/soc2008-xpath/examples/bench/xpath.py)
-
genshi/filters/transform.py (modified) (3 diffs)
-
genshi/path.py (modified) (13 diffs)
-
genshi/tests/path.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/genshi/filters/transform.py
r1000 r1025 711 711 test = self.path.test() 712 712 stream = iter(stream) 713 next = stream.next 713 714 for mark, event in stream: 714 715 if mark is None: 715 716 yield mark, event 716 717 continue 717 result = test(event, {}, {})718 result = test(event, namespaces, variables) 718 719 # XXX This is effectively genshi.core._ensure() for transform 719 720 # streams. … … 723 724 depth = 1 724 725 while depth > 0: 725 mark, subevent = stream.next()726 mark, subevent = next() 726 727 if subevent[0] is START: 727 728 depth += 1 … … 732 733 else: 733 734 yield INSIDE, subevent 734 test(subevent, {}, {}, updateonly=True)735 test(subevent, namespaces, variables, updateonly=True) 735 736 else: 736 737 yield OUTSIDE, event -
trunk/genshi/path.py
r970 r1025 39 39 """ 40 40 41 from collections import deque 41 42 try: 42 43 from functools import reduce … … 46 47 import operator 47 48 import re 49 from itertools import chain 48 50 49 51 from genshi.core import Stream, Attrs, Namespace, QName … … 79 81 80 82 83 class 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 236 class 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 node1.__class__ is not node2.__class__: 267 return False 268 if node1.__class__ == 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 xrange(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 443 class 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 81 513 class Path(object): 82 514 """Implements basic XPath support on streams. 83 515 84 Instances of this class represent a "compiled" XPath expression, and provide 85 methods for testing the path against a stream, as well as extracting a 86 substream matching that path. 87 """ 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) 88 522 89 523 def __init__(self, text, filename=None, lineno=-1): … … 97 531 self.source = text 98 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, "This path is not implemented" 99 541 100 542 def __repr__(self): … … 134 576 variables = {} 135 577 stream = iter(stream) 136 def _generate(): 578 def _generate(stream=stream, ns=namespaces, vs=variables): 579 next = stream.next 137 580 test = self.test() 138 581 for event in stream: 139 result = test(event, n amespaces, variables)582 result = test(event, ns, vs) 140 583 if result is True: 141 584 yield event … … 143 586 depth = 1 144 587 while depth > 0: 145 subevent = stream.next()588 subevent = next() 146 589 if subevent[0] is START: 147 590 depth += 1 … … 149 592 depth -= 1 150 593 yield subevent 151 test(subevent, namespaces, variables, 152 updateonly=True) 594 test(subevent, ns, vs, updateonly=True) 153 595 elif result: 154 596 yield result … … 174 616 >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>') 175 617 >>> test = Path('child').test() 618 >>> namespaces, variables = {}, {} 176 619 >>> for event in xml: 177 ... if test(event, {}, {}):620 ... if test(event, namespaces, variables): 178 621 ... print event[0], repr(event[1]) 179 622 START (QName(u'child'), Attrs([(QName(u'id'), u'2')])) … … 186 629 :rtype: ``function`` 187 630 """ 188 paths = [(p, len(p), [0], [], [0] * len(p)) for p in [ 189 (ignore_context and [_DOTSLASHSLASH] or []) + p for p in self.paths 190 ]] 191 192 def _test(event, namespaces, variables, updateonly=False): 193 kind, data, pos = event[:3] 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): 194 636 retval = None 195 for steps, size, cursors, cutoff, counter in paths: 196 # Manage the stack that tells us "where we are" in the stream 197 if kind is END: 198 if cursors: 199 cursors.pop() 200 continue 201 elif kind is START: 202 cursors.append(cursors and cursors[-1] or 0) 203 elif kind is START_NS or kind is END_NS \ 204 or kind is START_CDATA or kind is END_CDATA: 205 continue 206 207 if updateonly or retval or not cursors: 208 continue 209 cursor = cursors[-1] 210 depth = len(cursors) 211 212 if cutoff and depth + int(kind is not START) > cutoff[0]: 213 continue 214 215 ctxtnode = not ignore_context and kind is START \ 216 and depth == 2 217 matched = None 218 while 1: 219 # Fetch the next location step 220 axis, nodetest, predicates = steps[cursor] 221 222 # If this is the start event for the context node, and the 223 # axis of the location step doesn't include the current 224 # element, skip the test 225 if ctxtnode and (axis is CHILD or axis is DESCENDANT): 226 break 227 228 # Is this the last step of the location path? 229 last_step = cursor + 1 == size 230 231 # Perform the actual node test 232 matched = nodetest(kind, data, pos, namespaces, variables) 233 234 # The node test matched 235 if matched: 236 237 # Check all the predicates for this step 238 if predicates: 239 for predicate in predicates: 240 pretval = predicate(kind, data, pos, namespaces, 241 variables) 242 if type(pretval) is float: # FIXME <- need to 243 # check this for 244 # other types that 245 # can be coerced to 246 # float 247 counter[cursor] += 1 248 if counter[cursor] != int(pretval): 249 pretval = False 250 if not pretval: 251 matched = None 252 break 253 254 # Both the node test and the predicates matched 255 if matched: 256 if last_step: 257 if not ctxtnode or kind is not START \ 258 or axis is ATTRIBUTE or axis is SELF: 259 retval = matched 260 elif not ctxtnode or axis is SELF \ 261 or axis is DESCENDANT_OR_SELF: 262 cursor += 1 263 cursors[-1] = cursor 264 cutoff[:] = [] 265 266 if kind is START: 267 if last_step and not (axis is DESCENDANT or 268 axis is DESCENDANT_OR_SELF): 269 cutoff[:] = [depth] 270 271 elif steps[cursor][0] is ATTRIBUTE: 272 # If the axis of the next location step is the 273 # attribute axis, we need to move on to processing 274 # that step without waiting for the next markup 275 # event 276 continue 277 278 # We're done with this step if it's the last step or the 279 # axis isn't "self" 280 if not matched or last_step or not ( 281 axis is SELF or axis is DESCENDANT_OR_SELF): 282 break 283 if ctxtnode and axis is DESCENDANT_OR_SELF: 284 ctxtnode = False 285 286 if (retval or not matched) and kind is START and \ 287 not (axis is DESCENDANT or axis is DESCENDANT_OR_SELF): 288 # If this step is not a closure, it cannot be matched until 289 # the current element is closed... so we need to move the 290 # cursor back to the previous closure and retest that 291 # against the current element 292 backsteps = [(i, k, d, p) for i, (k, d, p) 293 in enumerate(steps[:cursor]) 294 if k is DESCENDANT or k is DESCENDANT_OR_SELF] 295 backsteps.reverse() 296 for cursor, axis, nodetest, predicates in backsteps: 297 if nodetest(kind, data, pos, namespaces, variables): 298 cutoff[:] = [] 299 break 300 cursors[-1] = cursor 301 637 for test in tests: 638 val = test(event, namespaces, variables, updateonly=updateonly) 639 if retval is None: 640 retval = val 302 641 return retval 303 304 return _test 642 return _multi 305 643 306 644 … … 375 713 while True: 376 714 if self.cur_token.startswith('/'): 377 if self.cur_token == '//': 715 if not steps: 716 if self.cur_token == '//': 717 # hack to make //* match every node - also root 718 self.next_token() 719 axis, nodetest, predicates = self._location_step() 720 steps.append((DESCENDANT_OR_SELF, nodetest, 721 predicates)) 722 if self.at_end or not self.cur_token.startswith('/'): 723 break 724 continue 725 else: 726 raise PathSyntaxError('Absolute location paths not ' 727 'supported', self.filename, 728 self.lineno) 729 elif self.cur_token == '//': 378 730 steps.append((DESCENDANT_OR_SELF, NodeTest(), [])) 379 elif not steps:380 raise PathSyntaxError('Absolute location paths not '381 'supported', self.filename,382 self.lineno)383 731 self.next_token() 384 732 … … 387 735 axis = CHILD 388 736 steps.append((axis, nodetest, predicates)) 389 390 737 if self.at_end or not self.cur_token.startswith('/'): 391 738 break … … 716 1063 """ 717 1064 __slots__ = ['expr'] 1065 _return_type = bool 718 1066 def __init__(self, expr): 719 1067 self.expr = expr … … 1173 1521 1174 1522 _DOTSLASHSLASH = (DESCENDANT_OR_SELF, PrincipalTypeTest(None), ()) 1523 _DOTSLASH = (SELF, PrincipalTypeTest(None), ()) -
trunk/genshi/tests/path.py
r754 r1025 16 16 17 17 from genshi.input import XML 18 from genshi.path import Path, PathSyntaxError 19 18 from genshi.path import Path, PathParser, PathSyntaxError, GenericStrategy, \ 19 SingleStepStrategy, SimplePathStrategy 20 21 22 class FakePath(Path): 23 def __init__(self, strategy): 24 self.strategy = strategy 25 def test(self, ignore_context = False): 26 return self.strategy.test(ignore_context) 20 27 21 28 class PathTestCase(unittest.TestCase): 29 30 strategies = [GenericStrategy, SingleStepStrategy, SimplePathStrategy] 31 def _create_path(self, expression, expected): 32 return path 33 34 def _test_strategies(self, stream, path, render, 35 namespaces=None, variables=None): 36 for strategy in self.strategies: 37 if not strategy.supports(path): 38 continue 39 s = strategy(path) 40 rendered = FakePath(s).select(stream,namespaces=namespaces, 41 variables=variables).render() 42 msg = "Bad render using %s strategy"%str(strategy) 43 msg += "\nExpected:\t'%s'"%render 44 msg += "\nRendered:\t'%s'"%rendered 45 self.assertEqual(render, rendered, msg) 46 47 def _test_expression(self, text, expected, stream=None, render="", 48 namespaces=None, variables=None): 49 path = Path(text) 50 if expected is not None: 51 self.assertEqual(expected, repr(path)) 52 53 if stream is None: 54 return 55 56 rendered = path.select(stream, namespaces=namespaces, 57 variables=variables).render() 58 msg = "Bad render using whole path" 59 msg += "\nExpected:\t'%s'"%render 60 msg += "\nRendered:\t'%s'"%rendered 61 self.assertEqual(render, rendered, msg) 62 63 if len(path.paths) == 1: 64 self._test_strategies(stream, path.paths[0], render, 65 namespaces=namespaces, variables=variables) 66 22 67 23 68 def test_error_no_absolute_path(self): … … 31 76 xml = XML('<root><elem/></root>') 32 77 33 path = Path('elem') 34 self.assertEqual('<Path "child::elem">', repr(path)) 35 self.assertEqual('<elem/>', path.select(xml).render()) 36 37 path = Path('child::elem') 38 self.assertEqual('<Path "child::elem">', repr(path)) 39 self.assertEqual('<elem/>', path.select(xml).render()) 40 41 path = Path('//elem') 42 self.assertEqual('<Path "descendant-or-self::node()/child::elem">', 43 repr(path)) 44 self.assertEqual('<elem/>', path.select(xml).render()) 45 46 path = Path('descendant::elem') 47 self.assertEqual('<Path "descendant::elem">', repr(path)) 48 self.assertEqual('<elem/>', path.select(xml).render()) 78 self._test_expression( 'elem', 79 '<Path "child::elem">', 80 xml, 81 '<elem/>') 82 83 self._test_expression( 'elem', 84 '<Path "child::elem">', 85 xml, 86 '<elem/>') 87 88 self._test_expression( 'child::elem', 89 '<Path "child::elem">', 90 xml, 91 '<elem/>') 92 93 self._test_expression( '//elem', 94 '<Path "descendant-or-self::elem">', 95 xml, 96 '<elem/>') 97 98 self._test_expression( 'descendant::elem', 99 '<Path "descendant::elem">', 100 xml, 101 '<elem/>') 49 102 50 103 def test_1step_self(self): 51 104 xml = XML('<root><elem/></root>') 52 105 53 path = Path('.') 54 self.assertEqual('<Path "self::node()">', repr(path)) 55 self.assertEqual('<root><elem/></root>', path.select(xml).render()) 56 57 path = Path('self::node()') 58 self.assertEqual('<Path "self::node()">', repr(path)) 59 self.assertEqual('<root><elem/></root>', path.select(xml).render()) 106 self._test_expression( '.', 107 '<Path "self::node()">', 108 xml, 109 '<root><elem/></root>') 110 111 self._test_expression( 'self::node()', 112 '<Path "self::node()">', 113 xml, 114 '<root><elem/></root>') 60 115 61 116 def test_1step_wildcard(self): 62 117 xml = XML('<root><elem/></root>') 63 118 64 path = Path('*') 65 self.assertEqual('<Path "child::*">', repr(path)) 66 self.assertEqual('<elem/>', path.select(xml).render()) 67 68 path = Path('child::*') 69 self.assertEqual('<Path "child::*">', repr(path)) 70 self.assertEqual('<elem/>', path.select(xml).render()) 71 72 path = Path('child::node()') 73 self.assertEqual('<Path "child::node()">', repr(path)) 74 self.assertEqual('<elem/>', Path('child::node()').select(xml).render()) 75 76 path = Path('//*') 77 self.assertEqual('<Path "descendant-or-self::node()/child::*">', 78 repr(path)) 79 self.assertEqual('<root><elem/></root>', path.select(xml).render()) 119 self._test_expression( '*', 120 '<Path "child::*">', 121 xml, 122 '<elem/>') 123 124 self._test_expression( 'child::*', 125 '<Path "child::*">', 126 xml, 127 '<elem/>') 128 129 self._test_expression( 'child::node()', 130 '<Path "child::node()">', 131 xml, 132 '<elem/>') 133 134 self._test_expression( '//*', 135 '<Path "descendant-or-self::*">', 136 xml, 137 '<root><elem/></root>') 80 138 81 139 def test_1step_attribute(self): 82 path = Path('@foo') 83 self.assertEqual('<Path "attribute::foo">', repr(path)) 84 85 xml = XML('<root/>') 86 self.assertEqual('', path.select(xml).render()) 140 self._test_expression( '@foo', 141 '<Path "attribute::foo">', 142 XML('<root/>'), 143 '') 87 144 88 145 xml = XML('<root foo="bar"/>') 89 self.assertEqual('bar', path.select(xml).render()) 90 91 path = Path('./@foo') 92 self.assertEqual('<Path "self::node()/attribute::foo">', repr(path)) 93 self.assertEqual('bar', path.select(xml).render()) 146 147 self._test_expression( '@foo', 148 '<Path "attribute::foo">', 149 xml, 150 'bar') 151 152 self._test_expression( './@foo', 153 '<Path "self::node()/attribute::foo">', 154 xml, 155 'bar') 94 156 95 157 def test_1step_text(self): 96 158 xml = XML('<root>Hey</root>') 97 159 98 path = Path('text()') 99 self.assertEqual('<Path "child::text()">', repr(path)) 100 self.assertEqual('Hey', path.select(xml).render()) 101 102 path = Path('./text()') 103 self.assertEqual('<Path "self::node()/child::text()">', repr(path)) 104 self.assertEqual('Hey', path.select(xml).render()) 105 106 path = Path('//text()') 107 self.assertEqual('<Path "descendant-or-self::node()/child::text()">', 108 repr(path)) 109 self.assertEqual('Hey', path.select(xml).render()) 110 111 path = Path('.//text()') 112 self.assertEqual('<Path "self::node()/descendant-or-self::node()/child::text()">', 113 repr(path)) 114 self.assertEqual('Hey', path.select(xml).render()) 160 self._test_expression( 'text()', 161 '<Path "child::text()">', 162 xml, 163 'Hey') 164 165 self._test_expression( './text()', 166 '<Path "self::node()/child::text()">', 167 xml, 168 'Hey') 169 170 self._test_expression( '//text()', 171 '<Path "descendant-or-self::text()">', 172 xml, 173 'Hey') 174 175 self._test_expression( './/text()', 176 '<Path "self::node()/descendant-or-self::node()/child::text()">', 177 xml, 178 'Hey') 115 179 116 180 def test_2step(self): 117 181 xml = XML('<root><foo/><bar/></root>') 118 self. assertEqual('<foo/><bar/>', Path('*').select(xml).render())119 self. assertEqual('<bar/>', Path('bar').select(xml).render())120 self. assertEqual('', Path('baz').select(xml).render())182 self._test_expression('*', None, xml, '<foo/><bar/>') 183 self._test_expression('bar', None, xml, '<bar/>') 184 self._test_expression('baz', None, xml, '') 121 185 122 186 def test_2step_attribute(self): 123 187 xml = XML('<elem class="x"><span id="joe">Hey Joe</span></elem>') 124 self. assertEqual('x', Path('@*').select(xml).render())125 self. assertEqual('x', Path('./@*').select(xml).render())126 self. assertEqual('xjoe', Path('.//@*').select(xml).render())127 self. assertEqual('joe', Path('*/@*').select(xml).render())188 self._test_expression('@*', None, xml, 'x') 189 self._test_expression('./@*', None, xml, 'x') 190 self._test_expression('.//@*', None, xml, 'xjoe') 191 self._test_expression('*/@*', None, xml, 'joe') 128 192 129 193 xml = XML('<elem><foo id="1"/><foo id="2"/></elem>') 130 self. assertEqual('', Path('@*').select(xml).render())131 self. assertEqual('12', Path('foo/@*').select(xml).render())194 self._test_expression('@*', None, xml, '') 195 self._test_expression('foo/@*', None, xml, '12') 132 196 133 197 def test_2step_complex(self): 134 198 xml = XML('<root><foo><bar/></foo></root>') 135 199 136 path = Path('foo/bar') 137 self.assertEqual('<Path "child::foo/child::bar">', repr(path)) 138 self.assertEqual('<bar/>', path.select(xml).render()) 139 140 path = Path('./bar') 141 self.assertEqual('<Path "self::node()/child::bar">', repr(path)) 142 self.assertEqual('', path.select(xml).render()) 143 144 path = Path('foo/*') 145 self.assertEqual('<Path "child::foo/child::*">', repr(path)) 146 self.assertEqual('<bar/>', path.select(xml).render()) 200 self._test_expression( 'foo/bar', 201 '<Path "child::foo/child::bar">', 202 xml, 203 '<bar/>') 204 205 self._test_expression( './bar', 206 '<Path "self::node()/child::bar">', 207 xml, 208 '') 209 210 self._test_expression( 'foo/*', 211 '<Path "child::foo/child::*">', 212 xml, 213 '<bar/>') 147 214 148 215 xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>') 149 path = Path('./bar') 150 self.assertEqual('<Path "self::node()/child::bar">', repr(path)) 151 self.assertEqual('<bar id="2"/>', path.select(xml).render()) 216 self._test_expression( './bar', 217 '<Path "self::node()/child::bar">', 218 xml, 219 '<bar id="2"/>') 152 220 153 221 def test_2step_text(self): 154 222 xml = XML('<root><item>Foo</item></root>') 155 223 156 path = Path('item/text()') 157 self.assertEqual('<Path "child::item/child::text()">', repr(path)) 158 self.assertEqual('Foo', path.select(xml).render()) 159 160 path = Path('*/text()') 161 self.assertEqual('<Path "child::*/child::text()">', repr(path)) 162 self.assertEqual('Foo', path.select(xml).render()) 163 164 path = Path('//text()') 165 self.assertEqual('<Path "descendant-or-self::node()/child::text()">', 166 repr(path)) 167 self.assertEqual('Foo', path.select(xml).render()) 168 169 path = Path('./text()') 170 self.assertEqual('<Path "self::node()/child::text()">', repr(path)) 171 self.assertEqual('', path.select(xml).render()) 224 self._test_expression( 'item/text()', 225 '<Path "child::item/child::text()">', 226 xml, 227 'Foo') 228 229 self._test_expression( '*/text()', 230 '<Path "child::*/child::text()">', 231 xml, 232 'Foo') 233 234 self._test_expression( '//text()', 235 '<Path "descendant-or-self::text()">', 236 xml, 237 'Foo') 238 239 self._test_expression( './text()', 240 '<Path "self::node()/child::text()">', 241 xml, 242 '') 172 243 173 244 xml = XML('<root><item>Foo</item><item>Bar</item></root>') 174 path = Path('item/text()') 175 self.assertEqual('<Path "child::item/child::text()">', repr(path)) 176 self.assertEqual('FooBar', path.select(xml).render()) 177 178 xml = XML('<root><item>Foo</item><item>Bar</item></root>') 179 self.assertEqual('FooBar', path.select(xml).render()) 245 self._test_expression( 'item/text()', 246 '<Path "child::item/child::text()">', 247 xml, 248 'FooBar') 180 249 181 250 def test_3step(self): 182 251 xml = XML('<root><foo><bar/></foo></root>') 183 path = Path('foo/*') 184 self.assertEqual('<Path "child::foo/child::*">', repr(path)) 185 self.assertEqual('<bar/>', path.select(xml).render()) 252 self._test_expression( 'foo/*', 253 '<Path "child::foo/child::*">', 254 xml, 255 '<bar/>') 186 256 187 257 def test_3step_complex(self): 188 258 xml = XML('<root><foo><bar/></foo></root>') 189 path = Path('*/bar') 190 self.assertEqual('<Path "child::*/child::bar">', repr(path)) 191 self.assertEqual('<bar/>', path.select(xml).render()) 259 self._test_expression( '*/bar', 260 '<Path "child::*/child::bar">', 261 xml, 262 '<bar/>') 192 263 193 264 xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>') 194 path = Path('//bar') 195 self.assertEqual('<Path "descendant-or-self::node()/child::bar">', 196 repr(path)) 197 self.assertEqual('<bar id="1"/><bar id="2"/>', 198 path.select(xml).render()) 265 self._test_expression( '//bar', 266 '<Path "descendant-or-self::bar">', 267 xml, 268 '<bar id="1"/><bar id="2"/>') 199 269 200 270 def test_node_type_comment(self): 201 271 xml = XML('<root><!-- commented --></root>') 202 path = Path('comment()') 203 self.assertEqual('<Path "child::comment()">', repr(path)) 204 self.assertEqual('<!-- commented -->', path.select(xml).render()) 272 self._test_expression( 'comment()', 273 '<Path "child::comment()">', 274 xml, 275 '<!-- commented -->') 205 276 206 277 def test_node_type_text(self): 207 278 xml = XML('<root>Some text <br/>in here.</root>') 208 path = Path('text()') 209 self.assertEqual('<Path "child::text()">', repr(path)) 210 self.assertEqual('Some text in here.', path.select(xml).render()) 279 self._test_expression( 'text()', 280 '<Path "child::text()">', 281 xml, 282 'Some text in here.') 211 283 212 284 def test_node_type_node(self): 213 285 xml = XML('<root>Some text <br/>in here.</root>') 214 path = Path('node()') 215 self.assertEqual('<Path "child::node()">', repr(path)) 216 self.assertEqual('Some text <br/>in here.', path.select(xml).render()) 286 self._test_expression( 'node()', 287 '<Path "child::node()">', 288 xml, 289 'Some text <br/>in here.',) 217 290 218 291 def test_node_type_processing_instruction(self): 219 292 xml = XML('<?python x = 2 * 3 ?><root><?php echo("x") ?></root>') 220 293 221 path = Path('processing-instruction()') 222 self.assertEqual('<Path "child::processing-instruction()">', 223 repr(path)) 224 self.assertEqual('<?python x = 2 * 3 ?><?php echo("x") ?>', 225 path.select(xml).render()) 226 227 path = Path('processing-instruction("php")') 228 self.assertEqual('<Path "child::processing-instruction(\"php\")">', 229 repr(path)) 230 self.assertEqual('<?php echo("x") ?>', path.select(xml).render()) 294 self._test_expression( '//processing-instruction()', 295 '<Path "descendant-or-self::processing-instruction()">', 296 xml, 297 '<?python x = 2 * 3 ?><?php echo("x") ?>') 298 299 self._test_expression( 'processing-instruction()', 300 '<Path "child::processing-instruction()">', 301 xml, 302 '<?php echo("x") ?>') 303 304 self._test_expression( 'processing-instruction("php")', 305 '<Path "child::processing-instruction(\"php\")">', 306 xml, 307 '<?php echo("x") ?>') 231 308 232 309 def test_simple_union(self): 233 310 xml = XML("""<body>1<br />2<br />3<br /></body>""") 234 path = Path('*|text()') 235 self.assertEqual('<Path "child::*|child::text()">', repr(path)) 236 self.assertEqual('1<br/>2<br/>3<br/>', path.select(xml).render()) 311 self._test_expression( '*|text()', 312 '<Path "child::*|child::text()">', 313 xml, 314 '1<br/>2<br/>3<br/>') 237 315 238 316 def test_predicate_name(self): 239 317 xml = XML('<root><foo/><bar/></root>') 240 path = Path('*[name()="foo"]') 241 self.assertEqual('<foo/>', path.select(xml).render()) 318 self._test_expression('*[name()="foo"]', None, xml, '<foo/>') 242 319 243 320 def test_predicate_localname(self): 244 321 xml = XML('<root><foo xmlns="NS"/><bar/></root>') 245 path = Path('*[local-name()="foo"]')246 self.assertEqual('<foo xmlns="NS"/>', path.select(xml).render())322 self._test_expression('*[local-name()="foo"]', None, xml, 323 '<foo xmlns="NS"/>') 247 324 248 325 def test_predicate_namespace(self): 249 326 xml = XML('<root><foo xmlns="NS"/><bar/></root>') 250 path = Path('*[namespace-uri()="NS"]')251 self.assertEqual('<foo xmlns="NS"/>', path.select(xml).render())327 self._test_expression('*[namespace-uri()="NS"]', None, xml, 328 '<foo xmlns="NS"/>') 252 329 253 330 def test_predicate_not_name(self): 254 331 xml = XML('<root><foo/><bar/></root>') 255 path = Path('*[not(name()="foo")]') 256 self.assertEqual('<bar/>', path.select(xml).render()) 332 self._test_expression('*[not(name()="foo")]', None, xml, '<bar/>') 257 333 258 334 def test_predicate_attr(self): 259 335 xml = XML('<root><item/><item important="very"/></root>') 260 path = Path('item[@important]')261 self.assertEqual('<item important="very"/>', path.select(xml).render())262 path = Path('item[@important="very"]')263 self.assertEqual('<item important="very"/>', path.select(xml).render())336 self._test_expression('item[@important]', None, xml, 337 '<item important="very"/>') 338 self._test_expression('item[@important="very"]', None, xml, 339 '<item important="very"/>') 264 340 265 341 def test_predicate_attr_equality(self): 266 342 xml = XML('<root><item/><item important="notso"/></root>') 267 path = Path('item[@important="very"]') 268 self.assertEqual('', path.select(xml).render()) 269 path = Path('item[@important!="very"]') 270 self.assertEqual('<item/><item important="notso"/>', 271 path.select(xml).render()) 343 self._test_expression('item[@important="very"]', None, xml, '') 344 self._test_expression('item[@important!="very"]', None, xml, 345 '<item/><item important="notso"/>') 272 346 273 347 def test_predicate_attr_greater_than(self): 274 348 xml = XML('<root><item priority="3"/></root>') 275 path = Path('item[@priority>3]') 276 self.assertEqual('', path.select(xml).render()) 277 path = Path('item[@priority>2]') 278 self.assertEqual('<item priority="3"/>', path.select(xml).render()) 349 self._test_expression('item[@priority>3]', None, xml, '') 350 self._test_expression('item[@priority>2]', None, xml, 351 '<item priority="3"/>') 279 352 280 353 def test_predicate_attr_less_than(self): 281 354 xml = XML('<root><item priority="3"/></root>') 282 path = Path('item[@priority<3]') 283 self.assertEqual('', path.select(xml).render()) 284 path = Path('item[@priority<4]') 285 self.assertEqual('<item priority="3"/>', path.select(xml).render()) 355 self._test_expression('item[@priority<3]', None, xml, '') 356 self._test_expression('item[@priority<4]', None, xml, 357 '<item priority="3"/>') 286 358 287 359 def test_predicate_attr_and(self): 288 360 xml = XML('<root><item/><item important="very"/></root>') 289 path = Path('item[@important and @important="very"]')290 self.assertEqual('<item important="very"/>', path.select(xml).render())291 path = Path('item[@important and @important="notso"]')292 self.assertEqual('', path.select(xml).render())361 self._test_expression('item[@important and @important="very"]', 362 None, xml, '<item important="very"/>') 363 self._test_expression('item[@important and @important="notso"]', 364 None, xml, '') 293 365 294 366 def test_predicate_attr_or(self): 295 367 xml = XML('<root><item/><item important="very"/></root>') 296 path = Path('item[@urgent or @important]') 297 self.assertEqual('<item important="very"/>', path.select(xml).render()) 298 path = Path('item[@urgent or @notso]') 299 self.assertEqual('', path.select(xml).render()) 368 self._test_expression('item[@urgent or @important]', None, xml, 369 '<item important="very"/>') 370 self._test_expression('item[@urgent or @notso]', None, xml, '') 300 371 301 372 def test_predicate_boolean_function(self): 302 373 xml = XML('<root><foo>bar</foo></root>') 303 path = Path('*[boolean("")]') 304 self.assertEqual('', path.select(xml).render()) 305 path = Path('*[boolean("yo")]') 306 self.assertEqual('<foo>bar</foo>', path.select(xml).render()) 307 path = Path('*[boolean(0)]') 308 self.assertEqual('', path.select(xml).render()) 309 path = Path('*[boolean(42)]') 310 self.assertEqual('<foo>bar</foo>', path.select(xml).render()) 311 path = Path('*[boolean(false())]') 312 self.assertEqual('', path.select(xml).render()) 313 path = Path('*[boolean(true())]') 314 self.assertEqual('<foo>bar</foo>', path.select(xml).render()) 374 self._test_expression('*[boolean("")]', None, xml, '') 375 self._test_expression('*[boolean("yo")]', None, xml, '<foo>bar</foo>') 376 self._test_expression('*[boolean(0)]', None, xml, '') 377 self._test_expression('*[boolean(42)]', None, xml, '<foo>bar</foo>') 378 self._test_expression('*[boolean(false())]', None, xml, '') 379 self._test_expression('*[boolean(true())]', None, xml, 380 '<foo>bar</foo>') 315 381 316 382 def test_predicate_ceil_function(self): 317 383 xml = XML('<root><foo>bar</foo></root>') 318 path = Path('*[ceiling("4.5")=5]')319 self.assertEqual('<foo>bar</foo>', path.select(xml).render())384 self._test_expression('*[ceiling("4.5")=5]', None, xml, 385 '<foo>bar</foo>') 320 386 321 387 def test_predicate_concat_function(self): 322 388 xml = XML('<root><foo>bar</foo></root>') 323 path = Path('*[name()=concat("f", "oo")]')324 self.assertEqual('<foo>bar</foo>', path.select(xml).render())389 self._test_expression('*[name()=concat("f", "oo")]', None, xml, 390 '<foo>bar</foo>') 325 391 326 392 def test_predicate_contains_function(self): 327 393 xml = XML('<root><foo>bar</foo></root>') 328 path = Path('*[contains(name(), "oo")]')329 self.assertEqual('<foo>bar</foo>', path.select(xml).render())394 self._test_expression('*[contains(name(), "oo")]', None, xml, 395 '<foo>bar</foo>') 330 396 331 397 def test_predicate_matches_function(self): 332 398 xml = XML('<root><foo>bar</foo><bar>foo</bar></root>') 333 path = Path('*[matches(name(), "foo|bar")]')334 self.assertEqual('<foo>bar</foo><bar>foo</bar>', path.select(xml).render())399 self._test_expression('*[matches(name(), "foo|bar")]', None, xml, 400 '<foo>bar</foo><bar>foo</bar>') 335 401 336 402 def test_predicate_false_function(self): 337 403 xml = XML('<root><foo>bar</foo></root>') 338 path = Path('*[false()]') 339 self.assertEqual('', path.select(xml).render()) 404 self._test_expression('*[false()]', None, xml, '') 340 405 341 406 def test_predicate_floor_function(self): 342 407 xml = XML('<root><foo>bar</foo></root>') 343 path = Path('*[floor("4.5")=4]')344 self.assertEqual('<foo>bar</foo>', path.select(xml).render())408 self._test_expression('*[floor("4.5")=4]', None, xml, 409 '<foo>bar</foo>') 345 410 346 411 def test_predicate_normalize_space_function(self): 347 412 xml = XML('<root><foo>bar</foo></root>') 348 path = Path('*[normalize-space(" foo bar ")="foo bar"]')349 self.assertEqual('<foo>bar</foo>', path.select(xml).render())413 self._test_expression('*[normalize-space(" foo bar ")="foo bar"]', 414 None, xml, '<foo>bar</foo>') 350 415 351 416 def test_predicate_number_function(self): 352 417 xml = XML('<root><foo>bar</foo></root>') 353 path = Path('*[number("3.0")=3]')354 self.assertEqual('<foo>bar</foo>', path.select(xml).render())355 path = Path('*[number("3.0")=3.0]')356 self.assertEqual('<foo>bar</foo>', path.select(xml).render())357 path = Path('*[number("0.1")=.1]')358 self.assertEqual('<foo>bar</foo>', path.select(xml).render())418 self._test_expression('*[number("3.0")=3]', None, xml, 419 '<foo>bar</foo>') 420 self._test_expression('*[number("3.0")=3.0]', None, xml, 421 '<foo>bar</foo>') 422 self._test_expression('*[number("0.1")=.1]', None, xml, 423 '<foo>bar</foo>') 359 424 360 425 def test_predicate_round_function(self): 361 426 xml = XML('<root><foo>bar</foo></root>') 362 path = Path('*[round("4.4")=4]')363 self.assertEqual('<foo>bar</foo>', path.select(xml).render())364 path = Path('*[round("4.6")=5]')365 self.assertEqual('<foo>bar</foo>', path.select(xml).render())427 self._test_expression('*[round("4.4")=4]', None, xml, 428 '<foo>bar</foo>') 429 self._test_expression('*[round("4.6")=5]', None, xml, 430 '<foo>bar</foo>') 366 431 367 432 def test_predicate_starts_with_function(self): 368 433 xml = XML('<root><foo>bar</foo></root>') 369 path = Path('*[starts-with(name(), "f")]') 370 self.assertEqual('<foo>bar</foo>', path.select(xml).render()) 371 path = Path('*[starts-with(name(), "b")]') 372 self.assertEqual('', path.select(xml).render()) 434 self._test_expression('*[starts-with(name(), "f")]', None, xml, 435 '<foo>bar</foo>') 436 self._test_expression('*[starts-with(name(), "b")]', None, xml, '') 373 437 374 438 def test_predicate_string_length_function(self): 375 439 xml = XML('<root><foo>bar</foo></root>') 376 path = Path('*[string-length(name())=3]')377 self.assertEqual('<foo>bar</foo>', path.select(xml).render())440 self._test_expression('*[string-length(name())=3]', None, xml, 441 '<foo>bar</foo>') 378 442 379 443 def test_predicate_substring_function(self): 380 444 xml = XML('<root><foo>bar</foo></root>') 381 path = Path('*[substring(name(), 1)="oo"]')382 self.assertEqual('<foo>bar</foo>', path.select(xml).render())383 path = Path('*[substring(name(), 1, 1)="o"]')384 self.assertEqual('<foo>bar</foo>', path.select(xml).render())445 self._test_expression('*[substring(name(), 1)="oo"]', None, xml, 446 '<foo>bar</foo>') 447 self._test_expression('*[substring(name(), 1, 1)="o"]', None, xml, 448 '<foo>bar</foo>') 385 449 386 450 def test_predicate_substring_after_function(self): 387 451 xml = XML('<root><foo>bar</foo></root>') 388 path = Path('*[substring-after(name(), "f")="oo"]')389 self.assertEqual('<foo>bar</foo>', path.select(xml).render())452 self._test_expression('*[substring-after(name(), "f")="oo"]', None, xml, 453 '<foo>bar</foo>') 390 454 391 455 def test_predicate_substring_before_function(self): 392 456 xml = XML('<root><foo>bar</foo></root>') 393 path = Path('*[substring-before(name(), "oo")="f"]')394 self.assertEqual('<foo>bar</foo>', path.select(xml).render())457 self._test_expression('*[substring-before(name(), "oo")="f"]', 458 None, xml, '<foo>bar</foo>') 395 459 396 460 def test_predicate_translate_function(self): 397 461 xml = XML('<root><foo>bar</foo></root>') 398 path = Path('*[translate(name(), "fo", "ba")="baa"]')399 self.assertEqual('<foo>bar</foo>', path.select(xml).render())462 self._test_expression('*[translate(name(), "fo", "ba")="baa"]', 463 None, xml, '<foo>bar</foo>') 400 464 401 465 def test_predicate_true_function(self): 402 466 xml = XML('<root><foo>bar</foo></root>') 403 path = Path('*[true()]') 404 self.assertEqual('<foo>bar</foo>', path.select(xml).render()) 467 self._test_expression('*[true()]', None, xml, '<foo>bar</foo>') 405 468 406 469 def test_predicate_variable(self): 407 470 xml = XML('<root><foo>bar</foo></root>') 408 path = Path('*[name()=$bar]')409 471 variables = {'bar': 'foo'} 410 self. assertEqual('<foo>bar</foo>',411 path.select(xml, variables=variables).render())472 self._test_expression('*[name()=$bar]', None, xml, '<foo>bar</foo>', 473 variables = variables) 412 474 413 475 def test_predicate_position(self): 414 476 xml = XML('<root><foo id="a1"/><foo id="a2"/><foo id="a3"/></root>') 415 path = Path('*[2]') 416 self.assertEqual('<foo id="a2"/>', path.select(xml).render()) 477 self._test_expression('*[2]', None, xml, '<foo id="a2"/>') 417 478 418 479 def test_predicate_attr_and_position(self): 419 480 xml = XML('<root><foo/><foo id="a1"/><foo id="a2"/></root>') 420 path = Path('*[@id][2]') 421 self.assertEqual('<foo id="a2"/>', path.select(xml).render()) 481 self._test_expression('*[@id][2]', None, xml, '<foo id="a2"/>') 422 482 423 483 def test_predicate_position_and_attr(self): 424 484 xml = XML('<root><foo/><foo id="a1"/><foo id="a2"/></root>') 425 path = Path('*[1][@id]') 426 self.assertEqual('', path.select(xml).render()) 427 path = Path('*[2][@id]') 428 self.assertEqual('<foo id="a1"/>', path.select(xml).render()) 485 self._test_expression('*[1][@id]', None, xml, '') 486 self._test_expression('*[2][@id]', None, xml, '<foo id="a1"/>') 487 488 def test_predicate_advanced_position(self): 489 xml = XML('<root><a><b><c><d><e/></d></c></b></a></root>') 490 self._test_expression( 'descendant-or-self::*/' 491 'descendant-or-self::*/' 492 'descendant-or-self::*[2]/' 493 'self::*/descendant::*[3]', None, xml, 494 '<d><e/></d>') 495 496 def test_predicate_child_position(self): 497 xml = XML('\ 498 <root><a><b>1</b><b>2</b><b>3</b></a><a><b>4</b><b>5</b></a></root>') 499 self._test_expression('//a/b[2]', None, xml, '<b>2</b><b>5</b>') 500 self._test_expression('//a/b[3]', None, xml, '<b>3</b>') 429 501 430 502 def test_name_with_namespace(self): 431 503 xml = XML('<root xmlns:f="FOO"><f:foo>bar</f:foo></root>') 432 path = Path('f:foo') 433 self.assertEqual('<Path "child::f:foo">', repr(path)) 434 namespaces = {'f': 'FOO'} 435 self.assertEqual('<foo xmlns="FOO">bar</foo>', 436 path.select(xml, namespaces=namespaces).render()) 504 self._test_expression('f:foo', '<Path "child::f:foo">', xml, 505 '<foo xmlns="FOO">bar</foo>', 506 namespaces = {'f': 'FOO'}) 437 507 438 508 def test_wildcard_with_namespace(self): 439 509 xml = XML('<root xmlns:f="FOO"><f:foo>bar</f:foo></root>') 440 path = Path('f:*') 441 self.assertEqual('<Path "child::f:*">', repr(path)) 442 namespaces = {'f': 'FOO'} 443 self.assertEqual('<foo xmlns="FOO">bar</foo>', 444 path.select(xml, namespaces=namespaces).render()) 510 self._test_expression('f:*', '<Path "child::f:*">', xml, 511 '<foo xmlns="FOO">bar</foo>', 512 namespaces = {'f': 'FOO'}) 445 513 446 514 def test_predicate_termination(self): … … 450 518 """ 451 519 xml = XML('<ul flag="1"><li>a</li><li>b</li></ul>') 452 path = Path('.[@flag="1"]/*')453 self.assertEqual('<li>a</li><li>b</li>', path.select(xml).render())520 self._test_expression('.[@flag="1"]/*', None, xml, 521 '<li>a</li><li>b</li>') 454 522 455 523 xml = XML('<ul flag="1"><li>a</li><li>b</li></ul>') 456 path = Path('.[@flag="0"]/*') 457 self.assertEqual('', path.select(xml).render()) 524 self._test_expression('.[@flag="0"]/*', None, xml, '') 458 525 459 526 def test_attrname_with_namespace(self): 460 527 xml = XML('<root xmlns:f="FOO"><foo f:bar="baz"/></root>') 461 path = Path('foo[@f:bar]')462 self.assertEqual('<foo xmlns:ns1="FOO" ns1:bar="baz"/>',463 path.select(xml, namespaces={'f': 'FOO'}).render())528 self._test_expression('foo[@f:bar]', None, xml, 529 '<foo xmlns:ns1="FOO" ns1:bar="baz"/>', 530 namespaces={'f': 'FOO'}) 464 531 465 532 def test_attrwildcard_with_namespace(self): 466 533 xml = XML('<root xmlns:f="FOO"><foo f:bar="baz"/></root>') 467 path = Path('foo[@f:*]') 468 self.assertEqual('<foo xmlns:ns1="FOO" ns1:bar="baz"/>', 469 path.select(xml, namespaces={'f': 'FOO'}).render()) 470 534 self._test_expression('foo[@f:*]', None, xml, 535 '<foo xmlns:ns1="FOO" ns1:bar="baz"/>', 536 namespaces={'f': 'FOO'}) 537 def test_self_and_descendant(self): 538 xml = XML('<root><foo/></root>') 539 self._test_expression('self::root', None, xml, '<root><foo/></root>') 540 self._test_expression('self::foo', None, xml, '') 541 self._test_expression('descendant::root', None, xml, '') 542 self._test_expression('descendant::foo', None, xml, '<foo/>') 543 self._test_expression('descendant-or-self::root', None, xml, 544 '<root><foo/></root>') 545 self._test_expression('descendant-or-self::foo', None, xml, '<foo/>') 546 547 def test_long_simple_paths(self): 548 xml = XML('<root><a><b><a><d><a><b><a><b><a><b><a><c>!' 549 '</c></a></b></a></b></a></b></a></d></a></b></a></root>') 550 self._test_expression('//a/b/a/b/a/c', None, xml, '<c>!</c>') 551 self._test_expression('//a/b/a/c', None, xml, '<c>!</c>') 552 self._test_expression('//a/c', None, xml, '<c>!</c>') 553 self._test_expression('//c', None, xml, '<c>!</c>') 554 # Please note that a//b is NOT the same as a/descendant::b 555 # it is a/descendant-or-self::node()/b, which SimplePathStrategy 556 # does NOT support 557 self._test_expression('a/b/descendant::a/c', None, xml, '<c>!</c>') 558 self._test_expression('a/b/descendant::a/d/descendant::a/c', 559 None, xml, '<c>!</c>') 560 self._test_expression('a/b/descendant::a/d/a/c', None, xml, '') 561 self._test_expression('//d/descendant::b/descendant::b/descendant::b' 562 '/descendant::c', None, xml, '<c>!</c>') 563 self._test_expression('//d/descendant::b/descendant::b/descendant::b' 564 '/descendant::b/descendant::c', None, xml, '') 565 def _test_support(self, strategy_class, text): 566 path = PathParser(text, None, -1).parse()[0] 567 return strategy_class.supports(path) 568 def test_simple_strategy_support(self): 569 self.assert_(self._test_support(SimplePathStrategy, 'a/b')) 570 self.assert_(self._test_support(SimplePathStrategy, 'self::a/b')) 571 self.assert_(self._test_support(SimplePathStrategy, 'descendant::a/b')) 572 self.assert_(self._test_support(SimplePathStrategy, 573 'descendant-or-self::a/b')) 574 self.assert_(self._test_support(SimplePathStrategy, '//a/b')) 575 self.assert_(self._test_support(SimplePathStrategy, 'a/@b')) 576 self.assert_(self._test_support(SimplePathStrategy, 'a/text()')) 577 578 # a//b is a/descendant-or-self::node()/b 579 self.assert_(not self._test_support(SimplePathStrategy, 'a//b')) 580 self.assert_(not self._test_support(SimplePathStrategy, 'node()/@a')) 581 self.assert_(not self._test_support(SimplePathStrategy, '@a')) 582 self.assert_(not self._test_support(SimplePathStrategy, 'foo:bar')) 583 self.assert_(not self._test_support(SimplePathStrategy, 'a/@foo:bar')) 471 584 472 585 def suite():
Note: See TracChangeset
for help on using the changeset viewer.
