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   
 20          tree = self.parse('<a><b></b><b></b></a>') 
 21          self.assertTrue(tree.xpath('boolean(/a/b)')) 
 22          self.assertTrue(not tree.xpath('boolean(/a/c)')) 
  23   
 25          tree = self.parse('<a>1</a>') 
 26          self.assertEqual(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.assertEqual('Foo', 
 37                            tree.xpath('string(/a/text())')) 
  38   
 40          tree = self.parse('<a><b/></a>') 
 41          self.assertEqual([], 
 42                            tree.xpath('/')) 
  43   
 45          tree = self.parse('<a xmlns="test" xmlns:p="myURI"/>') 
 46          self.assertTrue((None, "test") in tree.xpath('namespace::*')) 
 47          self.assertTrue(('p', 'myURI') in tree.xpath('namespace::*')) 
  48   
 50          tree = self.parse('<a/>') 
 51          self.assertEqual([('xml', 'http://www.w3.org/XML/1998/namespace')], 
 52                            tree.xpath('namespace::*')) 
  53   
 59   
 61          tree = self.parse('<a><b/></a>') 
 62          self.assertEqual([], 
 63                            tree.xpath('/a/c')) 
 64           
 65          self.assertEqual([], 
 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.assertEqual(['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.assertEqual(['FooBar', 'BarFoo'], 
 78                            tree.xpath('/a/b/text()')) 
 79          self.assertEqual([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.assertEqual(['FooBar', 'BarFoo'], 
 86                            tree.xpath('/a/b/text()', smart_strings=True)) 
 87          self.assertEqual([root[0], root[1]], 
 88                            [r.getparent() for r in 
 89                             tree.xpath('/a/b/text()', smart_strings=True)]) 
 90          self.assertEqual([None, None], 
 91                            [r.attrname for r in 
 92                             tree.xpath('/a/b/text()', smart_strings=True)]) 
 93   
 94          self.assertEqual(['FooBar', 'BarFoo'], 
 95                            tree.xpath('/a/b/text()', smart_strings=False)) 
 96          self.assertEqual([False, False], 
 97                            [hasattr(r, 'getparent') for r in 
 98                             tree.xpath('/a/b/text()', smart_strings=False)]) 
 99          self.assertEqual([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.assertEqual([_bytes('FooBar\\u0680\\u3120').decode("unicode_escape"), 
108                             _bytes('BarFoo\\u0680\\u3120').decode("unicode_escape")], 
109                            tree.xpath('/a/b/text()')) 
110          self.assertEqual([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.assertEqual(['B'], 
116                            tree.xpath('/a/@b')) 
 117   
119          tree = self.parse('<a b="BaSdFgHjKl" c="CqWeRtZuI"/>') 
120          results = tree.xpath('/a/@c') 
121          self.assertEqual(1, len(results)) 
122          self.assertEqual('CqWeRtZuI', results[0]) 
123          self.assertEqual(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.assertEqual(1, len(results)) 
130          self.assertEqual('CqWeRtZuI', results[0]) 
131          self.assertEqual('c', results[0].attrname) 
132          self.assertEqual(tree.getroot().tag, results[0].getparent().tag) 
133   
134          results = tree.xpath('/a/@c', smart_strings=False) 
135          self.assertEqual(1, len(results)) 
136          self.assertEqual('CqWeRtZuI', results[0]) 
137          self.assertEqual(False, hasattr(results[0], 'getparent')) 
138          self.assertEqual(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.assertEqual(['v1'], values) 
156          self.assertEqual('value', values[0].getparent().tag) 
 157   
162   
164          root = etree.XML('<a><b><c/></b></a>') 
165          el = root[0] 
166          self.assertTrue(el.xpath('boolean(c)')) 
167          self.assertTrue(not el.xpath('boolean(d)')) 
 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.assertEqual([c[0], c[1]], 
174                            c.xpath('b')) 
175          self.assertEqual([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.assertEqual( 
182              [root[0]], 
183              tree.xpath('//foo:b', namespaces={'foo': 'uri:a'})) 
184          self.assertEqual( 
185              [], 
186              tree.xpath('//foo:b', namespaces={'foo': 'uri:c'})) 
187          self.assertEqual( 
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.assertEqual( 
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.assertEqual(2, len(r)) 
329          self.assertEqual('Hoi', r[0].text) 
330          self.assertEqual('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.assertEqual(2, len(r)) 
345          self.assertEqual('Hoi', r[0].text) 
346          self.assertEqual('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.assertEqual(3, len(r)) 
362          self.assertEqual('Hoi',  r[0].text) 
363          self.assertEqual('Dag',  r[1].text) 
364          self.assertEqual('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.assertEqual(len(nodes), 1) 
372              check_call.append(nodes[0].tag) 
373              self.assertEqual(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.assertEqual(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.assertEqual(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.assertEqual(result, [tree.getroot()[1][0]]) 
402          self.assertEqual(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.assertEqual(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.assertEqual(result, [tree.getroot()[1]]) 
421          self.assertEqual(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.assertEqual(result, [tree.getroot()[1]]) 
430          self.assertEqual(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.assertEqual(0, len(r)) 
439   
440          r = e(expr, aval="true") 
441          self.assertEqual(1, len(r)) 
442          self.assertEqual("true", r[0].get('attr')) 
443   
444          r = e(expr, aval=True) 
445          self.assertEqual(1, len(r)) 
446          self.assertEqual("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.assertEqual(0, len(r)) 
531   
532          expr = etree.XPath("/a[@attr = 'true']") 
533          r = expr(x) 
534          self.assertEqual(1, len(r)) 
535   
536          expr = etree.XPath( expr.path ) 
537          r = expr(x) 
538          self.assertEqual(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.assertEqual(1, len(r)) 
547          self.assertEqual('b', r[0].tag) 
548   
549          expr = etree.XPath("./*") 
550          r = expr(root) 
551          self.assertEqual(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.assertEqual(0, len(r)) 
559   
560          r = expr(x, aval=True) 
561          self.assertEqual(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.assertEqual(len(match_dates), 1, str(match_dates)) 
587          self.assertEqual(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          aligned_date = tree.xpath( 
593              'str:align(string(//b[1]), "%s", "center")' % ('-'*20), 
594              namespaces=self.NSMAP) 
595          self.assertTrue(aligned_date, str(aligned_date)) 
596          self.assertEqual(aligned_date, '-----2009-11-12-----') 
  597   
598   
600      "Tests for the ETXPath class" 
602          x = self.parse('<a><b xmlns="nsa"/><b xmlns="nsb"/></a>') 
603   
604          expr = etree.ETXPath("/a/{nsa}b") 
605          r = expr(x) 
606          self.assertEqual(1, len(r)) 
607          self.assertEqual('{nsa}b', r[0].tag) 
608   
609          expr = etree.ETXPath("/a/{nsb}b") 
610          r = expr(x) 
611          self.assertEqual(1, len(r)) 
612          self.assertEqual('{nsb}b', r[0].tag) 
 613   
614       
615       
617          x = self.parse(_bytes('<a><b xmlns="http://nsa/\\uf8d2"/><b xmlns="http://nsb/\\uf8d1"/></a>' 
618                                ).decode("unicode_escape")) 
619   
620          expr = etree.ETXPath(_bytes("/a/{http://nsa/\\uf8d2}b").decode("unicode_escape")) 
621          r = expr(x) 
622          self.assertEqual(1, len(r)) 
623          self.assertEqual(_bytes('{http://nsa/\\uf8d2}b').decode("unicode_escape"), r[0].tag) 
624   
625          expr = etree.ETXPath(_bytes("/a/{http://nsb/\\uf8d1}b").decode("unicode_escape")) 
626          r = expr(x) 
627          self.assertEqual(1, len(r)) 
628          self.assertEqual(_bytes('{http://nsb/\\uf8d1}b').decode("unicode_escape"), r[0].tag) 
  629   
630  SAMPLE_XML = etree.parse(BytesIO(""" 
631  <body> 
632    <tag>text</tag> 
633    <section> 
634      <tag>subtext</tag> 
635    </section> 
636    <tag /> 
637    <tag /> 
638  </body> 
639  """)) 
640   
643   
645      return getattr(elem, 'tag', elem) 
 646   
649   
651      return ["Hello "] + list(s1) +  ["!"] 
 652       
655   
658       
661       
664   
666      return ", ".join(map(str, (s, f, b, list(map(tag, st))))) 
 667   
669      st1.extend(st2) 
670      return st1 
 671   
674   
677       
678  uri = "http://www.example.com/" 
679   
680  extension = {(None, 'stringTest'): stringTest, 
681               (None, 'stringListTest'): stringListTest, 
682               (None, 'floatTest'): floatTest, 
683               (None, 'booleanTest'): booleanTest, 
684               (None, 'setTest'): setTest, 
685               (None, 'setTest2'): setTest2, 
686               (None, 'argsTest1'): argsTest1, 
687               (None, 'argsTest2'): argsTest2, 
688               (None, 'resultTypesTest'): resultTypesTest, 
689               (None, 'resultTypesTest2'): resultTypesTest2,} 
690   
692      """ 
693      Test xpath extension functions. 
694       
695      >>> root = SAMPLE_XML 
696      >>> e = etree.XPathEvaluator(root, extensions=[extension]) 
697      >>> e("stringTest('you')") 
698      'Hello you' 
699      >>> e(_bytes("stringTest('\\\\xe9lan')").decode("unicode_escape")) 
700      u'Hello \\xe9lan' 
701      >>> e("stringTest('you','there')")   #doctest: +ELLIPSIS 
702      Traceback (most recent call last): 
703      ... 
704      TypeError: stringTest() takes... 2 ...arguments ... 
705      >>> e("floatTest(2)") 
706      6.0 
707      >>> e("booleanTest(true())") 
708      False 
709      >>> list(map(tag, e("setTest(/body/tag)"))) 
710      ['tag'] 
711      >>> list(map(tag, e("setTest2(/body/*)"))) 
712      ['tag', 'section'] 
713      >>> list(map(tag_or_value, e("stringListTest(/body/tag)"))) 
714      ['Hello ', 'tag', 'tag', 'tag', '!'] 
715      >>> e("argsTest1('a',1.5,true(),/body/tag)") 
716      "a, 1.5, True, ['tag', 'tag', 'tag']" 
717      >>> list(map(tag, e("argsTest2(/body/tag, /body/section)"))) 
718      ['tag', 'section', 'tag', 'tag'] 
719      >>> e("resultTypesTest()") 
720      Traceback (most recent call last): 
721      ... 
722      XPathResultError: This is not a supported node-set result: None 
723      >>> try: 
724      ...     e("resultTypesTest2()") 
725      ... except etree.XPathResultError: 
726      ...     print("Got error") 
727      Got error 
728      """ 
 729   
730  if sys.version_info[0] >= 3: 
731      xpath.__doc__ = xpath.__doc__.replace(" u'", " '") 
732      xpath.__doc__ = xpath.__doc__.replace(" XPathResultError", 
733                                            " lxml.etree.XPathResultError") 
734      xpath.__doc__ = xpath.__doc__.replace(" exactly 2 arguments", 
735                                            " exactly 2 positional arguments") 
736   
748   
749  if __name__ == '__main__': 
750      print('to test use test.py %s' % __file__) 
751