1
2
3 """
4 Test cases related to XPath evaluation and the XPath class
5 """
6
7 import unittest, sys, os.path
8
9 this_dir = os.path.dirname(__file__)
10 if this_dir not in sys.path:
11 sys.path.insert(0, this_dir)
12
13 from common_imports import etree, HelperTestCase, _bytes, BytesIO
14 from common_imports import doctest, make_doctest
15
17 """XPath tests etree"""
18
23
25 tree = self.parse('<a>1</a>')
26 self.assertEquals(1.,
27 tree.xpath('number(/a)'))
28 tree = self.parse('<a>A</a>')
29 actual = str(tree.xpath('number(/a)'))
30 expected = ['nan', '1.#qnan', 'nanq']
31 if not actual.lower() in expected:
32 self.fail('Expected a NAN value, got %s' % actual)
33
35 tree = self.parse('<a>Foo</a>')
36 self.assertEquals('Foo',
37 tree.xpath('string(/a/text())'))
38
40 tree = self.parse('<a><b/></a>')
41 self.assertEquals([],
42 tree.xpath('/'))
43
45 tree = self.parse('<a xmlns="test" xmlns:p="myURI"/>')
46 self.assert_((None, "test") in tree.xpath('namespace::*'))
47 self.assert_(('p', 'myURI') in tree.xpath('namespace::*'))
48
50 tree = self.parse('<a/>')
51 self.assertEquals([('xml', 'http://www.w3.org/XML/1998/namespace')],
52 tree.xpath('namespace::*'))
53
59
61 tree = self.parse('<a><b/></a>')
62 self.assertEquals([],
63 tree.xpath('/a/c'))
64
65 self.assertEquals([],
66 tree.xpath('/a/c/text()'))
67
69 tree = self.parse('<a><b>Foo</b><b>Bar</b></a>')
70 root = tree.getroot()
71 self.assertEquals(['Foo', 'Bar'],
72 tree.xpath('/a/b/text()'))
73
75 tree = self.parse('<a><b>FooBar</b><b>BarFoo</b></a>')
76 root = tree.getroot()
77 self.assertEquals(['FooBar', 'BarFoo'],
78 tree.xpath('/a/b/text()'))
79 self.assertEquals([root[0], root[1]],
80 [r.getparent() for r in tree.xpath('/a/b/text()')])
81
83 tree = self.parse('<a><b>FooBar</b><b>BarFoo</b></a>')
84 root = tree.getroot()
85 self.assertEquals(['FooBar', 'BarFoo'],
86 tree.xpath('/a/b/text()', smart_strings=True))
87 self.assertEquals([root[0], root[1]],
88 [r.getparent() for r in
89 tree.xpath('/a/b/text()', smart_strings=True)])
90 self.assertEquals([None, None],
91 [r.attrname for r in
92 tree.xpath('/a/b/text()', smart_strings=True)])
93
94 self.assertEquals(['FooBar', 'BarFoo'],
95 tree.xpath('/a/b/text()', smart_strings=False))
96 self.assertEquals([False, False],
97 [hasattr(r, 'getparent') for r in
98 tree.xpath('/a/b/text()', smart_strings=False)])
99 self.assertEquals([None, None],
100 [r.attrname for r in
101 tree.xpath('/a/b/text()', smart_strings=True)])
102
104 xml = _bytes('<a><b>FooBar\\u0680\\u3120</b><b>BarFoo\\u0680\\u3120</b></a>').decode("unicode_escape")
105 tree = self.parse(xml.encode('utf-8'))
106 root = tree.getroot()
107 self.assertEquals([_bytes('FooBar\\u0680\\u3120').decode("unicode_escape"),
108 _bytes('BarFoo\\u0680\\u3120').decode("unicode_escape")],
109 tree.xpath('/a/b/text()'))
110 self.assertEquals([root[0], root[1]],
111 [r.getparent() for r in tree.xpath('/a/b/text()')])
112
114 tree = self.parse('<a b="B" c="C"/>')
115 self.assertEquals(['B'],
116 tree.xpath('/a/@b'))
117
119 tree = self.parse('<a b="BaSdFgHjKl" c="CqWeRtZuI"/>')
120 results = tree.xpath('/a/@c')
121 self.assertEquals(1, len(results))
122 self.assertEquals('CqWeRtZuI', results[0])
123 self.assertEquals(tree.getroot().tag, results[0].getparent().tag)
124
126 tree = self.parse('<a b="BaSdFgHjKl" c="CqWeRtZuI"/>')
127
128 results = tree.xpath('/a/@c', smart_strings=True)
129 self.assertEquals(1, len(results))
130 self.assertEquals('CqWeRtZuI', results[0])
131 self.assertEquals('c', results[0].attrname)
132 self.assertEquals(tree.getroot().tag, results[0].getparent().tag)
133
134 results = tree.xpath('/a/@c', smart_strings=False)
135 self.assertEquals(1, len(results))
136 self.assertEquals('CqWeRtZuI', results[0])
137 self.assertEquals(False, hasattr(results[0], 'getparent'))
138 self.assertEquals(False, hasattr(results[0], 'attrname'))
139
141 xml_data = '''
142 <table>
143 <item xml:id="k1"><value>v1</value></item>
144 <item xml:id="k2"><value>v2</value></item>
145 </table>
146 '''
147
148 def lookup(dummy, id):
149 return etree.XML(xml_data).xpath('id(%r)' % id)
150 functions = {(None, 'lookup') : lookup}
151
152 root = etree.XML('<dummy/>')
153 values = root.xpath("lookup('k1')/value/text()",
154 extensions=functions)
155 self.assertEquals(['v1'], values)
156 self.assertEquals('value', values[0].getparent().tag)
157
162
168
170 tree = self.parse('<a><c><b>Foo</b><b>Bar</b></c><c><b>Hey</b></c></a>')
171 root = tree.getroot()
172 c = root[0]
173 self.assertEquals([c[0], c[1]],
174 c.xpath('b'))
175 self.assertEquals([c[0], c[1], root[1][0]],
176 c.xpath('//b'))
177
179 tree = self.parse('<a xmlns="uri:a"><b></b></a>')
180 root = tree.getroot()
181 self.assertEquals(
182 [root[0]],
183 tree.xpath('//foo:b', namespaces={'foo': 'uri:a'}))
184 self.assertEquals(
185 [],
186 tree.xpath('//foo:b', namespaces={'foo': 'uri:c'}))
187 self.assertEquals(
188 [root[0]],
189 root.xpath('//baz:b', namespaces={'baz': 'uri:a'}))
190
192 tree = self.parse('<a xmlns="uri:a"><b></b></a>')
193 root = tree.getroot()
194 self.assertRaises(
195 TypeError,
196 root.xpath, '//b', namespaces={None: 'uri:a'})
197
199 tree = self.parse('<a xmlns="uri:a"><b></b></a>')
200 root = tree.getroot()
201 self.assertRaises(
202 TypeError,
203 root.xpath, '//b', namespaces={'': 'uri:a'})
204
208
212
216
221
234
247
255
267
282
290
292 def foo(evaluator, a):
293 return 'hello %s' % a
294 extension = {(None, 'foo'): foo}
295 tree = self.parse('<a><b></b></a>')
296 e = etree.XPathEvaluator(tree, extensions=[extension])
297 self.assertEquals(
298 "hello you", e("foo('you')"))
299
301 def foo(evaluator, a, b):
302 return "hello %s and %s" % (a, b)
303 extension = {(None, 'foo'): foo}
304 tree = self.parse('<a><b></b></a>')
305 e = etree.XPathEvaluator(tree, extensions=[extension])
306 self.assertRaises(TypeError, e, "foo('you')")
307
309 def foo(evaluator, a):
310 return 1/0
311 extension = {(None, 'foo'): foo}
312 tree = self.parse('<a/>')
313 e = etree.XPathEvaluator(tree, extensions=[extension])
314 self.assertRaises(ZeroDivisionError, e, "foo('test')")
315
324
325 x = self.parse('<a/>')
326 e = etree.XPathEvaluator(x, extensions=[{(None, 'foo'): f}])
327 r = e("foo('World')/result")
328 self.assertEquals(2, len(r))
329 self.assertEquals('Hoi', r[0].text)
330 self.assertEquals('Dag', r[1].text)
331
340
341 x = self.parse('<a/>')
342 e = etree.XPathEvaluator(x, extensions=[{(None, 'foo'): f}])
343 r = e("foo(/*)/result")
344 self.assertEquals(2, len(r))
345 self.assertEquals('Hoi', r[0].text)
346 self.assertEquals('Dag', r[1].text)
347
357
358 x = self.parse('<result>Honk</result>')
359 e = etree.XPathEvaluator(x, extensions=[{(None, 'foo'): f}])
360 r = e("foo(/*)/result")
361 self.assertEquals(3, len(r))
362 self.assertEquals('Hoi', r[0].text)
363 self.assertEquals('Dag', r[1].text)
364 self.assertEquals('Honk', r[2].text)
365
367 tree = self.parse('<root><a/><b><c/></b></root>')
368
369 check_call = []
370 def check_context(ctxt, nodes):
371 self.assertEquals(len(nodes), 1)
372 check_call.append(nodes[0].tag)
373 self.assertEquals(ctxt.context_node, nodes[0])
374 return True
375
376 find = etree.XPath("//*[p:foo(.)]",
377 namespaces={'p' : 'ns'},
378 extensions=[{('ns', 'foo') : check_context}])
379 find(tree)
380
381 check_call.sort()
382 self.assertEquals(check_call, ["a", "b", "c", "root"])
383
385 tree = self.parse('<root><a/><b><c/></b></root>')
386
387 check_call = {}
388 def check_context(ctxt, nodes):
389 self.assertEquals(len(nodes), 1)
390 tag = nodes[0].tag
391
392 check_call[tag] = ctxt.eval_context.get("b")
393 ctxt.eval_context[tag] = tag
394 return True
395
396 find = etree.XPath("//b[p:foo(.)]/c[p:foo(.)]",
397 namespaces={'p' : 'ns'},
398 extensions=[{('ns', 'foo') : check_context}])
399 result = find(tree)
400
401 self.assertEquals(result, [tree.getroot()[1][0]])
402 self.assertEquals(check_call, {'b':None, 'c':'b'})
403
405 tree = self.parse('<root><a/><b><c/></b></root>')
406
407 check_call = {}
408 def check_context(ctxt):
409 check_call["done"] = True
410
411 self.assertEquals(len(ctxt.eval_context), 0)
412 ctxt.eval_context["test"] = True
413 return True
414
415 find = etree.XPath("//b[p:foo()]",
416 namespaces={'p' : 'ns'},
417 extensions=[{('ns', 'foo') : check_context}])
418 result = find(tree)
419
420 self.assertEquals(result, [tree.getroot()[1]])
421 self.assertEquals(check_call["done"], True)
422
423 check_call.clear()
424 find = etree.XPath("//b[p:foo()]",
425 namespaces={'p' : 'ns'},
426 extensions=[{('ns', 'foo') : check_context}])
427 result = find(tree)
428
429 self.assertEquals(result, [tree.getroot()[1]])
430 self.assertEquals(check_call["done"], True)
431
433 x = self.parse('<a attr="true"/>')
434 e = etree.XPathEvaluator(x)
435
436 expr = "/a[@attr=$aval]"
437 r = e(expr, aval=1)
438 self.assertEquals(0, len(r))
439
440 r = e(expr, aval="true")
441 self.assertEquals(1, len(r))
442 self.assertEquals("true", r[0].get('attr'))
443
444 r = e(expr, aval=True)
445 self.assertEquals(1, len(r))
446 self.assertEquals("true", r[0].get('attr'))
447
459
461 x = self.parse('<a attr="true"><test/></a>')
462
463 class LocalException(Exception):
464 pass
465
466 def foo(evaluator, a, varval):
467 etree.Element("DUMMY")
468 if varval == 0:
469 raise LocalException
470 elif varval == 1:
471 return ()
472 elif varval == 2:
473 return None
474 elif varval == 3:
475 return a[0][0]
476 a = a[0]
477 if a.get("attr") == str(varval):
478 return a
479 else:
480 return etree.Element("NODE")
481
482 extension = {(None, 'foo'): foo}
483 e = etree.XPathEvaluator(x, extensions=[extension])
484 del x
485
486 self.assertRaises(LocalException, e, "foo(., 0)")
487 self.assertRaises(LocalException, e, "foo(., $value)", value=0)
488
489 r = e("foo(., $value)", value=1)
490 self.assertEqual(len(r), 0)
491
492 r = e("foo(., 1)")
493 self.assertEqual(len(r), 0)
494
495 r = e("foo(., $value)", value=2)
496 self.assertEqual(len(r), 0)
497
498 r = e("foo(., $value)", value=3)
499 self.assertEqual(len(r), 1)
500 self.assertEqual(r[0].tag, "test")
501
502 r = e("foo(., $value)", value="false")
503 self.assertEqual(len(r), 1)
504 self.assertEqual(r[0].tag, "NODE")
505
506 r = e("foo(., 'false')")
507 self.assertEqual(len(r), 1)
508 self.assertEqual(r[0].tag, "NODE")
509
510 r = e("foo(., 'true')")
511 self.assertEqual(len(r), 1)
512 self.assertEqual(r[0].tag, "a")
513 self.assertEqual(r[0][0].tag, "test")
514
515 r = e("foo(., $value)", value="true")
516 self.assertEqual(len(r), 1)
517 self.assertEqual(r[0].tag, "a")
518
519 self.assertRaises(LocalException, e, "foo(., 0)")
520 self.assertRaises(LocalException, e, "foo(., $value)", value=0)
521
522
524 "Tests for the XPath class"
526 x = self.parse('<a attr="true"/>')
527
528 expr = etree.XPath("/a[@attr != 'true']")
529 r = expr(x)
530 self.assertEquals(0, len(r))
531
532 expr = etree.XPath("/a[@attr = 'true']")
533 r = expr(x)
534 self.assertEquals(1, len(r))
535
536 expr = etree.XPath( expr.path )
537 r = expr(x)
538 self.assertEquals(1, len(r))
539
541 x = self.parse('<a><b/><c/></a>')
542 root = x.getroot()
543
544 expr = etree.XPath("./b")
545 r = expr(root)
546 self.assertEquals(1, len(r))
547 self.assertEquals('b', r[0].tag)
548
549 expr = etree.XPath("./*")
550 r = expr(root)
551 self.assertEquals(2, len(r))
552
554 x = self.parse('<a attr="true"/>')
555
556 expr = etree.XPath("/a[@attr=$aval]")
557 r = expr(x, aval=False)
558 self.assertEquals(0, len(r))
559
560 r = expr(x, aval=True)
561 self.assertEquals(1, len(r))
562
564 self.assertRaises(SyntaxError, etree.XPath, '\\fad')
565
568
569
571 "Tests for the EXSLT support in XPath (requires libxslt 1.1.25+)"
572
573 NSMAP = dict(
574 date = "http://exslt.org/dates-and-times",
575 math = "http://exslt.org/math",
576 set = "http://exslt.org/sets",
577 str = "http://exslt.org/strings",
578 )
579
581 tree = self.parse('<a><b>2009-11-12</b><b>2008-12-11</b></a>')
582
583 match_dates = tree.xpath('//b[date:year(string()) = 2009]',
584 namespaces=self.NSMAP)
585 self.assertTrue(match_dates, str(match_dates))
586 self.assertEquals(len(match_dates), 1, str(match_dates))
587 self.assertEquals(match_dates[0].text, '2009-11-12')
588
590 tree = self.parse('<a><b>2009-11-12</b><b>2008-12-11</b></a>')
591
592 match_date = tree.xpath('str:replace(//b[1], "-", "*")',
593 namespaces=self.NSMAP)
594 self.assertTrue(match_date, str(match_date))
595 self.assertEquals(match_date, '2009*11*12')
596
597
599 "Tests for the ETXPath class"
601 x = self.parse('<a><b xmlns="nsa"/><b xmlns="nsb"/></a>')
602
603 expr = etree.ETXPath("/a/{nsa}b")
604 r = expr(x)
605 self.assertEquals(1, len(r))
606 self.assertEquals('{nsa}b', r[0].tag)
607
608 expr = etree.ETXPath("/a/{nsb}b")
609 r = expr(x)
610 self.assertEquals(1, len(r))
611 self.assertEquals('{nsb}b', r[0].tag)
612
613
614
616 x = self.parse(_bytes('<a><b xmlns="http://nsa/\\uf8d2"/><b xmlns="http://nsb/\\uf8d1"/></a>'
617 ).decode("unicode_escape"))
618
619 expr = etree.ETXPath(_bytes("/a/{http://nsa/\\uf8d2}b").decode("unicode_escape"))
620 r = expr(x)
621 self.assertEquals(1, len(r))
622 self.assertEquals(_bytes('{http://nsa/\\uf8d2}b').decode("unicode_escape"), r[0].tag)
623
624 expr = etree.ETXPath(_bytes("/a/{http://nsb/\\uf8d1}b").decode("unicode_escape"))
625 r = expr(x)
626 self.assertEquals(1, len(r))
627 self.assertEquals(_bytes('{http://nsb/\\uf8d1}b').decode("unicode_escape"), r[0].tag)
628
629 SAMPLE_XML = etree.parse(BytesIO("""
630 <body>
631 <tag>text</tag>
632 <section>
633 <tag>subtext</tag>
634 </section>
635 <tag />
636 <tag />
637 </body>
638 """))
639
642
644 return getattr(elem, 'tag', elem)
645
648
650 return ["Hello "] + list(s1) + ["!"]
651
654
657
660
663
665 return ", ".join(map(str, (s, f, b, list(map(tag, st)))))
666
668 st1.extend(st2)
669 return st1
670
673
676
677 uri = "http://www.example.com/"
678
679 extension = {(None, 'stringTest'): stringTest,
680 (None, 'stringListTest'): stringListTest,
681 (None, 'floatTest'): floatTest,
682 (None, 'booleanTest'): booleanTest,
683 (None, 'setTest'): setTest,
684 (None, 'setTest2'): setTest2,
685 (None, 'argsTest1'): argsTest1,
686 (None, 'argsTest2'): argsTest2,
687 (None, 'resultTypesTest'): resultTypesTest,
688 (None, 'resultTypesTest2'): resultTypesTest2,}
689
691 """
692 Test xpath extension functions.
693
694 >>> root = SAMPLE_XML
695 >>> e = etree.XPathEvaluator(root, extensions=[extension])
696 >>> e("stringTest('you')")
697 'Hello you'
698 >>> e(_bytes("stringTest('\\\\xe9lan')").decode("unicode_escape"))
699 u'Hello \\xe9lan'
700 >>> e("stringTest('you','there')") #doctest: +ELLIPSIS
701 Traceback (most recent call last):
702 ...
703 TypeError: stringTest() takes... 2 ...arguments ...
704 >>> e("floatTest(2)")
705 6.0
706 >>> e("booleanTest(true())")
707 False
708 >>> list(map(tag, e("setTest(/body/tag)")))
709 ['tag']
710 >>> list(map(tag, e("setTest2(/body/*)")))
711 ['tag', 'section']
712 >>> list(map(tag_or_value, e("stringListTest(/body/tag)")))
713 ['Hello ', 'tag', 'tag', 'tag', '!']
714 >>> e("argsTest1('a',1.5,true(),/body/tag)")
715 "a, 1.5, True, ['tag', 'tag', 'tag']"
716 >>> list(map(tag, e("argsTest2(/body/tag, /body/section)")))
717 ['tag', 'section', 'tag', 'tag']
718 >>> e("resultTypesTest()")
719 Traceback (most recent call last):
720 ...
721 XPathResultError: This is not a supported node-set result: None
722 >>> try:
723 ... e("resultTypesTest2()")
724 ... except etree.XPathResultError:
725 ... print("Got error")
726 Got error
727 """
728
729 if sys.version_info[0] >= 3:
730 xpath.__doc__ = xpath.__doc__.replace(" u'", " '")
731 xpath.__doc__ = xpath.__doc__.replace(" XPathResultError",
732 " lxml.etree.XPathResultError")
733 xpath.__doc__ = xpath.__doc__.replace(" exactly 2 arguments",
734 " exactly 2 positional arguments")
735
747
748 if __name__ == '__main__':
749 print('to test use test.py %s' % __file__)
750