1 | # regression test for SAX 2.0 -*- coding: utf-8 -*-
|
---|
2 | # $Id$
|
---|
3 |
|
---|
4 | from xml.sax import make_parser, ContentHandler, \
|
---|
5 | SAXException, SAXReaderNotAvailable, SAXParseException
|
---|
6 | try:
|
---|
7 | make_parser()
|
---|
8 | except SAXReaderNotAvailable:
|
---|
9 | # don't try to test this module if we cannot create a parser
|
---|
10 | raise ImportError("no XML parsers available")
|
---|
11 | from xml.sax.saxutils import XMLGenerator, escape, unescape, quoteattr, \
|
---|
12 | XMLFilterBase
|
---|
13 | from xml.sax.expatreader import create_parser
|
---|
14 | from xml.sax.handler import feature_namespaces
|
---|
15 | from xml.sax.xmlreader import InputSource, AttributesImpl, AttributesNSImpl
|
---|
16 | from cStringIO import StringIO
|
---|
17 | import io
|
---|
18 | import os.path
|
---|
19 | import shutil
|
---|
20 | import test.test_support as support
|
---|
21 | from test.test_support import findfile, run_unittest
|
---|
22 | import unittest
|
---|
23 |
|
---|
24 | TEST_XMLFILE = findfile("test.xml", subdir="xmltestdata")
|
---|
25 | TEST_XMLFILE_OUT = findfile("test.xml.out", subdir="xmltestdata")
|
---|
26 |
|
---|
27 | supports_unicode_filenames = True
|
---|
28 | if not os.path.supports_unicode_filenames:
|
---|
29 | try:
|
---|
30 | support.TESTFN_UNICODE.encode(support.TESTFN_ENCODING)
|
---|
31 | except (AttributeError, UnicodeError, TypeError):
|
---|
32 | # Either the file system encoding is None, or the file name
|
---|
33 | # cannot be encoded in the file system encoding.
|
---|
34 | supports_unicode_filenames = False
|
---|
35 | requires_unicode_filenames = unittest.skipUnless(
|
---|
36 | supports_unicode_filenames,
|
---|
37 | 'Requires unicode filenames support')
|
---|
38 |
|
---|
39 | ns_uri = "http://www.python.org/xml-ns/saxtest/"
|
---|
40 |
|
---|
41 | class XmlTestBase(unittest.TestCase):
|
---|
42 | def verify_empty_attrs(self, attrs):
|
---|
43 | self.assertRaises(KeyError, attrs.getValue, "attr")
|
---|
44 | self.assertRaises(KeyError, attrs.getValueByQName, "attr")
|
---|
45 | self.assertRaises(KeyError, attrs.getNameByQName, "attr")
|
---|
46 | self.assertRaises(KeyError, attrs.getQNameByName, "attr")
|
---|
47 | self.assertRaises(KeyError, attrs.__getitem__, "attr")
|
---|
48 | self.assertEqual(attrs.getLength(), 0)
|
---|
49 | self.assertEqual(attrs.getNames(), [])
|
---|
50 | self.assertEqual(attrs.getQNames(), [])
|
---|
51 | self.assertEqual(len(attrs), 0)
|
---|
52 | self.assertFalse(attrs.has_key("attr"))
|
---|
53 | self.assertEqual(attrs.keys(), [])
|
---|
54 | self.assertEqual(attrs.get("attrs"), None)
|
---|
55 | self.assertEqual(attrs.get("attrs", 25), 25)
|
---|
56 | self.assertEqual(attrs.items(), [])
|
---|
57 | self.assertEqual(attrs.values(), [])
|
---|
58 |
|
---|
59 | def verify_empty_nsattrs(self, attrs):
|
---|
60 | self.assertRaises(KeyError, attrs.getValue, (ns_uri, "attr"))
|
---|
61 | self.assertRaises(KeyError, attrs.getValueByQName, "ns:attr")
|
---|
62 | self.assertRaises(KeyError, attrs.getNameByQName, "ns:attr")
|
---|
63 | self.assertRaises(KeyError, attrs.getQNameByName, (ns_uri, "attr"))
|
---|
64 | self.assertRaises(KeyError, attrs.__getitem__, (ns_uri, "attr"))
|
---|
65 | self.assertEqual(attrs.getLength(), 0)
|
---|
66 | self.assertEqual(attrs.getNames(), [])
|
---|
67 | self.assertEqual(attrs.getQNames(), [])
|
---|
68 | self.assertEqual(len(attrs), 0)
|
---|
69 | self.assertFalse(attrs.has_key((ns_uri, "attr")))
|
---|
70 | self.assertEqual(attrs.keys(), [])
|
---|
71 | self.assertEqual(attrs.get((ns_uri, "attr")), None)
|
---|
72 | self.assertEqual(attrs.get((ns_uri, "attr"), 25), 25)
|
---|
73 | self.assertEqual(attrs.items(), [])
|
---|
74 | self.assertEqual(attrs.values(), [])
|
---|
75 |
|
---|
76 | def verify_attrs_wattr(self, attrs):
|
---|
77 | self.assertEqual(attrs.getLength(), 1)
|
---|
78 | self.assertEqual(attrs.getNames(), ["attr"])
|
---|
79 | self.assertEqual(attrs.getQNames(), ["attr"])
|
---|
80 | self.assertEqual(len(attrs), 1)
|
---|
81 | self.assertTrue(attrs.has_key("attr"))
|
---|
82 | self.assertEqual(attrs.keys(), ["attr"])
|
---|
83 | self.assertEqual(attrs.get("attr"), "val")
|
---|
84 | self.assertEqual(attrs.get("attr", 25), "val")
|
---|
85 | self.assertEqual(attrs.items(), [("attr", "val")])
|
---|
86 | self.assertEqual(attrs.values(), ["val"])
|
---|
87 | self.assertEqual(attrs.getValue("attr"), "val")
|
---|
88 | self.assertEqual(attrs.getValueByQName("attr"), "val")
|
---|
89 | self.assertEqual(attrs.getNameByQName("attr"), "attr")
|
---|
90 | self.assertEqual(attrs["attr"], "val")
|
---|
91 | self.assertEqual(attrs.getQNameByName("attr"), "attr")
|
---|
92 |
|
---|
93 | class MakeParserTest(unittest.TestCase):
|
---|
94 | def test_make_parser2(self):
|
---|
95 | # Creating parsers several times in a row should succeed.
|
---|
96 | # Testing this because there have been failures of this kind
|
---|
97 | # before.
|
---|
98 | from xml.sax import make_parser
|
---|
99 | p = make_parser()
|
---|
100 | from xml.sax import make_parser
|
---|
101 | p = make_parser()
|
---|
102 | from xml.sax import make_parser
|
---|
103 | p = make_parser()
|
---|
104 | from xml.sax import make_parser
|
---|
105 | p = make_parser()
|
---|
106 | from xml.sax import make_parser
|
---|
107 | p = make_parser()
|
---|
108 | from xml.sax import make_parser
|
---|
109 | p = make_parser()
|
---|
110 |
|
---|
111 |
|
---|
112 | # ===========================================================================
|
---|
113 | #
|
---|
114 | # saxutils tests
|
---|
115 | #
|
---|
116 | # ===========================================================================
|
---|
117 |
|
---|
118 | class SaxutilsTest(unittest.TestCase):
|
---|
119 | # ===== escape
|
---|
120 | def test_escape_basic(self):
|
---|
121 | self.assertEqual(escape("Donald Duck & Co"), "Donald Duck & Co")
|
---|
122 |
|
---|
123 | def test_escape_all(self):
|
---|
124 | self.assertEqual(escape("<Donald Duck & Co>"),
|
---|
125 | "<Donald Duck & Co>")
|
---|
126 |
|
---|
127 | def test_escape_extra(self):
|
---|
128 | self.assertEqual(escape("Hei pÃ¥ deg", {"Ã¥" : "å"}),
|
---|
129 | "Hei på deg")
|
---|
130 |
|
---|
131 | # ===== unescape
|
---|
132 | def test_unescape_basic(self):
|
---|
133 | self.assertEqual(unescape("Donald Duck & Co"), "Donald Duck & Co")
|
---|
134 |
|
---|
135 | def test_unescape_all(self):
|
---|
136 | self.assertEqual(unescape("<Donald Duck & Co>"),
|
---|
137 | "<Donald Duck & Co>")
|
---|
138 |
|
---|
139 | def test_unescape_extra(self):
|
---|
140 | self.assertEqual(unescape("Hei pÃ¥ deg", {"Ã¥" : "å"}),
|
---|
141 | "Hei på deg")
|
---|
142 |
|
---|
143 | def test_unescape_amp_extra(self):
|
---|
144 | self.assertEqual(unescape("&foo;", {"&foo;": "splat"}), "&foo;")
|
---|
145 |
|
---|
146 | # ===== quoteattr
|
---|
147 | def test_quoteattr_basic(self):
|
---|
148 | self.assertEqual(quoteattr("Donald Duck & Co"),
|
---|
149 | '"Donald Duck & Co"')
|
---|
150 |
|
---|
151 | def test_single_quoteattr(self):
|
---|
152 | self.assertEqual(quoteattr('Includes "double" quotes'),
|
---|
153 | '\'Includes "double" quotes\'')
|
---|
154 |
|
---|
155 | def test_double_quoteattr(self):
|
---|
156 | self.assertEqual(quoteattr("Includes 'single' quotes"),
|
---|
157 | "\"Includes 'single' quotes\"")
|
---|
158 |
|
---|
159 | def test_single_double_quoteattr(self):
|
---|
160 | self.assertEqual(quoteattr("Includes 'single' and \"double\" quotes"),
|
---|
161 | "\"Includes 'single' and "double" quotes\"")
|
---|
162 |
|
---|
163 | # ===== make_parser
|
---|
164 | def test_make_parser(self):
|
---|
165 | # Creating a parser should succeed - it should fall back
|
---|
166 | # to the expatreader
|
---|
167 | p = make_parser(['xml.parsers.no_such_parser'])
|
---|
168 |
|
---|
169 |
|
---|
170 | # ===== XMLGenerator
|
---|
171 |
|
---|
172 | start = '<?xml version="1.0" encoding="iso-8859-1"?>\n'
|
---|
173 |
|
---|
174 | class XmlgenTest:
|
---|
175 | def test_xmlgen_basic(self):
|
---|
176 | result = self.ioclass()
|
---|
177 | gen = XMLGenerator(result)
|
---|
178 | gen.startDocument()
|
---|
179 | gen.startElement("doc", {})
|
---|
180 | gen.endElement("doc")
|
---|
181 | gen.endDocument()
|
---|
182 |
|
---|
183 | self.assertEqual(result.getvalue(), start + "<doc></doc>")
|
---|
184 |
|
---|
185 | def test_xmlgen_content(self):
|
---|
186 | result = self.ioclass()
|
---|
187 | gen = XMLGenerator(result)
|
---|
188 |
|
---|
189 | gen.startDocument()
|
---|
190 | gen.startElement("doc", {})
|
---|
191 | gen.characters("huhei")
|
---|
192 | gen.endElement("doc")
|
---|
193 | gen.endDocument()
|
---|
194 |
|
---|
195 | self.assertEqual(result.getvalue(), start + "<doc>huhei</doc>")
|
---|
196 |
|
---|
197 | def test_xmlgen_pi(self):
|
---|
198 | result = self.ioclass()
|
---|
199 | gen = XMLGenerator(result)
|
---|
200 |
|
---|
201 | gen.startDocument()
|
---|
202 | gen.processingInstruction("test", "data")
|
---|
203 | gen.startElement("doc", {})
|
---|
204 | gen.endElement("doc")
|
---|
205 | gen.endDocument()
|
---|
206 |
|
---|
207 | self.assertEqual(result.getvalue(), start + "<?test data?><doc></doc>")
|
---|
208 |
|
---|
209 | def test_xmlgen_content_escape(self):
|
---|
210 | result = self.ioclass()
|
---|
211 | gen = XMLGenerator(result)
|
---|
212 |
|
---|
213 | gen.startDocument()
|
---|
214 | gen.startElement("doc", {})
|
---|
215 | gen.characters("<huhei&")
|
---|
216 | gen.endElement("doc")
|
---|
217 | gen.endDocument()
|
---|
218 |
|
---|
219 | self.assertEqual(result.getvalue(),
|
---|
220 | start + "<doc><huhei&</doc>")
|
---|
221 |
|
---|
222 | def test_xmlgen_attr_escape(self):
|
---|
223 | result = self.ioclass()
|
---|
224 | gen = XMLGenerator(result)
|
---|
225 |
|
---|
226 | gen.startDocument()
|
---|
227 | gen.startElement("doc", {"a": '"'})
|
---|
228 | gen.startElement("e", {"a": "'"})
|
---|
229 | gen.endElement("e")
|
---|
230 | gen.startElement("e", {"a": "'\""})
|
---|
231 | gen.endElement("e")
|
---|
232 | gen.startElement("e", {"a": "\n\r\t"})
|
---|
233 | gen.endElement("e")
|
---|
234 | gen.endElement("doc")
|
---|
235 | gen.endDocument()
|
---|
236 |
|
---|
237 | self.assertEqual(result.getvalue(), start +
|
---|
238 | ("<doc a='\"'><e a=\"'\"></e>"
|
---|
239 | "<e a=\"'"\"></e>"
|
---|
240 | "<e a=\" 	\"></e></doc>"))
|
---|
241 |
|
---|
242 | def test_xmlgen_encoding(self):
|
---|
243 | encodings = ('iso-8859-15', 'utf-8',
|
---|
244 | 'utf-16be', 'utf-16le',
|
---|
245 | 'utf-32be', 'utf-32le')
|
---|
246 | for encoding in encodings:
|
---|
247 | result = self.ioclass()
|
---|
248 | gen = XMLGenerator(result, encoding=encoding)
|
---|
249 |
|
---|
250 | gen.startDocument()
|
---|
251 | gen.startElement("doc", {"a": u'\u20ac'})
|
---|
252 | gen.characters(u"\u20ac")
|
---|
253 | gen.endElement("doc")
|
---|
254 | gen.endDocument()
|
---|
255 |
|
---|
256 | self.assertEqual(result.getvalue(), (
|
---|
257 | u'<?xml version="1.0" encoding="%s"?>\n'
|
---|
258 | u'<doc a="\u20ac">\u20ac</doc>' % encoding
|
---|
259 | ).encode(encoding, 'xmlcharrefreplace'))
|
---|
260 |
|
---|
261 | def test_xmlgen_unencodable(self):
|
---|
262 | result = self.ioclass()
|
---|
263 | gen = XMLGenerator(result, encoding='ascii')
|
---|
264 |
|
---|
265 | gen.startDocument()
|
---|
266 | gen.startElement("doc", {"a": u'\u20ac'})
|
---|
267 | gen.characters(u"\u20ac")
|
---|
268 | gen.endElement("doc")
|
---|
269 | gen.endDocument()
|
---|
270 |
|
---|
271 | self.assertEqual(result.getvalue(),
|
---|
272 | '<?xml version="1.0" encoding="ascii"?>\n'
|
---|
273 | '<doc a="€">€</doc>')
|
---|
274 |
|
---|
275 | def test_xmlgen_ignorable(self):
|
---|
276 | result = self.ioclass()
|
---|
277 | gen = XMLGenerator(result)
|
---|
278 |
|
---|
279 | gen.startDocument()
|
---|
280 | gen.startElement("doc", {})
|
---|
281 | gen.ignorableWhitespace(" ")
|
---|
282 | gen.endElement("doc")
|
---|
283 | gen.endDocument()
|
---|
284 |
|
---|
285 | self.assertEqual(result.getvalue(), start + "<doc> </doc>")
|
---|
286 |
|
---|
287 | def test_xmlgen_encoding_bytes(self):
|
---|
288 | encodings = ('iso-8859-15', 'utf-8',
|
---|
289 | 'utf-16be', 'utf-16le',
|
---|
290 | 'utf-32be', 'utf-32le')
|
---|
291 | for encoding in encodings:
|
---|
292 | result = self.ioclass()
|
---|
293 | gen = XMLGenerator(result, encoding=encoding)
|
---|
294 |
|
---|
295 | gen.startDocument()
|
---|
296 | gen.startElement("doc", {"a": u'\u20ac'})
|
---|
297 | gen.characters(u"\u20ac".encode(encoding))
|
---|
298 | gen.ignorableWhitespace(" ".encode(encoding))
|
---|
299 | gen.endElement("doc")
|
---|
300 | gen.endDocument()
|
---|
301 |
|
---|
302 | self.assertEqual(result.getvalue(), (
|
---|
303 | u'<?xml version="1.0" encoding="%s"?>\n'
|
---|
304 | u'<doc a="\u20ac">\u20ac </doc>' % encoding
|
---|
305 | ).encode(encoding, 'xmlcharrefreplace'))
|
---|
306 |
|
---|
307 | def test_xmlgen_ns(self):
|
---|
308 | result = self.ioclass()
|
---|
309 | gen = XMLGenerator(result)
|
---|
310 |
|
---|
311 | gen.startDocument()
|
---|
312 | gen.startPrefixMapping("ns1", ns_uri)
|
---|
313 | gen.startElementNS((ns_uri, "doc"), "ns1:doc", {})
|
---|
314 | # add an unqualified name
|
---|
315 | gen.startElementNS((None, "udoc"), None, {})
|
---|
316 | gen.endElementNS((None, "udoc"), None)
|
---|
317 | gen.endElementNS((ns_uri, "doc"), "ns1:doc")
|
---|
318 | gen.endPrefixMapping("ns1")
|
---|
319 | gen.endDocument()
|
---|
320 |
|
---|
321 | self.assertEqual(result.getvalue(), start + \
|
---|
322 | ('<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
|
---|
323 | ns_uri))
|
---|
324 |
|
---|
325 | def test_1463026_1(self):
|
---|
326 | result = self.ioclass()
|
---|
327 | gen = XMLGenerator(result)
|
---|
328 |
|
---|
329 | gen.startDocument()
|
---|
330 | gen.startElementNS((None, 'a'), 'a', {(None, 'b'):'c'})
|
---|
331 | gen.endElementNS((None, 'a'), 'a')
|
---|
332 | gen.endDocument()
|
---|
333 |
|
---|
334 | self.assertEqual(result.getvalue(), start+'<a b="c"></a>')
|
---|
335 |
|
---|
336 | def test_1463026_2(self):
|
---|
337 | result = self.ioclass()
|
---|
338 | gen = XMLGenerator(result)
|
---|
339 |
|
---|
340 | gen.startDocument()
|
---|
341 | gen.startPrefixMapping(None, 'qux')
|
---|
342 | gen.startElementNS(('qux', 'a'), 'a', {})
|
---|
343 | gen.endElementNS(('qux', 'a'), 'a')
|
---|
344 | gen.endPrefixMapping(None)
|
---|
345 | gen.endDocument()
|
---|
346 |
|
---|
347 | self.assertEqual(result.getvalue(), start+'<a xmlns="qux"></a>')
|
---|
348 |
|
---|
349 | def test_1463026_3(self):
|
---|
350 | result = self.ioclass()
|
---|
351 | gen = XMLGenerator(result)
|
---|
352 |
|
---|
353 | gen.startDocument()
|
---|
354 | gen.startPrefixMapping('my', 'qux')
|
---|
355 | gen.startElementNS(('qux', 'a'), 'a', {(None, 'b'):'c'})
|
---|
356 | gen.endElementNS(('qux', 'a'), 'a')
|
---|
357 | gen.endPrefixMapping('my')
|
---|
358 | gen.endDocument()
|
---|
359 |
|
---|
360 | self.assertEqual(result.getvalue(),
|
---|
361 | start+'<my:a xmlns:my="qux" b="c"></my:a>')
|
---|
362 |
|
---|
363 | def test_5027_1(self):
|
---|
364 | # The xml prefix (as in xml:lang below) is reserved and bound by
|
---|
365 | # definition to http://www.w3.org/XML/1998/namespace. XMLGenerator had
|
---|
366 | # a bug whereby a KeyError is raised because this namespace is missing
|
---|
367 | # from a dictionary.
|
---|
368 | #
|
---|
369 | # This test demonstrates the bug by parsing a document.
|
---|
370 | test_xml = StringIO(
|
---|
371 | '<?xml version="1.0"?>'
|
---|
372 | '<a:g1 xmlns:a="http://example.com/ns">'
|
---|
373 | '<a:g2 xml:lang="en">Hello</a:g2>'
|
---|
374 | '</a:g1>')
|
---|
375 |
|
---|
376 | parser = make_parser()
|
---|
377 | parser.setFeature(feature_namespaces, True)
|
---|
378 | result = self.ioclass()
|
---|
379 | gen = XMLGenerator(result)
|
---|
380 | parser.setContentHandler(gen)
|
---|
381 | parser.parse(test_xml)
|
---|
382 |
|
---|
383 | self.assertEqual(result.getvalue(),
|
---|
384 | start + (
|
---|
385 | '<a:g1 xmlns:a="http://example.com/ns">'
|
---|
386 | '<a:g2 xml:lang="en">Hello</a:g2>'
|
---|
387 | '</a:g1>'))
|
---|
388 |
|
---|
389 | def test_5027_2(self):
|
---|
390 | # The xml prefix (as in xml:lang below) is reserved and bound by
|
---|
391 | # definition to http://www.w3.org/XML/1998/namespace. XMLGenerator had
|
---|
392 | # a bug whereby a KeyError is raised because this namespace is missing
|
---|
393 | # from a dictionary.
|
---|
394 | #
|
---|
395 | # This test demonstrates the bug by direct manipulation of the
|
---|
396 | # XMLGenerator.
|
---|
397 | result = self.ioclass()
|
---|
398 | gen = XMLGenerator(result)
|
---|
399 |
|
---|
400 | gen.startDocument()
|
---|
401 | gen.startPrefixMapping('a', 'http://example.com/ns')
|
---|
402 | gen.startElementNS(('http://example.com/ns', 'g1'), 'g1', {})
|
---|
403 | lang_attr = {('http://www.w3.org/XML/1998/namespace', 'lang'): 'en'}
|
---|
404 | gen.startElementNS(('http://example.com/ns', 'g2'), 'g2', lang_attr)
|
---|
405 | gen.characters('Hello')
|
---|
406 | gen.endElementNS(('http://example.com/ns', 'g2'), 'g2')
|
---|
407 | gen.endElementNS(('http://example.com/ns', 'g1'), 'g1')
|
---|
408 | gen.endPrefixMapping('a')
|
---|
409 | gen.endDocument()
|
---|
410 |
|
---|
411 | self.assertEqual(result.getvalue(),
|
---|
412 | start + (
|
---|
413 | '<a:g1 xmlns:a="http://example.com/ns">'
|
---|
414 | '<a:g2 xml:lang="en">Hello</a:g2>'
|
---|
415 | '</a:g1>'))
|
---|
416 |
|
---|
417 | def test_no_close_file(self):
|
---|
418 | result = self.ioclass()
|
---|
419 | def func(out):
|
---|
420 | gen = XMLGenerator(out)
|
---|
421 | gen.startDocument()
|
---|
422 | gen.startElement("doc", {})
|
---|
423 | func(result)
|
---|
424 | self.assertFalse(result.closed)
|
---|
425 |
|
---|
426 | def test_xmlgen_fragment(self):
|
---|
427 | result = self.ioclass()
|
---|
428 | gen = XMLGenerator(result)
|
---|
429 |
|
---|
430 | # Don't call gen.startDocument()
|
---|
431 | gen.startElement("foo", {"a": "1.0"})
|
---|
432 | gen.characters("Hello")
|
---|
433 | gen.endElement("foo")
|
---|
434 | gen.startElement("bar", {"b": "2.0"})
|
---|
435 | gen.endElement("bar")
|
---|
436 | # Don't call gen.endDocument()
|
---|
437 |
|
---|
438 | self.assertEqual(result.getvalue(),
|
---|
439 | '<foo a="1.0">Hello</foo><bar b="2.0"></bar>')
|
---|
440 |
|
---|
441 | class StringXmlgenTest(XmlgenTest, unittest.TestCase):
|
---|
442 | ioclass = StringIO
|
---|
443 |
|
---|
444 | class BytesIOXmlgenTest(XmlgenTest, unittest.TestCase):
|
---|
445 | ioclass = io.BytesIO
|
---|
446 |
|
---|
447 | class WriterXmlgenTest(XmlgenTest, unittest.TestCase):
|
---|
448 | class ioclass(list):
|
---|
449 | write = list.append
|
---|
450 | closed = False
|
---|
451 |
|
---|
452 | def getvalue(self):
|
---|
453 | return b''.join(self)
|
---|
454 |
|
---|
455 |
|
---|
456 | class XMLFilterBaseTest(unittest.TestCase):
|
---|
457 | def test_filter_basic(self):
|
---|
458 | result = StringIO()
|
---|
459 | gen = XMLGenerator(result)
|
---|
460 | filter = XMLFilterBase()
|
---|
461 | filter.setContentHandler(gen)
|
---|
462 |
|
---|
463 | filter.startDocument()
|
---|
464 | filter.startElement("doc", {})
|
---|
465 | filter.characters("content")
|
---|
466 | filter.ignorableWhitespace(" ")
|
---|
467 | filter.endElement("doc")
|
---|
468 | filter.endDocument()
|
---|
469 |
|
---|
470 | self.assertEqual(result.getvalue(), start + "<doc>content </doc>")
|
---|
471 |
|
---|
472 | # ===========================================================================
|
---|
473 | #
|
---|
474 | # expatreader tests
|
---|
475 | #
|
---|
476 | # ===========================================================================
|
---|
477 |
|
---|
478 | xml_test_out = open(TEST_XMLFILE_OUT).read()
|
---|
479 |
|
---|
480 | class ExpatReaderTest(XmlTestBase):
|
---|
481 |
|
---|
482 | # ===== XMLReader support
|
---|
483 |
|
---|
484 | def test_expat_file(self):
|
---|
485 | parser = create_parser()
|
---|
486 | result = StringIO()
|
---|
487 | xmlgen = XMLGenerator(result)
|
---|
488 |
|
---|
489 | parser.setContentHandler(xmlgen)
|
---|
490 | parser.parse(open(TEST_XMLFILE))
|
---|
491 |
|
---|
492 | self.assertEqual(result.getvalue(), xml_test_out)
|
---|
493 |
|
---|
494 | @requires_unicode_filenames
|
---|
495 | def test_expat_file_unicode(self):
|
---|
496 | fname = support.TESTFN_UNICODE
|
---|
497 | shutil.copyfile(TEST_XMLFILE, fname)
|
---|
498 | self.addCleanup(support.unlink, fname)
|
---|
499 |
|
---|
500 | parser = create_parser()
|
---|
501 | result = StringIO()
|
---|
502 | xmlgen = XMLGenerator(result)
|
---|
503 |
|
---|
504 | parser.setContentHandler(xmlgen)
|
---|
505 | parser.parse(open(fname))
|
---|
506 |
|
---|
507 | self.assertEqual(result.getvalue(), xml_test_out)
|
---|
508 |
|
---|
509 | # ===== DTDHandler support
|
---|
510 |
|
---|
511 | class TestDTDHandler:
|
---|
512 |
|
---|
513 | def __init__(self):
|
---|
514 | self._notations = []
|
---|
515 | self._entities = []
|
---|
516 |
|
---|
517 | def notationDecl(self, name, publicId, systemId):
|
---|
518 | self._notations.append((name, publicId, systemId))
|
---|
519 |
|
---|
520 | def unparsedEntityDecl(self, name, publicId, systemId, ndata):
|
---|
521 | self._entities.append((name, publicId, systemId, ndata))
|
---|
522 |
|
---|
523 | def test_expat_dtdhandler(self):
|
---|
524 | parser = create_parser()
|
---|
525 | handler = self.TestDTDHandler()
|
---|
526 | parser.setDTDHandler(handler)
|
---|
527 |
|
---|
528 | parser.feed('<!DOCTYPE doc [\n')
|
---|
529 | parser.feed(' <!ENTITY img SYSTEM "expat.gif" NDATA GIF>\n')
|
---|
530 | parser.feed(' <!NOTATION GIF PUBLIC "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN">\n')
|
---|
531 | parser.feed(']>\n')
|
---|
532 | parser.feed('<doc></doc>')
|
---|
533 | parser.close()
|
---|
534 |
|
---|
535 | self.assertEqual(handler._notations,
|
---|
536 | [("GIF", "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN", None)])
|
---|
537 | self.assertEqual(handler._entities, [("img", None, "expat.gif", "GIF")])
|
---|
538 |
|
---|
539 | # ===== EntityResolver support
|
---|
540 |
|
---|
541 | class TestEntityResolver:
|
---|
542 |
|
---|
543 | def resolveEntity(self, publicId, systemId):
|
---|
544 | inpsrc = InputSource()
|
---|
545 | inpsrc.setByteStream(StringIO("<entity/>"))
|
---|
546 | return inpsrc
|
---|
547 |
|
---|
548 | def test_expat_entityresolver(self):
|
---|
549 | parser = create_parser()
|
---|
550 | parser.setEntityResolver(self.TestEntityResolver())
|
---|
551 | result = StringIO()
|
---|
552 | parser.setContentHandler(XMLGenerator(result))
|
---|
553 |
|
---|
554 | parser.feed('<!DOCTYPE doc [\n')
|
---|
555 | parser.feed(' <!ENTITY test SYSTEM "whatever">\n')
|
---|
556 | parser.feed(']>\n')
|
---|
557 | parser.feed('<doc>&test;</doc>')
|
---|
558 | parser.close()
|
---|
559 |
|
---|
560 | self.assertEqual(result.getvalue(), start +
|
---|
561 | "<doc><entity></entity></doc>")
|
---|
562 |
|
---|
563 | # ===== Attributes support
|
---|
564 |
|
---|
565 | class AttrGatherer(ContentHandler):
|
---|
566 |
|
---|
567 | def startElement(self, name, attrs):
|
---|
568 | self._attrs = attrs
|
---|
569 |
|
---|
570 | def startElementNS(self, name, qname, attrs):
|
---|
571 | self._attrs = attrs
|
---|
572 |
|
---|
573 | def test_expat_attrs_empty(self):
|
---|
574 | parser = create_parser()
|
---|
575 | gather = self.AttrGatherer()
|
---|
576 | parser.setContentHandler(gather)
|
---|
577 |
|
---|
578 | parser.feed("<doc/>")
|
---|
579 | parser.close()
|
---|
580 |
|
---|
581 | self.verify_empty_attrs(gather._attrs)
|
---|
582 |
|
---|
583 | def test_expat_attrs_wattr(self):
|
---|
584 | parser = create_parser()
|
---|
585 | gather = self.AttrGatherer()
|
---|
586 | parser.setContentHandler(gather)
|
---|
587 |
|
---|
588 | parser.feed("<doc attr='val'/>")
|
---|
589 | parser.close()
|
---|
590 |
|
---|
591 | self.verify_attrs_wattr(gather._attrs)
|
---|
592 |
|
---|
593 | def test_expat_nsattrs_empty(self):
|
---|
594 | parser = create_parser(1)
|
---|
595 | gather = self.AttrGatherer()
|
---|
596 | parser.setContentHandler(gather)
|
---|
597 |
|
---|
598 | parser.feed("<doc/>")
|
---|
599 | parser.close()
|
---|
600 |
|
---|
601 | self.verify_empty_nsattrs(gather._attrs)
|
---|
602 |
|
---|
603 | def test_expat_nsattrs_wattr(self):
|
---|
604 | parser = create_parser(1)
|
---|
605 | gather = self.AttrGatherer()
|
---|
606 | parser.setContentHandler(gather)
|
---|
607 |
|
---|
608 | parser.feed("<doc xmlns:ns='%s' ns:attr='val'/>" % ns_uri)
|
---|
609 | parser.close()
|
---|
610 |
|
---|
611 | attrs = gather._attrs
|
---|
612 |
|
---|
613 | self.assertEqual(attrs.getLength(), 1)
|
---|
614 | self.assertEqual(attrs.getNames(), [(ns_uri, "attr")])
|
---|
615 | self.assertTrue((attrs.getQNames() == [] or
|
---|
616 | attrs.getQNames() == ["ns:attr"]))
|
---|
617 | self.assertEqual(len(attrs), 1)
|
---|
618 | self.assertTrue(attrs.has_key((ns_uri, "attr")))
|
---|
619 | self.assertEqual(attrs.get((ns_uri, "attr")), "val")
|
---|
620 | self.assertEqual(attrs.get((ns_uri, "attr"), 25), "val")
|
---|
621 | self.assertEqual(attrs.items(), [((ns_uri, "attr"), "val")])
|
---|
622 | self.assertEqual(attrs.values(), ["val"])
|
---|
623 | self.assertEqual(attrs.getValue((ns_uri, "attr")), "val")
|
---|
624 | self.assertEqual(attrs[(ns_uri, "attr")], "val")
|
---|
625 |
|
---|
626 | # ===== InputSource support
|
---|
627 |
|
---|
628 | def test_expat_inpsource_filename(self):
|
---|
629 | parser = create_parser()
|
---|
630 | result = StringIO()
|
---|
631 | xmlgen = XMLGenerator(result)
|
---|
632 |
|
---|
633 | parser.setContentHandler(xmlgen)
|
---|
634 | parser.parse(TEST_XMLFILE)
|
---|
635 |
|
---|
636 | self.assertEqual(result.getvalue(), xml_test_out)
|
---|
637 |
|
---|
638 | def test_expat_inpsource_sysid(self):
|
---|
639 | parser = create_parser()
|
---|
640 | result = StringIO()
|
---|
641 | xmlgen = XMLGenerator(result)
|
---|
642 |
|
---|
643 | parser.setContentHandler(xmlgen)
|
---|
644 | parser.parse(InputSource(TEST_XMLFILE))
|
---|
645 |
|
---|
646 | self.assertEqual(result.getvalue(), xml_test_out)
|
---|
647 |
|
---|
648 | @requires_unicode_filenames
|
---|
649 | def test_expat_inpsource_sysid_unicode(self):
|
---|
650 | fname = support.TESTFN_UNICODE
|
---|
651 | shutil.copyfile(TEST_XMLFILE, fname)
|
---|
652 | self.addCleanup(support.unlink, fname)
|
---|
653 |
|
---|
654 | parser = create_parser()
|
---|
655 | result = StringIO()
|
---|
656 | xmlgen = XMLGenerator(result)
|
---|
657 |
|
---|
658 | parser.setContentHandler(xmlgen)
|
---|
659 | parser.parse(InputSource(fname))
|
---|
660 |
|
---|
661 | self.assertEqual(result.getvalue(), xml_test_out)
|
---|
662 |
|
---|
663 | def test_expat_inpsource_stream(self):
|
---|
664 | parser = create_parser()
|
---|
665 | result = StringIO()
|
---|
666 | xmlgen = XMLGenerator(result)
|
---|
667 |
|
---|
668 | parser.setContentHandler(xmlgen)
|
---|
669 | inpsrc = InputSource()
|
---|
670 | inpsrc.setByteStream(open(TEST_XMLFILE))
|
---|
671 | parser.parse(inpsrc)
|
---|
672 |
|
---|
673 | self.assertEqual(result.getvalue(), xml_test_out)
|
---|
674 |
|
---|
675 | # ===== IncrementalParser support
|
---|
676 |
|
---|
677 | def test_expat_incremental(self):
|
---|
678 | result = StringIO()
|
---|
679 | xmlgen = XMLGenerator(result)
|
---|
680 | parser = create_parser()
|
---|
681 | parser.setContentHandler(xmlgen)
|
---|
682 |
|
---|
683 | parser.feed("<doc>")
|
---|
684 | parser.feed("</doc>")
|
---|
685 | parser.close()
|
---|
686 |
|
---|
687 | self.assertEqual(result.getvalue(), start + "<doc></doc>")
|
---|
688 |
|
---|
689 | def test_expat_incremental_reset(self):
|
---|
690 | result = StringIO()
|
---|
691 | xmlgen = XMLGenerator(result)
|
---|
692 | parser = create_parser()
|
---|
693 | parser.setContentHandler(xmlgen)
|
---|
694 |
|
---|
695 | parser.feed("<doc>")
|
---|
696 | parser.feed("text")
|
---|
697 |
|
---|
698 | result = StringIO()
|
---|
699 | xmlgen = XMLGenerator(result)
|
---|
700 | parser.setContentHandler(xmlgen)
|
---|
701 | parser.reset()
|
---|
702 |
|
---|
703 | parser.feed("<doc>")
|
---|
704 | parser.feed("text")
|
---|
705 | parser.feed("</doc>")
|
---|
706 | parser.close()
|
---|
707 |
|
---|
708 | self.assertEqual(result.getvalue(), start + "<doc>text</doc>")
|
---|
709 |
|
---|
710 | # ===== Locator support
|
---|
711 |
|
---|
712 | def test_expat_locator_noinfo(self):
|
---|
713 | result = StringIO()
|
---|
714 | xmlgen = XMLGenerator(result)
|
---|
715 | parser = create_parser()
|
---|
716 | parser.setContentHandler(xmlgen)
|
---|
717 |
|
---|
718 | parser.feed("<doc>")
|
---|
719 | parser.feed("</doc>")
|
---|
720 | parser.close()
|
---|
721 |
|
---|
722 | self.assertEqual(parser.getSystemId(), None)
|
---|
723 | self.assertEqual(parser.getPublicId(), None)
|
---|
724 | self.assertEqual(parser.getLineNumber(), 1)
|
---|
725 |
|
---|
726 | def test_expat_locator_withinfo(self):
|
---|
727 | result = StringIO()
|
---|
728 | xmlgen = XMLGenerator(result)
|
---|
729 | parser = create_parser()
|
---|
730 | parser.setContentHandler(xmlgen)
|
---|
731 | parser.parse(TEST_XMLFILE)
|
---|
732 |
|
---|
733 | self.assertEqual(parser.getSystemId(), TEST_XMLFILE)
|
---|
734 | self.assertEqual(parser.getPublicId(), None)
|
---|
735 |
|
---|
736 | @requires_unicode_filenames
|
---|
737 | def test_expat_locator_withinfo_unicode(self):
|
---|
738 | fname = support.TESTFN_UNICODE
|
---|
739 | shutil.copyfile(TEST_XMLFILE, fname)
|
---|
740 | self.addCleanup(support.unlink, fname)
|
---|
741 |
|
---|
742 | result = StringIO()
|
---|
743 | xmlgen = XMLGenerator(result)
|
---|
744 | parser = create_parser()
|
---|
745 | parser.setContentHandler(xmlgen)
|
---|
746 | parser.parse(fname)
|
---|
747 |
|
---|
748 | self.assertEqual(parser.getSystemId(), fname)
|
---|
749 | self.assertEqual(parser.getPublicId(), None)
|
---|
750 |
|
---|
751 |
|
---|
752 | # ===========================================================================
|
---|
753 | #
|
---|
754 | # error reporting
|
---|
755 | #
|
---|
756 | # ===========================================================================
|
---|
757 |
|
---|
758 | class ErrorReportingTest(unittest.TestCase):
|
---|
759 | def test_expat_inpsource_location(self):
|
---|
760 | parser = create_parser()
|
---|
761 | parser.setContentHandler(ContentHandler()) # do nothing
|
---|
762 | source = InputSource()
|
---|
763 | source.setByteStream(StringIO("<foo bar foobar>")) #ill-formed
|
---|
764 | name = "a file name"
|
---|
765 | source.setSystemId(name)
|
---|
766 | try:
|
---|
767 | parser.parse(source)
|
---|
768 | self.fail()
|
---|
769 | except SAXException, e:
|
---|
770 | self.assertEqual(e.getSystemId(), name)
|
---|
771 |
|
---|
772 | def test_expat_incomplete(self):
|
---|
773 | parser = create_parser()
|
---|
774 | parser.setContentHandler(ContentHandler()) # do nothing
|
---|
775 | self.assertRaises(SAXParseException, parser.parse, StringIO("<foo>"))
|
---|
776 |
|
---|
777 | def test_sax_parse_exception_str(self):
|
---|
778 | # pass various values from a locator to the SAXParseException to
|
---|
779 | # make sure that the __str__() doesn't fall apart when None is
|
---|
780 | # passed instead of an integer line and column number
|
---|
781 | #
|
---|
782 | # use "normal" values for the locator:
|
---|
783 | str(SAXParseException("message", None,
|
---|
784 | self.DummyLocator(1, 1)))
|
---|
785 | # use None for the line number:
|
---|
786 | str(SAXParseException("message", None,
|
---|
787 | self.DummyLocator(None, 1)))
|
---|
788 | # use None for the column number:
|
---|
789 | str(SAXParseException("message", None,
|
---|
790 | self.DummyLocator(1, None)))
|
---|
791 | # use None for both:
|
---|
792 | str(SAXParseException("message", None,
|
---|
793 | self.DummyLocator(None, None)))
|
---|
794 |
|
---|
795 | class DummyLocator:
|
---|
796 | def __init__(self, lineno, colno):
|
---|
797 | self._lineno = lineno
|
---|
798 | self._colno = colno
|
---|
799 |
|
---|
800 | def getPublicId(self):
|
---|
801 | return "pubid"
|
---|
802 |
|
---|
803 | def getSystemId(self):
|
---|
804 | return "sysid"
|
---|
805 |
|
---|
806 | def getLineNumber(self):
|
---|
807 | return self._lineno
|
---|
808 |
|
---|
809 | def getColumnNumber(self):
|
---|
810 | return self._colno
|
---|
811 |
|
---|
812 | # ===========================================================================
|
---|
813 | #
|
---|
814 | # xmlreader tests
|
---|
815 | #
|
---|
816 | # ===========================================================================
|
---|
817 |
|
---|
818 | class XmlReaderTest(XmlTestBase):
|
---|
819 |
|
---|
820 | # ===== AttributesImpl
|
---|
821 | def test_attrs_empty(self):
|
---|
822 | self.verify_empty_attrs(AttributesImpl({}))
|
---|
823 |
|
---|
824 | def test_attrs_wattr(self):
|
---|
825 | self.verify_attrs_wattr(AttributesImpl({"attr" : "val"}))
|
---|
826 |
|
---|
827 | def test_nsattrs_empty(self):
|
---|
828 | self.verify_empty_nsattrs(AttributesNSImpl({}, {}))
|
---|
829 |
|
---|
830 | def test_nsattrs_wattr(self):
|
---|
831 | attrs = AttributesNSImpl({(ns_uri, "attr") : "val"},
|
---|
832 | {(ns_uri, "attr") : "ns:attr"})
|
---|
833 |
|
---|
834 | self.assertEqual(attrs.getLength(), 1)
|
---|
835 | self.assertEqual(attrs.getNames(), [(ns_uri, "attr")])
|
---|
836 | self.assertEqual(attrs.getQNames(), ["ns:attr"])
|
---|
837 | self.assertEqual(len(attrs), 1)
|
---|
838 | self.assertTrue(attrs.has_key((ns_uri, "attr")))
|
---|
839 | self.assertEqual(attrs.keys(), [(ns_uri, "attr")])
|
---|
840 | self.assertEqual(attrs.get((ns_uri, "attr")), "val")
|
---|
841 | self.assertEqual(attrs.get((ns_uri, "attr"), 25), "val")
|
---|
842 | self.assertEqual(attrs.items(), [((ns_uri, "attr"), "val")])
|
---|
843 | self.assertEqual(attrs.values(), ["val"])
|
---|
844 | self.assertEqual(attrs.getValue((ns_uri, "attr")), "val")
|
---|
845 | self.assertEqual(attrs.getValueByQName("ns:attr"), "val")
|
---|
846 | self.assertEqual(attrs.getNameByQName("ns:attr"), (ns_uri, "attr"))
|
---|
847 | self.assertEqual(attrs[(ns_uri, "attr")], "val")
|
---|
848 | self.assertEqual(attrs.getQNameByName((ns_uri, "attr")), "ns:attr")
|
---|
849 |
|
---|
850 |
|
---|
851 | # During the development of Python 2.5, an attempt to move the "xml"
|
---|
852 | # package implementation to a new package ("xmlcore") proved painful.
|
---|
853 | # The goal of this change was to allow applications to be able to
|
---|
854 | # obtain and rely on behavior in the standard library implementation
|
---|
855 | # of the XML support without needing to be concerned about the
|
---|
856 | # availability of the PyXML implementation.
|
---|
857 | #
|
---|
858 | # While the existing import hackery in Lib/xml/__init__.py can cause
|
---|
859 | # PyXML's _xmlpus package to supplant the "xml" package, that only
|
---|
860 | # works because either implementation uses the "xml" package name for
|
---|
861 | # imports.
|
---|
862 | #
|
---|
863 | # The move resulted in a number of problems related to the fact that
|
---|
864 | # the import machinery's "package context" is based on the name that's
|
---|
865 | # being imported rather than the __name__ of the actual package
|
---|
866 | # containment; it wasn't possible for the "xml" package to be replaced
|
---|
867 | # by a simple module that indirected imports to the "xmlcore" package.
|
---|
868 | #
|
---|
869 | # The following two tests exercised bugs that were introduced in that
|
---|
870 | # attempt. Keeping these tests around will help detect problems with
|
---|
871 | # other attempts to provide reliable access to the standard library's
|
---|
872 | # implementation of the XML support.
|
---|
873 |
|
---|
874 | def test_sf_1511497(self):
|
---|
875 | # Bug report: http://www.python.org/sf/1511497
|
---|
876 | import sys
|
---|
877 | old_modules = sys.modules.copy()
|
---|
878 | for modname in sys.modules.keys():
|
---|
879 | if modname.startswith("xml."):
|
---|
880 | del sys.modules[modname]
|
---|
881 | try:
|
---|
882 | import xml.sax.expatreader
|
---|
883 | module = xml.sax.expatreader
|
---|
884 | self.assertEqual(module.__name__, "xml.sax.expatreader")
|
---|
885 | finally:
|
---|
886 | sys.modules.update(old_modules)
|
---|
887 |
|
---|
888 | def test_sf_1513611(self):
|
---|
889 | # Bug report: http://www.python.org/sf/1513611
|
---|
890 | sio = StringIO("invalid")
|
---|
891 | parser = make_parser()
|
---|
892 | from xml.sax import SAXParseException
|
---|
893 | self.assertRaises(SAXParseException, parser.parse, sio)
|
---|
894 |
|
---|
895 |
|
---|
896 | def test_main():
|
---|
897 | run_unittest(MakeParserTest,
|
---|
898 | SaxutilsTest,
|
---|
899 | StringXmlgenTest,
|
---|
900 | BytesIOXmlgenTest,
|
---|
901 | WriterXmlgenTest,
|
---|
902 | ExpatReaderTest,
|
---|
903 | ErrorReportingTest,
|
---|
904 | XmlReaderTest)
|
---|
905 |
|
---|
906 | if __name__ == "__main__":
|
---|
907 | test_main()
|
---|