test_domish.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.words.xish.domish}, a DOM-like library for XMPP.
  5. """
  6. from __future__ import absolute_import, division
  7. from zope.interface.verify import verifyObject
  8. from twisted.python.compat import _PY3, unicode
  9. from twisted.python.reflect import requireModule
  10. from twisted.trial import unittest
  11. from twisted.words.xish import domish
  12. class ElementTests(unittest.TestCase):
  13. """
  14. Tests for L{domish.Element}.
  15. """
  16. def test_interface(self):
  17. """
  18. L{domish.Element} implements L{domish.IElement}.
  19. """
  20. verifyObject(domish.IElement, domish.Element((None, u"foo")))
  21. def test_escaping(self):
  22. """
  23. The built-in entity references are properly encoded.
  24. """
  25. s = "&<>'\""
  26. self.assertEqual(domish.escapeToXml(s), "&amp;&lt;&gt;'\"")
  27. self.assertEqual(domish.escapeToXml(s, 1), "&amp;&lt;&gt;&apos;&quot;")
  28. def test_namespace(self):
  29. """
  30. An attribute on L{domish.Namespace} yields a qualified name.
  31. """
  32. ns = domish.Namespace("testns")
  33. self.assertEqual(ns.foo, ("testns", "foo"))
  34. def test_elementInit(self):
  35. """
  36. Basic L{domish.Element} initialization tests.
  37. """
  38. e = domish.Element((None, "foo"))
  39. self.assertEqual(e.name, "foo")
  40. self.assertEqual(e.uri, None)
  41. self.assertEqual(e.defaultUri, None)
  42. self.assertEqual(e.parent, None)
  43. e = domish.Element(("", "foo"))
  44. self.assertEqual(e.name, "foo")
  45. self.assertEqual(e.uri, "")
  46. self.assertEqual(e.defaultUri, "")
  47. self.assertEqual(e.parent, None)
  48. e = domish.Element(("testns", "foo"))
  49. self.assertEqual(e.name, "foo")
  50. self.assertEqual(e.uri, "testns")
  51. self.assertEqual(e.defaultUri, "testns")
  52. self.assertEqual(e.parent, None)
  53. e = domish.Element(("testns", "foo"), "test2ns")
  54. self.assertEqual(e.name, "foo")
  55. self.assertEqual(e.uri, "testns")
  56. self.assertEqual(e.defaultUri, "test2ns")
  57. def test_childOps(self):
  58. """
  59. Basic L{domish.Element} child tests.
  60. """
  61. e = domish.Element(("testns", "foo"))
  62. e.addContent(u"somecontent")
  63. b2 = e.addElement(("testns2", "bar2"))
  64. e["attrib1"] = "value1"
  65. e[("testns2", "attrib2")] = "value2"
  66. e.addElement("bar")
  67. e.addElement("bar")
  68. e.addContent(u"abc")
  69. e.addContent(u"123")
  70. # Check content merging
  71. self.assertEqual(e.children[-1], "abc123")
  72. # Check direct child accessor
  73. self.assertEqual(e.bar2, b2)
  74. e.bar2.addContent(u"subcontent")
  75. e.bar2["bar2value"] = "somevalue"
  76. # Check child ops
  77. self.assertEqual(e.children[1], e.bar2)
  78. self.assertEqual(e.children[2], e.bar)
  79. # Check attribute ops
  80. self.assertEqual(e["attrib1"], "value1")
  81. del e["attrib1"]
  82. self.assertEqual(e.hasAttribute("attrib1"), 0)
  83. self.assertEqual(e.hasAttribute("attrib2"), 0)
  84. self.assertEqual(e[("testns2", "attrib2")], "value2")
  85. def test_characterData(self):
  86. """
  87. Extract character data using L{unicode}.
  88. """
  89. element = domish.Element((u"testns", u"foo"))
  90. element.addContent(u"somecontent")
  91. text = unicode(element)
  92. self.assertEqual(u"somecontent", text)
  93. self.assertIsInstance(text, unicode)
  94. def test_characterDataNativeString(self):
  95. """
  96. Extract ascii character data using L{str}.
  97. """
  98. element = domish.Element((u"testns", u"foo"))
  99. element.addContent(u"somecontent")
  100. text = str(element)
  101. self.assertEqual("somecontent", text)
  102. self.assertIsInstance(text, str)
  103. def test_characterDataUnicode(self):
  104. """
  105. Extract character data using L{unicode}.
  106. """
  107. element = domish.Element((u"testns", u"foo"))
  108. element.addContent(u"\N{SNOWMAN}")
  109. text = unicode(element)
  110. self.assertEqual(u"\N{SNOWMAN}", text)
  111. self.assertIsInstance(text, unicode)
  112. def test_characterDataBytes(self):
  113. """
  114. Extract character data as UTF-8 using L{bytes}.
  115. """
  116. element = domish.Element((u"testns", u"foo"))
  117. element.addContent(u"\N{SNOWMAN}")
  118. text = bytes(element)
  119. self.assertEqual(u"\N{SNOWMAN}".encode('utf-8'), text)
  120. self.assertIsInstance(text, bytes)
  121. def test_characterDataMixed(self):
  122. """
  123. Mixing addChild with cdata and element, the first cdata is returned.
  124. """
  125. element = domish.Element((u"testns", u"foo"))
  126. element.addChild(u"abc")
  127. element.addElement("bar")
  128. element.addChild(u"def")
  129. self.assertEqual(u"abc", unicode(element))
  130. def test_addContent(self):
  131. """
  132. Unicode strings passed to C{addContent} become the character data.
  133. """
  134. element = domish.Element((u"testns", u"foo"))
  135. element.addContent(u'unicode')
  136. self.assertEqual(u"unicode", unicode(element))
  137. def test_addContentNativeStringASCII(self):
  138. """
  139. ASCII native strings passed to C{addContent} become the character data.
  140. """
  141. element = domish.Element((u"testns", u"foo"))
  142. element.addContent('native')
  143. self.assertEqual(u"native", unicode(element))
  144. def test_addContentBytes(self):
  145. """
  146. Byte strings passed to C{addContent} are not acceptable on Python 3.
  147. """
  148. element = domish.Element((u"testns", u"foo"))
  149. self.assertRaises(TypeError, element.addContent, b'bytes')
  150. if not _PY3:
  151. test_addContentBytes.skip = (
  152. "Bytes behavior of addContent only provided on Python 3.")
  153. def test_addContentBytesNonASCII(self):
  154. """
  155. Non-ASCII byte strings passed to C{addContent} yield L{UnicodeError}.
  156. """
  157. element = domish.Element((u"testns", u"foo"))
  158. self.assertRaises(UnicodeError, element.addContent, b'\xe2\x98\x83')
  159. if _PY3:
  160. test_addContentBytesNonASCII.skip = (
  161. "Bytes behavior of addContent only provided on Python 2.")
  162. def test_addElementContent(self):
  163. """
  164. Content passed to addElement becomes character data on the new child.
  165. """
  166. element = domish.Element((u"testns", u"foo"))
  167. child = element.addElement("bar", content=u"abc")
  168. self.assertEqual(u"abc", unicode(child))
  169. def test_elements(self):
  170. """
  171. Calling C{elements} without arguments on a L{domish.Element} returns
  172. all child elements, whatever the qualified name.
  173. """
  174. e = domish.Element((u"testns", u"foo"))
  175. c1 = e.addElement(u"name")
  176. c2 = e.addElement((u"testns2", u"baz"))
  177. c3 = e.addElement(u"quux")
  178. c4 = e.addElement((u"testns", u"name"))
  179. elts = list(e.elements())
  180. self.assertIn(c1, elts)
  181. self.assertIn(c2, elts)
  182. self.assertIn(c3, elts)
  183. self.assertIn(c4, elts)
  184. def test_elementsWithQN(self):
  185. """
  186. Calling C{elements} with a namespace and local name on a
  187. L{domish.Element} returns all child elements with that qualified name.
  188. """
  189. e = domish.Element((u"testns", u"foo"))
  190. c1 = e.addElement(u"name")
  191. c2 = e.addElement((u"testns2", u"baz"))
  192. c3 = e.addElement(u"quux")
  193. c4 = e.addElement((u"testns", u"name"))
  194. elts = list(e.elements(u"testns", u"name"))
  195. self.assertIn(c1, elts)
  196. self.assertNotIn(c2, elts)
  197. self.assertNotIn(c3, elts)
  198. self.assertIn(c4, elts)
  199. class DomishStreamTestsMixin:
  200. """
  201. Mixin defining tests for different stream implementations.
  202. @ivar streamClass: A no-argument callable which will be used to create an
  203. XML parser which can produce a stream of elements from incremental
  204. input.
  205. """
  206. def setUp(self):
  207. self.doc_started = False
  208. self.doc_ended = False
  209. self.root = None
  210. self.elements = []
  211. self.stream = self.streamClass()
  212. self.stream.DocumentStartEvent = self._docStarted
  213. self.stream.ElementEvent = self.elements.append
  214. self.stream.DocumentEndEvent = self._docEnded
  215. def _docStarted(self, root):
  216. self.root = root
  217. self.doc_started = True
  218. def _docEnded(self):
  219. self.doc_ended = True
  220. def doTest(self, xml):
  221. self.stream.parse(xml)
  222. def testHarness(self):
  223. xml = b"<root><child/><child2/></root>"
  224. self.stream.parse(xml)
  225. self.assertEqual(self.doc_started, True)
  226. self.assertEqual(self.root.name, 'root')
  227. self.assertEqual(self.elements[0].name, 'child')
  228. self.assertEqual(self.elements[1].name, 'child2')
  229. self.assertEqual(self.doc_ended, True)
  230. def testBasic(self):
  231. xml = b"<stream:stream xmlns:stream='etherx' xmlns='jabber'>\n" + \
  232. b" <message to='bar'>" + \
  233. b" <x xmlns='xdelay'>some&amp;data&gt;</x>" + \
  234. b" </message>" + \
  235. b"</stream:stream>"
  236. self.stream.parse(xml)
  237. self.assertEqual(self.root.name, 'stream')
  238. self.assertEqual(self.root.uri, 'etherx')
  239. self.assertEqual(self.elements[0].name, 'message')
  240. self.assertEqual(self.elements[0].uri, 'jabber')
  241. self.assertEqual(self.elements[0]['to'], 'bar')
  242. self.assertEqual(self.elements[0].x.uri, 'xdelay')
  243. self.assertEqual(unicode(self.elements[0].x), 'some&data>')
  244. def testNoRootNS(self):
  245. xml = b"<stream><error xmlns='etherx'/></stream>"
  246. self.stream.parse(xml)
  247. self.assertEqual(self.root.uri, '')
  248. self.assertEqual(self.elements[0].uri, 'etherx')
  249. def testNoDefaultNS(self):
  250. xml = b"<stream:stream xmlns:stream='etherx'><error/></stream:stream>"
  251. self.stream.parse(xml)
  252. self.assertEqual(self.root.uri, 'etherx')
  253. self.assertEqual(self.root.defaultUri, '')
  254. self.assertEqual(self.elements[0].uri, '')
  255. self.assertEqual(self.elements[0].defaultUri, '')
  256. def testChildDefaultNS(self):
  257. xml = b"<root xmlns='testns'><child/></root>"
  258. self.stream.parse(xml)
  259. self.assertEqual(self.root.uri, 'testns')
  260. self.assertEqual(self.elements[0].uri, 'testns')
  261. def testEmptyChildNS(self):
  262. xml = b"""<root xmlns='testns'>
  263. <child1><child2 xmlns=''/></child1>
  264. </root>"""
  265. self.stream.parse(xml)
  266. self.assertEqual(self.elements[0].child2.uri, '')
  267. def test_namespaceWithWhitespace(self):
  268. """
  269. Whitespace in an xmlns value is preserved in the resulting node's C{uri}
  270. attribute.
  271. """
  272. xml = b"<root xmlns:foo=' bar baz '><foo:bar foo:baz='quux'/></root>"
  273. self.stream.parse(xml)
  274. self.assertEqual(self.elements[0].uri, " bar baz ")
  275. self.assertEqual(
  276. self.elements[0].attributes, {(" bar baz ", "baz"): "quux"})
  277. def testChildPrefix(self):
  278. xml = b"<root xmlns='testns' xmlns:foo='testns2'><foo:child/></root>"
  279. self.stream.parse(xml)
  280. self.assertEqual(self.root.localPrefixes['foo'], 'testns2')
  281. self.assertEqual(self.elements[0].uri, 'testns2')
  282. def testUnclosedElement(self):
  283. self.assertRaises(domish.ParserError, self.stream.parse,
  284. b"<root><error></root>")
  285. def test_namespaceReuse(self):
  286. """
  287. Test that reuse of namespaces does affect an element's serialization.
  288. When one element uses a prefix for a certain namespace, this is
  289. stored in the C{localPrefixes} attribute of the element. We want
  290. to make sure that elements created after such use, won't have this
  291. prefix end up in their C{localPrefixes} attribute, too.
  292. """
  293. xml = b"""<root>
  294. <foo:child1 xmlns:foo='testns'/>
  295. <child2 xmlns='testns'/>
  296. </root>"""
  297. self.stream.parse(xml)
  298. self.assertEqual('child1', self.elements[0].name)
  299. self.assertEqual('testns', self.elements[0].uri)
  300. self.assertEqual('', self.elements[0].defaultUri)
  301. self.assertEqual({'foo': 'testns'}, self.elements[0].localPrefixes)
  302. self.assertEqual('child2', self.elements[1].name)
  303. self.assertEqual('testns', self.elements[1].uri)
  304. self.assertEqual('testns', self.elements[1].defaultUri)
  305. self.assertEqual({}, self.elements[1].localPrefixes)
  306. class DomishExpatStreamTests(DomishStreamTestsMixin, unittest.TestCase):
  307. """
  308. Tests for L{domish.ExpatElementStream}, the expat-based element stream
  309. implementation.
  310. """
  311. streamClass = domish.ExpatElementStream
  312. if requireModule('pyexpat', default=None) is None:
  313. skip = "pyexpat is required for ExpatElementStream tests."
  314. else:
  315. skip = None
  316. class DomishSuxStreamTests(DomishStreamTestsMixin, unittest.TestCase):
  317. """
  318. Tests for L{domish.SuxElementStream}, the L{twisted.web.sux}-based element
  319. stream implementation.
  320. """
  321. streamClass = domish.SuxElementStream
  322. if _PY3:
  323. skip = "twisted.web.sux has not been ported to Python 3, yet."
  324. elif domish.SuxElementStream is None:
  325. skip = "twisted.web is required for SuxElementStream tests."
  326. class SerializerTests(unittest.TestCase):
  327. def testNoNamespace(self):
  328. e = domish.Element((None, "foo"))
  329. self.assertEqual(e.toXml(), "<foo/>")
  330. self.assertEqual(e.toXml(closeElement = 0), "<foo>")
  331. def testDefaultNamespace(self):
  332. e = domish.Element(("testns", "foo"))
  333. self.assertEqual(e.toXml(), "<foo xmlns='testns'/>")
  334. def testOtherNamespace(self):
  335. e = domish.Element(("testns", "foo"), "testns2")
  336. self.assertEqual(e.toXml({'testns': 'bar'}),
  337. "<bar:foo xmlns:bar='testns' xmlns='testns2'/>")
  338. def testChildDefaultNamespace(self):
  339. e = domish.Element(("testns", "foo"))
  340. e.addElement("bar")
  341. self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
  342. def testChildSameNamespace(self):
  343. e = domish.Element(("testns", "foo"))
  344. e.addElement(("testns", "bar"))
  345. self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
  346. def testChildSameDefaultNamespace(self):
  347. e = domish.Element(("testns", "foo"))
  348. e.addElement("bar", "testns")
  349. self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
  350. def testChildOtherDefaultNamespace(self):
  351. e = domish.Element(("testns", "foo"))
  352. e.addElement(("testns2", "bar"), 'testns2')
  353. self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar xmlns='testns2'/></foo>")
  354. def testOnlyChildDefaultNamespace(self):
  355. e = domish.Element((None, "foo"))
  356. e.addElement(("ns2", "bar"), 'ns2')
  357. self.assertEqual(e.toXml(), "<foo><bar xmlns='ns2'/></foo>")
  358. def testOnlyChildDefaultNamespace2(self):
  359. e = domish.Element((None, "foo"))
  360. e.addElement("bar")
  361. self.assertEqual(e.toXml(), "<foo><bar/></foo>")
  362. def testChildInDefaultNamespace(self):
  363. e = domish.Element(("testns", "foo"), "testns2")
  364. e.addElement(("testns2", "bar"))
  365. self.assertEqual(e.toXml(), "<xn0:foo xmlns:xn0='testns' xmlns='testns2'><bar/></xn0:foo>")
  366. def testQualifiedAttribute(self):
  367. e = domish.Element((None, "foo"),
  368. attribs = {("testns2", "bar"): "baz"})
  369. self.assertEqual(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'/>")
  370. def testQualifiedAttributeDefaultNS(self):
  371. e = domish.Element(("testns", "foo"),
  372. attribs = {("testns", "bar"): "baz"})
  373. self.assertEqual(e.toXml(), "<foo xmlns='testns' xmlns:xn0='testns' xn0:bar='baz'/>")
  374. def testTwoChilds(self):
  375. e = domish.Element(('', "foo"))
  376. child1 = e.addElement(("testns", "bar"), "testns2")
  377. child1.addElement(('testns2', 'quux'))
  378. child2 = e.addElement(("testns3", "baz"), "testns4")
  379. child2.addElement(('testns', 'quux'))
  380. self.assertEqual(e.toXml(), "<foo><xn0:bar xmlns:xn0='testns' xmlns='testns2'><quux/></xn0:bar><xn1:baz xmlns:xn1='testns3' xmlns='testns4'><xn0:quux xmlns:xn0='testns'/></xn1:baz></foo>")
  381. def testXMLNamespace(self):
  382. e = domish.Element((None, "foo"),
  383. attribs = {("http://www.w3.org/XML/1998/namespace",
  384. "lang"): "en_US"})
  385. self.assertEqual(e.toXml(), "<foo xml:lang='en_US'/>")
  386. def testQualifiedAttributeGivenListOfPrefixes(self):
  387. e = domish.Element((None, "foo"),
  388. attribs = {("testns2", "bar"): "baz"})
  389. self.assertEqual(e.toXml({"testns2": "qux"}),
  390. "<foo xmlns:qux='testns2' qux:bar='baz'/>")
  391. def testNSPrefix(self):
  392. e = domish.Element((None, "foo"),
  393. attribs = {("testns2", "bar"): "baz"})
  394. c = e.addElement(("testns2", "qux"))
  395. c[("testns2", "bar")] = "quux"
  396. self.assertEqual(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'><xn0:qux xn0:bar='quux'/></foo>")
  397. def testDefaultNSPrefix(self):
  398. e = domish.Element((None, "foo"),
  399. attribs = {("testns2", "bar"): "baz"})
  400. c = e.addElement(("testns2", "qux"))
  401. c[("testns2", "bar")] = "quux"
  402. c.addElement('foo')
  403. self.assertEqual(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'><xn0:qux xn0:bar='quux'><xn0:foo/></xn0:qux></foo>")
  404. def testPrefixScope(self):
  405. e = domish.Element(('testns', 'foo'))
  406. self.assertEqual(e.toXml(prefixes={'testns': 'bar'},
  407. prefixesInScope=['bar']),
  408. "<bar:foo/>")
  409. def testLocalPrefixes(self):
  410. e = domish.Element(('testns', 'foo'), localPrefixes={'bar': 'testns'})
  411. self.assertEqual(e.toXml(), "<bar:foo xmlns:bar='testns'/>")
  412. def testLocalPrefixesWithChild(self):
  413. e = domish.Element(('testns', 'foo'), localPrefixes={'bar': 'testns'})
  414. e.addElement('baz')
  415. self.assertIdentical(e.baz.defaultUri, None)
  416. self.assertEqual(e.toXml(), "<bar:foo xmlns:bar='testns'><baz/></bar:foo>")
  417. def test_prefixesReuse(self):
  418. """
  419. Test that prefixes passed to serialization are not modified.
  420. This test makes sure that passing a dictionary of prefixes repeatedly
  421. to C{toXml} of elements does not cause serialization errors. A
  422. previous implementation changed the passed in dictionary internally,
  423. causing havoc later on.
  424. """
  425. prefixes = {'testns': 'foo'}
  426. # test passing of dictionary
  427. s = domish.SerializerClass(prefixes=prefixes)
  428. self.assertNotIdentical(prefixes, s.prefixes)
  429. # test proper serialization on prefixes reuse
  430. e = domish.Element(('testns2', 'foo'),
  431. localPrefixes={'quux': 'testns2'})
  432. self.assertEqual("<quux:foo xmlns:quux='testns2'/>",
  433. e.toXml(prefixes=prefixes))
  434. e = domish.Element(('testns2', 'foo'))
  435. self.assertEqual("<foo xmlns='testns2'/>",
  436. e.toXml(prefixes=prefixes))
  437. def testRawXMLSerialization(self):
  438. e = domish.Element((None, "foo"))
  439. e.addRawXml("<abc123>")
  440. # The testcase below should NOT generate valid XML -- that's
  441. # the whole point of using the raw XML call -- it's the callers
  442. # responsibility to ensure that the data inserted is valid
  443. self.assertEqual(e.toXml(), "<foo><abc123></foo>")
  444. def testRawXMLWithUnicodeSerialization(self):
  445. e = domish.Element((None, "foo"))
  446. e.addRawXml(u"<degree>\u00B0</degree>")
  447. self.assertEqual(e.toXml(), u"<foo><degree>\u00B0</degree></foo>")
  448. def testUnicodeSerialization(self):
  449. e = domish.Element((None, "foo"))
  450. e["test"] = u"my value\u0221e"
  451. e.addContent(u"A degree symbol...\u00B0")
  452. self.assertEqual(e.toXml(),
  453. u"<foo test='my value\u0221e'>A degree symbol...\u00B0</foo>")