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