test_xpath.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. from __future__ import absolute_import, division
  5. from twisted.trial import unittest
  6. from twisted.words.xish import xpath
  7. from twisted.words.xish.domish import Element
  8. from twisted.words.xish.xpath import XPathQuery
  9. from twisted.words.xish.xpathparser import SyntaxError
  10. class XPathTests(unittest.TestCase):
  11. def setUp(self):
  12. # Build element:
  13. # <foo xmlns='testns' attrib1='value1' attrib3="user@host/resource">
  14. # somecontent
  15. # <bar>
  16. # <foo>
  17. # <gar>DEF</gar>
  18. # </foo>
  19. # </bar>
  20. # somemorecontent
  21. # <bar attrib2="value2">
  22. # <bar>
  23. # <foo/>
  24. # <gar>ABC</gar>
  25. # </bar>
  26. # <bar/>
  27. # <bar attrib4='value4' attrib5='value5'>
  28. # <foo/>
  29. # <gar>JKL</gar>
  30. # </bar>
  31. # <bar attrib4='value4' attrib5='value4'>
  32. # <foo/>
  33. # <gar>MNO</gar>
  34. # </bar>
  35. # <bar attrib4='value4' attrib5='value6' attrib6='á'>
  36. # <quux>☃</quux>
  37. # </bar>
  38. # </foo>
  39. self.e = Element(("testns", "foo"))
  40. self.e["attrib1"] = "value1"
  41. self.e["attrib3"] = "user@host/resource"
  42. self.e.addContent(u"somecontent")
  43. self.bar1 = self.e.addElement("bar")
  44. self.subfoo = self.bar1.addElement("foo")
  45. self.gar1 = self.subfoo.addElement("gar")
  46. self.gar1.addContent(u"DEF")
  47. self.e.addContent(u"somemorecontent")
  48. self.bar2 = self.e.addElement("bar")
  49. self.bar2["attrib2"] = "value2"
  50. self.bar3 = self.bar2.addElement("bar")
  51. self.subfoo2 = self.bar3.addElement("foo")
  52. self.gar2 = self.bar3.addElement("gar")
  53. self.gar2.addContent(u"ABC")
  54. self.bar4 = self.e.addElement("bar")
  55. self.bar5 = self.e.addElement("bar")
  56. self.bar5["attrib4"] = "value4"
  57. self.bar5["attrib5"] = "value5"
  58. self.subfoo3 = self.bar5.addElement("foo")
  59. self.gar3 = self.bar5.addElement("gar")
  60. self.gar3.addContent(u"JKL")
  61. self.bar6 = self.e.addElement("bar")
  62. self.bar6["attrib4"] = "value4"
  63. self.bar6["attrib5"] = "value4"
  64. self.subfoo4 = self.bar6.addElement("foo")
  65. self.gar4 = self.bar6.addElement("gar")
  66. self.gar4.addContent(u"MNO")
  67. self.bar7 = self.e.addElement("bar")
  68. self.bar7["attrib4"] = "value4"
  69. self.bar7["attrib5"] = "value6"
  70. self.bar7["attrib6"] = u"á"
  71. self.quux = self.bar7.addElement("quux")
  72. self.quux.addContent(u"\N{SNOWMAN}")
  73. def test_staticMethods(self):
  74. """
  75. Test basic operation of the static methods.
  76. """
  77. self.assertEqual(xpath.matches("/foo/bar", self.e),
  78. True)
  79. self.assertEqual(xpath.queryForNodes("/foo/bar", self.e),
  80. [self.bar1, self.bar2, self.bar4,
  81. self.bar5, self.bar6, self.bar7])
  82. self.assertEqual(xpath.queryForString("/foo", self.e),
  83. "somecontent")
  84. self.assertEqual(xpath.queryForStringList("/foo", self.e),
  85. ["somecontent", "somemorecontent"])
  86. def test_locationFooBar(self):
  87. """
  88. Test matching foo with child bar.
  89. """
  90. xp = XPathQuery("/foo/bar")
  91. self.assertEqual(xp.matches(self.e), 1)
  92. def test_locationFooBarFoo(self):
  93. """
  94. Test finding foos at the second level.
  95. """
  96. xp = XPathQuery("/foo/bar/foo")
  97. self.assertEqual(xp.matches(self.e), 1)
  98. self.assertEqual(xp.queryForNodes(self.e), [self.subfoo,
  99. self.subfoo3,
  100. self.subfoo4])
  101. def test_locationNoBar3(self):
  102. """
  103. Test not finding bar3.
  104. """
  105. xp = XPathQuery("/foo/bar3")
  106. self.assertEqual(xp.matches(self.e), 0)
  107. def test_locationAllChilds(self):
  108. """
  109. Test finding childs of foo.
  110. """
  111. xp = XPathQuery("/foo/*")
  112. self.assertEqual(xp.matches(self.e), True)
  113. self.assertEqual(xp.queryForNodes(self.e), [self.bar1, self.bar2,
  114. self.bar4, self.bar5,
  115. self.bar6, self.bar7])
  116. def test_attribute(self):
  117. """
  118. Test matching foo with attribute.
  119. """
  120. xp = XPathQuery("/foo[@attrib1]")
  121. self.assertEqual(xp.matches(self.e), True)
  122. def test_attributeWithValueAny(self):
  123. """
  124. Test find nodes with attribute having value.
  125. """
  126. xp = XPathQuery("/foo/*[@attrib2='value2']")
  127. self.assertEqual(xp.matches(self.e), True)
  128. self.assertEqual(xp.queryForNodes(self.e), [self.bar2])
  129. def test_locationWithValueUnicode(self):
  130. """
  131. Nodes' attributes can be matched with non-ASCII values.
  132. """
  133. xp = XPathQuery(u"/foo/*[@attrib6='á']")
  134. self.assertEqual(xp.matches(self.e), True)
  135. self.assertEqual(xp.queryForNodes(self.e), [self.bar7])
  136. def test_namespaceFound(self):
  137. """
  138. Test matching node with namespace.
  139. """
  140. xp = XPathQuery("/foo[@xmlns='testns']/bar")
  141. self.assertEqual(xp.matches(self.e), 1)
  142. def test_namespaceNotFound(self):
  143. """
  144. Test not matching node with wrong namespace.
  145. """
  146. xp = XPathQuery("/foo[@xmlns='badns']/bar2")
  147. self.assertEqual(xp.matches(self.e), 0)
  148. def test_attributeWithValue(self):
  149. """
  150. Test matching node with attribute having value.
  151. """
  152. xp = XPathQuery("/foo[@attrib1='value1']")
  153. self.assertEqual(xp.matches(self.e), 1)
  154. def test_queryForString(self):
  155. """
  156. queryforString on absolute paths returns their first CDATA.
  157. """
  158. xp = XPathQuery("/foo")
  159. self.assertEqual(xp.queryForString(self.e), "somecontent")
  160. def test_queryForStringList(self):
  161. """
  162. queryforStringList on absolute paths returns all their CDATA.
  163. """
  164. xp = XPathQuery("/foo")
  165. self.assertEqual(xp.queryForStringList(self.e),
  166. ["somecontent", "somemorecontent"])
  167. def test_queryForStringListAnyLocation(self):
  168. """
  169. queryforStringList on relative paths returns all their CDATA.
  170. """
  171. xp = XPathQuery("//foo")
  172. self.assertEqual(xp.queryForStringList(self.e),
  173. ["somecontent", "somemorecontent"])
  174. def test_queryForNodes(self):
  175. """
  176. Test finding nodes.
  177. """
  178. xp = XPathQuery("/foo/bar")
  179. self.assertEqual(xp.queryForNodes(self.e), [self.bar1, self.bar2,
  180. self.bar4, self.bar5,
  181. self.bar6, self.bar7])
  182. def test_textCondition(self):
  183. """
  184. Test matching a node with given text.
  185. """
  186. xp = XPathQuery("/foo[text() = 'somecontent']")
  187. self.assertEqual(xp.matches(self.e), True)
  188. def test_textConditionUnicode(self):
  189. """
  190. A node can be matched by text with non-ascii code points.
  191. """
  192. xp = XPathQuery(u"//*[text()='\N{SNOWMAN}']")
  193. self.assertEqual(xp.matches(self.e), True)
  194. self.assertEqual(xp.queryForNodes(self.e), [self.quux])
  195. def test_textNotOperator(self):
  196. """
  197. Test for not operator.
  198. """
  199. xp = XPathQuery("/foo[not(@nosuchattrib)]")
  200. self.assertEqual(xp.matches(self.e), True)
  201. def test_anyLocationAndText(self):
  202. """
  203. Test finding any nodes named gar and getting their text contents.
  204. """
  205. xp = XPathQuery("//gar")
  206. self.assertEqual(xp.matches(self.e), True)
  207. self.assertEqual(xp.queryForNodes(self.e), [self.gar1, self.gar2,
  208. self.gar3, self.gar4])
  209. self.assertEqual(xp.queryForStringList(self.e), ["DEF", "ABC",
  210. "JKL", "MNO"])
  211. def test_anyLocation(self):
  212. """
  213. Test finding any nodes named bar.
  214. """
  215. xp = XPathQuery("//bar")
  216. self.assertEqual(xp.matches(self.e), True)
  217. self.assertEqual(xp.queryForNodes(self.e), [self.bar1, self.bar2,
  218. self.bar3, self.bar4,
  219. self.bar5, self.bar6,
  220. self.bar7])
  221. def test_anyLocationQueryForString(self):
  222. """
  223. L{XPathQuery.queryForString} should raise a L{NotImplementedError}
  224. for any location.
  225. """
  226. xp = XPathQuery("//bar")
  227. self.assertRaises(NotImplementedError, xp.queryForString, None)
  228. def test_andOperator(self):
  229. """
  230. Test boolean and operator in condition.
  231. """
  232. xp = XPathQuery("//bar[@attrib4='value4' and @attrib5='value5']")
  233. self.assertEqual(xp.matches(self.e), True)
  234. self.assertEqual(xp.queryForNodes(self.e), [self.bar5])
  235. def test_orOperator(self):
  236. """
  237. Test boolean or operator in condition.
  238. """
  239. xp = XPathQuery("//bar[@attrib5='value4' or @attrib5='value5']")
  240. self.assertEqual(xp.matches(self.e), True)
  241. self.assertEqual(xp.queryForNodes(self.e), [self.bar5, self.bar6])
  242. def test_booleanOperatorsParens(self):
  243. """
  244. Test multiple boolean operators in condition with parens.
  245. """
  246. xp = XPathQuery("""//bar[@attrib4='value4' and
  247. (@attrib5='value4' or @attrib5='value6')]""")
  248. self.assertEqual(xp.matches(self.e), True)
  249. self.assertEqual(xp.queryForNodes(self.e), [self.bar6, self.bar7])
  250. def test_booleanOperatorsNoParens(self):
  251. """
  252. Test multiple boolean operators in condition without parens.
  253. """
  254. xp = XPathQuery("""//bar[@attrib5='value4' or
  255. @attrib5='value5' or
  256. @attrib5='value6']""")
  257. self.assertEqual(xp.matches(self.e), True)
  258. self.assertEqual(xp.queryForNodes(self.e), [self.bar5, self.bar6, self.bar7])
  259. def test_badXPathNoClosingBracket(self):
  260. """
  261. A missing closing bracket raises a SyntaxError.
  262. This test excercises the most common failure mode.
  263. """
  264. exc = self.assertRaises(SyntaxError, XPathQuery, """//bar[@attrib1""")
  265. self.assertTrue(exc.msg.startswith("Trying to find one of"),
  266. ("SyntaxError message '%s' doesn't start with "
  267. "'Trying to find one of'") % exc.msg)