• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# regression test for SAX 2.0
2# $Id$
3
4from xml.sax import make_parser, ContentHandler, \
5                    SAXException, SAXReaderNotAvailable, SAXParseException
6import unittest
7from unittest import mock
8try:
9    make_parser()
10except SAXReaderNotAvailable:
11    # don't try to test this module if we cannot create a parser
12    raise unittest.SkipTest("no XML parsers available")
13from xml.sax.saxutils import XMLGenerator, escape, unescape, quoteattr, \
14                             XMLFilterBase, prepare_input_source
15from xml.sax.expatreader import create_parser
16from xml.sax.handler import feature_namespaces, feature_external_ges
17from xml.sax.xmlreader import InputSource, AttributesImpl, AttributesNSImpl
18from io import BytesIO, StringIO
19import codecs
20import os.path
21import shutil
22from urllib.error import URLError
23from test import support
24from test.support import findfile, run_unittest, FakePath, TESTFN
25
26TEST_XMLFILE = findfile("test.xml", subdir="xmltestdata")
27TEST_XMLFILE_OUT = findfile("test.xml.out", subdir="xmltestdata")
28try:
29    TEST_XMLFILE.encode("utf-8")
30    TEST_XMLFILE_OUT.encode("utf-8")
31except UnicodeEncodeError:
32    raise unittest.SkipTest("filename is not encodable to utf8")
33
34supports_nonascii_filenames = True
35if not os.path.supports_unicode_filenames:
36    try:
37        support.TESTFN_UNICODE.encode(support.TESTFN_ENCODING)
38    except (UnicodeError, TypeError):
39        # Either the file system encoding is None, or the file name
40        # cannot be encoded in the file system encoding.
41        supports_nonascii_filenames = False
42requires_nonascii_filenames = unittest.skipUnless(
43        supports_nonascii_filenames,
44        'Requires non-ascii filenames support')
45
46ns_uri = "http://www.python.org/xml-ns/saxtest/"
47
48class XmlTestBase(unittest.TestCase):
49    def verify_empty_attrs(self, attrs):
50        self.assertRaises(KeyError, attrs.getValue, "attr")
51        self.assertRaises(KeyError, attrs.getValueByQName, "attr")
52        self.assertRaises(KeyError, attrs.getNameByQName, "attr")
53        self.assertRaises(KeyError, attrs.getQNameByName, "attr")
54        self.assertRaises(KeyError, attrs.__getitem__, "attr")
55        self.assertEqual(attrs.getLength(), 0)
56        self.assertEqual(attrs.getNames(), [])
57        self.assertEqual(attrs.getQNames(), [])
58        self.assertEqual(len(attrs), 0)
59        self.assertNotIn("attr", attrs)
60        self.assertEqual(list(attrs.keys()), [])
61        self.assertEqual(attrs.get("attrs"), None)
62        self.assertEqual(attrs.get("attrs", 25), 25)
63        self.assertEqual(list(attrs.items()), [])
64        self.assertEqual(list(attrs.values()), [])
65
66    def verify_empty_nsattrs(self, attrs):
67        self.assertRaises(KeyError, attrs.getValue, (ns_uri, "attr"))
68        self.assertRaises(KeyError, attrs.getValueByQName, "ns:attr")
69        self.assertRaises(KeyError, attrs.getNameByQName, "ns:attr")
70        self.assertRaises(KeyError, attrs.getQNameByName, (ns_uri, "attr"))
71        self.assertRaises(KeyError, attrs.__getitem__, (ns_uri, "attr"))
72        self.assertEqual(attrs.getLength(), 0)
73        self.assertEqual(attrs.getNames(), [])
74        self.assertEqual(attrs.getQNames(), [])
75        self.assertEqual(len(attrs), 0)
76        self.assertNotIn((ns_uri, "attr"), attrs)
77        self.assertEqual(list(attrs.keys()), [])
78        self.assertEqual(attrs.get((ns_uri, "attr")), None)
79        self.assertEqual(attrs.get((ns_uri, "attr"), 25), 25)
80        self.assertEqual(list(attrs.items()), [])
81        self.assertEqual(list(attrs.values()), [])
82
83    def verify_attrs_wattr(self, attrs):
84        self.assertEqual(attrs.getLength(), 1)
85        self.assertEqual(attrs.getNames(), ["attr"])
86        self.assertEqual(attrs.getQNames(), ["attr"])
87        self.assertEqual(len(attrs), 1)
88        self.assertIn("attr", attrs)
89        self.assertEqual(list(attrs.keys()), ["attr"])
90        self.assertEqual(attrs.get("attr"), "val")
91        self.assertEqual(attrs.get("attr", 25), "val")
92        self.assertEqual(list(attrs.items()), [("attr", "val")])
93        self.assertEqual(list(attrs.values()), ["val"])
94        self.assertEqual(attrs.getValue("attr"), "val")
95        self.assertEqual(attrs.getValueByQName("attr"), "val")
96        self.assertEqual(attrs.getNameByQName("attr"), "attr")
97        self.assertEqual(attrs["attr"], "val")
98        self.assertEqual(attrs.getQNameByName("attr"), "attr")
99
100
101def xml_str(doc, encoding=None):
102    if encoding is None:
103        return doc
104    return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, doc)
105
106def xml_bytes(doc, encoding, decl_encoding=...):
107    if decl_encoding is ...:
108        decl_encoding = encoding
109    return xml_str(doc, decl_encoding).encode(encoding, 'xmlcharrefreplace')
110
111def make_xml_file(doc, encoding, decl_encoding=...):
112    if decl_encoding is ...:
113        decl_encoding = encoding
114    with open(TESTFN, 'w', encoding=encoding, errors='xmlcharrefreplace') as f:
115        f.write(xml_str(doc, decl_encoding))
116
117
118class ParseTest(unittest.TestCase):
119    data = '<money value="$\xa3\u20ac\U0001017b">$\xa3\u20ac\U0001017b</money>'
120
121    def tearDown(self):
122        support.unlink(TESTFN)
123
124    def check_parse(self, f):
125        from xml.sax import parse
126        result = StringIO()
127        parse(f, XMLGenerator(result, 'utf-8'))
128        self.assertEqual(result.getvalue(), xml_str(self.data, 'utf-8'))
129
130    def test_parse_text(self):
131        encodings = ('us-ascii', 'iso-8859-1', 'utf-8',
132                     'utf-16', 'utf-16le', 'utf-16be')
133        for encoding in encodings:
134            self.check_parse(StringIO(xml_str(self.data, encoding)))
135            make_xml_file(self.data, encoding)
136            with open(TESTFN, 'r', encoding=encoding) as f:
137                self.check_parse(f)
138            self.check_parse(StringIO(self.data))
139            make_xml_file(self.data, encoding, None)
140            with open(TESTFN, 'r', encoding=encoding) as f:
141                self.check_parse(f)
142
143    def test_parse_bytes(self):
144        # UTF-8 is default encoding, US-ASCII is compatible with UTF-8,
145        # UTF-16 is autodetected
146        encodings = ('us-ascii', 'utf-8', 'utf-16', 'utf-16le', 'utf-16be')
147        for encoding in encodings:
148            self.check_parse(BytesIO(xml_bytes(self.data, encoding)))
149            make_xml_file(self.data, encoding)
150            self.check_parse(TESTFN)
151            with open(TESTFN, 'rb') as f:
152                self.check_parse(f)
153            self.check_parse(BytesIO(xml_bytes(self.data, encoding, None)))
154            make_xml_file(self.data, encoding, None)
155            self.check_parse(TESTFN)
156            with open(TESTFN, 'rb') as f:
157                self.check_parse(f)
158        # accept UTF-8 with BOM
159        self.check_parse(BytesIO(xml_bytes(self.data, 'utf-8-sig', 'utf-8')))
160        make_xml_file(self.data, 'utf-8-sig', 'utf-8')
161        self.check_parse(TESTFN)
162        with open(TESTFN, 'rb') as f:
163            self.check_parse(f)
164        self.check_parse(BytesIO(xml_bytes(self.data, 'utf-8-sig', None)))
165        make_xml_file(self.data, 'utf-8-sig', None)
166        self.check_parse(TESTFN)
167        with open(TESTFN, 'rb') as f:
168            self.check_parse(f)
169        # accept data with declared encoding
170        self.check_parse(BytesIO(xml_bytes(self.data, 'iso-8859-1')))
171        make_xml_file(self.data, 'iso-8859-1')
172        self.check_parse(TESTFN)
173        with open(TESTFN, 'rb') as f:
174            self.check_parse(f)
175        # fail on non-UTF-8 incompatible data without declared encoding
176        with self.assertRaises(SAXException):
177            self.check_parse(BytesIO(xml_bytes(self.data, 'iso-8859-1', None)))
178        make_xml_file(self.data, 'iso-8859-1', None)
179        with self.assertRaises(SAXException):
180            self.check_parse(TESTFN)
181        with open(TESTFN, 'rb') as f:
182            with self.assertRaises(SAXException):
183                self.check_parse(f)
184
185    def test_parse_path_object(self):
186        make_xml_file(self.data, 'utf-8', None)
187        self.check_parse(FakePath(TESTFN))
188
189    def test_parse_InputSource(self):
190        # accept data without declared but with explicitly specified encoding
191        make_xml_file(self.data, 'iso-8859-1', None)
192        with open(TESTFN, 'rb') as f:
193            input = InputSource()
194            input.setByteStream(f)
195            input.setEncoding('iso-8859-1')
196            self.check_parse(input)
197
198    def test_parse_close_source(self):
199        builtin_open = open
200        fileobj = None
201
202        def mock_open(*args):
203            nonlocal fileobj
204            fileobj = builtin_open(*args)
205            return fileobj
206
207        with mock.patch('xml.sax.saxutils.open', side_effect=mock_open):
208            make_xml_file(self.data, 'iso-8859-1', None)
209            with self.assertRaises(SAXException):
210                self.check_parse(TESTFN)
211            self.assertTrue(fileobj.closed)
212
213    def check_parseString(self, s):
214        from xml.sax import parseString
215        result = StringIO()
216        parseString(s, XMLGenerator(result, 'utf-8'))
217        self.assertEqual(result.getvalue(), xml_str(self.data, 'utf-8'))
218
219    def test_parseString_text(self):
220        encodings = ('us-ascii', 'iso-8859-1', 'utf-8',
221                     'utf-16', 'utf-16le', 'utf-16be')
222        for encoding in encodings:
223            self.check_parseString(xml_str(self.data, encoding))
224        self.check_parseString(self.data)
225
226    def test_parseString_bytes(self):
227        # UTF-8 is default encoding, US-ASCII is compatible with UTF-8,
228        # UTF-16 is autodetected
229        encodings = ('us-ascii', 'utf-8', 'utf-16', 'utf-16le', 'utf-16be')
230        for encoding in encodings:
231            self.check_parseString(xml_bytes(self.data, encoding))
232            self.check_parseString(xml_bytes(self.data, encoding, None))
233        # accept UTF-8 with BOM
234        self.check_parseString(xml_bytes(self.data, 'utf-8-sig', 'utf-8'))
235        self.check_parseString(xml_bytes(self.data, 'utf-8-sig', None))
236        # accept data with declared encoding
237        self.check_parseString(xml_bytes(self.data, 'iso-8859-1'))
238        # fail on non-UTF-8 incompatible data without declared encoding
239        with self.assertRaises(SAXException):
240            self.check_parseString(xml_bytes(self.data, 'iso-8859-1', None))
241
242class MakeParserTest(unittest.TestCase):
243    def test_make_parser2(self):
244        # Creating parsers several times in a row should succeed.
245        # Testing this because there have been failures of this kind
246        # before.
247        from xml.sax import make_parser
248        p = make_parser()
249        from xml.sax import make_parser
250        p = make_parser()
251        from xml.sax import make_parser
252        p = make_parser()
253        from xml.sax import make_parser
254        p = make_parser()
255        from xml.sax import make_parser
256        p = make_parser()
257        from xml.sax import make_parser
258        p = make_parser()
259
260    def test_make_parser3(self):
261        # Testing that make_parser can handle different types of
262        # iterables.
263        make_parser(['module'])
264        make_parser(('module', ))
265        make_parser({'module'})
266        make_parser(frozenset({'module'}))
267        make_parser({'module': None})
268        make_parser(iter(['module']))
269
270    def test_make_parser4(self):
271        # Testing that make_parser can handle empty iterables.
272        make_parser([])
273        make_parser(tuple())
274        make_parser(set())
275        make_parser(frozenset())
276        make_parser({})
277        make_parser(iter([]))
278
279    def test_make_parser5(self):
280        # Testing that make_parser can handle iterables with more than
281        # one item.
282        make_parser(['module1', 'module2'])
283        make_parser(('module1', 'module2'))
284        make_parser({'module1', 'module2'})
285        make_parser(frozenset({'module1', 'module2'}))
286        make_parser({'module1': None, 'module2': None})
287        make_parser(iter(['module1', 'module2']))
288
289# ===========================================================================
290#
291#   saxutils tests
292#
293# ===========================================================================
294
295class SaxutilsTest(unittest.TestCase):
296    # ===== escape
297    def test_escape_basic(self):
298        self.assertEqual(escape("Donald Duck & Co"), "Donald Duck &amp; Co")
299
300    def test_escape_all(self):
301        self.assertEqual(escape("<Donald Duck & Co>"),
302                         "&lt;Donald Duck &amp; Co&gt;")
303
304    def test_escape_extra(self):
305        self.assertEqual(escape("Hei på deg", {"å" : "&aring;"}),
306                         "Hei p&aring; deg")
307
308    # ===== unescape
309    def test_unescape_basic(self):
310        self.assertEqual(unescape("Donald Duck &amp; Co"), "Donald Duck & Co")
311
312    def test_unescape_all(self):
313        self.assertEqual(unescape("&lt;Donald Duck &amp; Co&gt;"),
314                         "<Donald Duck & Co>")
315
316    def test_unescape_extra(self):
317        self.assertEqual(unescape("Hei på deg", {"å" : "&aring;"}),
318                         "Hei p&aring; deg")
319
320    def test_unescape_amp_extra(self):
321        self.assertEqual(unescape("&amp;foo;", {"&foo;": "splat"}), "&foo;")
322
323    # ===== quoteattr
324    def test_quoteattr_basic(self):
325        self.assertEqual(quoteattr("Donald Duck & Co"),
326                         '"Donald Duck &amp; Co"')
327
328    def test_single_quoteattr(self):
329        self.assertEqual(quoteattr('Includes "double" quotes'),
330                         '\'Includes "double" quotes\'')
331
332    def test_double_quoteattr(self):
333        self.assertEqual(quoteattr("Includes 'single' quotes"),
334                         "\"Includes 'single' quotes\"")
335
336    def test_single_double_quoteattr(self):
337        self.assertEqual(quoteattr("Includes 'single' and \"double\" quotes"),
338                         "\"Includes 'single' and &quot;double&quot; quotes\"")
339
340    # ===== make_parser
341    def test_make_parser(self):
342        # Creating a parser should succeed - it should fall back
343        # to the expatreader
344        p = make_parser(['xml.parsers.no_such_parser'])
345
346
347class PrepareInputSourceTest(unittest.TestCase):
348
349    def setUp(self):
350        self.file = support.TESTFN
351        with open(self.file, "w") as tmp:
352            tmp.write("This was read from a file.")
353
354    def tearDown(self):
355        support.unlink(self.file)
356
357    def make_byte_stream(self):
358        return BytesIO(b"This is a byte stream.")
359
360    def make_character_stream(self):
361        return StringIO("This is a character stream.")
362
363    def checkContent(self, stream, content):
364        self.assertIsNotNone(stream)
365        self.assertEqual(stream.read(), content)
366        stream.close()
367
368
369    def test_character_stream(self):
370        # If the source is an InputSource with a character stream, use it.
371        src = InputSource(self.file)
372        src.setCharacterStream(self.make_character_stream())
373        prep = prepare_input_source(src)
374        self.assertIsNone(prep.getByteStream())
375        self.checkContent(prep.getCharacterStream(),
376                          "This is a character stream.")
377
378    def test_byte_stream(self):
379        # If the source is an InputSource that does not have a character
380        # stream but does have a byte stream, use the byte stream.
381        src = InputSource(self.file)
382        src.setByteStream(self.make_byte_stream())
383        prep = prepare_input_source(src)
384        self.assertIsNone(prep.getCharacterStream())
385        self.checkContent(prep.getByteStream(),
386                          b"This is a byte stream.")
387
388    def test_system_id(self):
389        # If the source is an InputSource that has neither a character
390        # stream nor a byte stream, open the system ID.
391        src = InputSource(self.file)
392        prep = prepare_input_source(src)
393        self.assertIsNone(prep.getCharacterStream())
394        self.checkContent(prep.getByteStream(),
395                          b"This was read from a file.")
396
397    def test_string(self):
398        # If the source is a string, use it as a system ID and open it.
399        prep = prepare_input_source(self.file)
400        self.assertIsNone(prep.getCharacterStream())
401        self.checkContent(prep.getByteStream(),
402                          b"This was read from a file.")
403
404    def test_path_objects(self):
405        # If the source is a Path object, use it as a system ID and open it.
406        prep = prepare_input_source(FakePath(self.file))
407        self.assertIsNone(prep.getCharacterStream())
408        self.checkContent(prep.getByteStream(),
409                          b"This was read from a file.")
410
411    def test_binary_file(self):
412        # If the source is a binary file-like object, use it as a byte
413        # stream.
414        prep = prepare_input_source(self.make_byte_stream())
415        self.assertIsNone(prep.getCharacterStream())
416        self.checkContent(prep.getByteStream(),
417                          b"This is a byte stream.")
418
419    def test_text_file(self):
420        # If the source is a text file-like object, use it as a character
421        # stream.
422        prep = prepare_input_source(self.make_character_stream())
423        self.assertIsNone(prep.getByteStream())
424        self.checkContent(prep.getCharacterStream(),
425                          "This is a character stream.")
426
427
428# ===== XMLGenerator
429
430class XmlgenTest:
431    def test_xmlgen_basic(self):
432        result = self.ioclass()
433        gen = XMLGenerator(result)
434        gen.startDocument()
435        gen.startElement("doc", {})
436        gen.endElement("doc")
437        gen.endDocument()
438
439        self.assertEqual(result.getvalue(), self.xml("<doc></doc>"))
440
441    def test_xmlgen_basic_empty(self):
442        result = self.ioclass()
443        gen = XMLGenerator(result, short_empty_elements=True)
444        gen.startDocument()
445        gen.startElement("doc", {})
446        gen.endElement("doc")
447        gen.endDocument()
448
449        self.assertEqual(result.getvalue(), self.xml("<doc/>"))
450
451    def test_xmlgen_content(self):
452        result = self.ioclass()
453        gen = XMLGenerator(result)
454
455        gen.startDocument()
456        gen.startElement("doc", {})
457        gen.characters("huhei")
458        gen.endElement("doc")
459        gen.endDocument()
460
461        self.assertEqual(result.getvalue(), self.xml("<doc>huhei</doc>"))
462
463    def test_xmlgen_content_empty(self):
464        result = self.ioclass()
465        gen = XMLGenerator(result, short_empty_elements=True)
466
467        gen.startDocument()
468        gen.startElement("doc", {})
469        gen.characters("huhei")
470        gen.endElement("doc")
471        gen.endDocument()
472
473        self.assertEqual(result.getvalue(), self.xml("<doc>huhei</doc>"))
474
475    def test_xmlgen_pi(self):
476        result = self.ioclass()
477        gen = XMLGenerator(result)
478
479        gen.startDocument()
480        gen.processingInstruction("test", "data")
481        gen.startElement("doc", {})
482        gen.endElement("doc")
483        gen.endDocument()
484
485        self.assertEqual(result.getvalue(),
486            self.xml("<?test data?><doc></doc>"))
487
488    def test_xmlgen_content_escape(self):
489        result = self.ioclass()
490        gen = XMLGenerator(result)
491
492        gen.startDocument()
493        gen.startElement("doc", {})
494        gen.characters("<huhei&")
495        gen.endElement("doc")
496        gen.endDocument()
497
498        self.assertEqual(result.getvalue(),
499            self.xml("<doc>&lt;huhei&amp;</doc>"))
500
501    def test_xmlgen_attr_escape(self):
502        result = self.ioclass()
503        gen = XMLGenerator(result)
504
505        gen.startDocument()
506        gen.startElement("doc", {"a": '"'})
507        gen.startElement("e", {"a": "'"})
508        gen.endElement("e")
509        gen.startElement("e", {"a": "'\""})
510        gen.endElement("e")
511        gen.startElement("e", {"a": "\n\r\t"})
512        gen.endElement("e")
513        gen.endElement("doc")
514        gen.endDocument()
515
516        self.assertEqual(result.getvalue(), self.xml(
517            "<doc a='\"'><e a=\"'\"></e>"
518            "<e a=\"'&quot;\"></e>"
519            "<e a=\"&#10;&#13;&#9;\"></e></doc>"))
520
521    def test_xmlgen_encoding(self):
522        encodings = ('iso-8859-15', 'utf-8', 'utf-8-sig',
523                     'utf-16', 'utf-16be', 'utf-16le',
524                     'utf-32', 'utf-32be', 'utf-32le')
525        for encoding in encodings:
526            result = self.ioclass()
527            gen = XMLGenerator(result, encoding=encoding)
528
529            gen.startDocument()
530            gen.startElement("doc", {"a": '\u20ac'})
531            gen.characters("\u20ac")
532            gen.endElement("doc")
533            gen.endDocument()
534
535            self.assertEqual(result.getvalue(),
536                self.xml('<doc a="\u20ac">\u20ac</doc>', encoding=encoding))
537
538    def test_xmlgen_unencodable(self):
539        result = self.ioclass()
540        gen = XMLGenerator(result, encoding='ascii')
541
542        gen.startDocument()
543        gen.startElement("doc", {"a": '\u20ac'})
544        gen.characters("\u20ac")
545        gen.endElement("doc")
546        gen.endDocument()
547
548        self.assertEqual(result.getvalue(),
549            self.xml('<doc a="&#8364;">&#8364;</doc>', encoding='ascii'))
550
551    def test_xmlgen_ignorable(self):
552        result = self.ioclass()
553        gen = XMLGenerator(result)
554
555        gen.startDocument()
556        gen.startElement("doc", {})
557        gen.ignorableWhitespace(" ")
558        gen.endElement("doc")
559        gen.endDocument()
560
561        self.assertEqual(result.getvalue(), self.xml("<doc> </doc>"))
562
563    def test_xmlgen_ignorable_empty(self):
564        result = self.ioclass()
565        gen = XMLGenerator(result, short_empty_elements=True)
566
567        gen.startDocument()
568        gen.startElement("doc", {})
569        gen.ignorableWhitespace(" ")
570        gen.endElement("doc")
571        gen.endDocument()
572
573        self.assertEqual(result.getvalue(), self.xml("<doc> </doc>"))
574
575    def test_xmlgen_encoding_bytes(self):
576        encodings = ('iso-8859-15', 'utf-8', 'utf-8-sig',
577                     'utf-16', 'utf-16be', 'utf-16le',
578                     'utf-32', 'utf-32be', 'utf-32le')
579        for encoding in encodings:
580            result = self.ioclass()
581            gen = XMLGenerator(result, encoding=encoding)
582
583            gen.startDocument()
584            gen.startElement("doc", {"a": '\u20ac'})
585            gen.characters("\u20ac".encode(encoding))
586            gen.ignorableWhitespace(" ".encode(encoding))
587            gen.endElement("doc")
588            gen.endDocument()
589
590            self.assertEqual(result.getvalue(),
591                self.xml('<doc a="\u20ac">\u20ac </doc>', encoding=encoding))
592
593    def test_xmlgen_ns(self):
594        result = self.ioclass()
595        gen = XMLGenerator(result)
596
597        gen.startDocument()
598        gen.startPrefixMapping("ns1", ns_uri)
599        gen.startElementNS((ns_uri, "doc"), "ns1:doc", {})
600        # add an unqualified name
601        gen.startElementNS((None, "udoc"), None, {})
602        gen.endElementNS((None, "udoc"), None)
603        gen.endElementNS((ns_uri, "doc"), "ns1:doc")
604        gen.endPrefixMapping("ns1")
605        gen.endDocument()
606
607        self.assertEqual(result.getvalue(), self.xml(
608           '<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
609                                         ns_uri))
610
611    def test_xmlgen_ns_empty(self):
612        result = self.ioclass()
613        gen = XMLGenerator(result, short_empty_elements=True)
614
615        gen.startDocument()
616        gen.startPrefixMapping("ns1", ns_uri)
617        gen.startElementNS((ns_uri, "doc"), "ns1:doc", {})
618        # add an unqualified name
619        gen.startElementNS((None, "udoc"), None, {})
620        gen.endElementNS((None, "udoc"), None)
621        gen.endElementNS((ns_uri, "doc"), "ns1:doc")
622        gen.endPrefixMapping("ns1")
623        gen.endDocument()
624
625        self.assertEqual(result.getvalue(), self.xml(
626           '<ns1:doc xmlns:ns1="%s"><udoc/></ns1:doc>' %
627                                         ns_uri))
628
629    def test_1463026_1(self):
630        result = self.ioclass()
631        gen = XMLGenerator(result)
632
633        gen.startDocument()
634        gen.startElementNS((None, 'a'), 'a', {(None, 'b'):'c'})
635        gen.endElementNS((None, 'a'), 'a')
636        gen.endDocument()
637
638        self.assertEqual(result.getvalue(), self.xml('<a b="c"></a>'))
639
640    def test_1463026_1_empty(self):
641        result = self.ioclass()
642        gen = XMLGenerator(result, short_empty_elements=True)
643
644        gen.startDocument()
645        gen.startElementNS((None, 'a'), 'a', {(None, 'b'):'c'})
646        gen.endElementNS((None, 'a'), 'a')
647        gen.endDocument()
648
649        self.assertEqual(result.getvalue(), self.xml('<a b="c"/>'))
650
651    def test_1463026_2(self):
652        result = self.ioclass()
653        gen = XMLGenerator(result)
654
655        gen.startDocument()
656        gen.startPrefixMapping(None, 'qux')
657        gen.startElementNS(('qux', 'a'), 'a', {})
658        gen.endElementNS(('qux', 'a'), 'a')
659        gen.endPrefixMapping(None)
660        gen.endDocument()
661
662        self.assertEqual(result.getvalue(), self.xml('<a xmlns="qux"></a>'))
663
664    def test_1463026_2_empty(self):
665        result = self.ioclass()
666        gen = XMLGenerator(result, short_empty_elements=True)
667
668        gen.startDocument()
669        gen.startPrefixMapping(None, 'qux')
670        gen.startElementNS(('qux', 'a'), 'a', {})
671        gen.endElementNS(('qux', 'a'), 'a')
672        gen.endPrefixMapping(None)
673        gen.endDocument()
674
675        self.assertEqual(result.getvalue(), self.xml('<a xmlns="qux"/>'))
676
677    def test_1463026_3(self):
678        result = self.ioclass()
679        gen = XMLGenerator(result)
680
681        gen.startDocument()
682        gen.startPrefixMapping('my', 'qux')
683        gen.startElementNS(('qux', 'a'), 'a', {(None, 'b'):'c'})
684        gen.endElementNS(('qux', 'a'), 'a')
685        gen.endPrefixMapping('my')
686        gen.endDocument()
687
688        self.assertEqual(result.getvalue(),
689            self.xml('<my:a xmlns:my="qux" b="c"></my:a>'))
690
691    def test_1463026_3_empty(self):
692        result = self.ioclass()
693        gen = XMLGenerator(result, short_empty_elements=True)
694
695        gen.startDocument()
696        gen.startPrefixMapping('my', 'qux')
697        gen.startElementNS(('qux', 'a'), 'a', {(None, 'b'):'c'})
698        gen.endElementNS(('qux', 'a'), 'a')
699        gen.endPrefixMapping('my')
700        gen.endDocument()
701
702        self.assertEqual(result.getvalue(),
703            self.xml('<my:a xmlns:my="qux" b="c"/>'))
704
705    def test_5027_1(self):
706        # The xml prefix (as in xml:lang below) is reserved and bound by
707        # definition to http://www.w3.org/XML/1998/namespace.  XMLGenerator had
708        # a bug whereby a KeyError is raised because this namespace is missing
709        # from a dictionary.
710        #
711        # This test demonstrates the bug by parsing a document.
712        test_xml = StringIO(
713            '<?xml version="1.0"?>'
714            '<a:g1 xmlns:a="http://example.com/ns">'
715             '<a:g2 xml:lang="en">Hello</a:g2>'
716            '</a:g1>')
717
718        parser = make_parser()
719        parser.setFeature(feature_namespaces, True)
720        result = self.ioclass()
721        gen = XMLGenerator(result)
722        parser.setContentHandler(gen)
723        parser.parse(test_xml)
724
725        self.assertEqual(result.getvalue(),
726                         self.xml(
727                         '<a:g1 xmlns:a="http://example.com/ns">'
728                          '<a:g2 xml:lang="en">Hello</a:g2>'
729                         '</a:g1>'))
730
731    def test_5027_2(self):
732        # The xml prefix (as in xml:lang below) is reserved and bound by
733        # definition to http://www.w3.org/XML/1998/namespace.  XMLGenerator had
734        # a bug whereby a KeyError is raised because this namespace is missing
735        # from a dictionary.
736        #
737        # This test demonstrates the bug by direct manipulation of the
738        # XMLGenerator.
739        result = self.ioclass()
740        gen = XMLGenerator(result)
741
742        gen.startDocument()
743        gen.startPrefixMapping('a', 'http://example.com/ns')
744        gen.startElementNS(('http://example.com/ns', 'g1'), 'g1', {})
745        lang_attr = {('http://www.w3.org/XML/1998/namespace', 'lang'): 'en'}
746        gen.startElementNS(('http://example.com/ns', 'g2'), 'g2', lang_attr)
747        gen.characters('Hello')
748        gen.endElementNS(('http://example.com/ns', 'g2'), 'g2')
749        gen.endElementNS(('http://example.com/ns', 'g1'), 'g1')
750        gen.endPrefixMapping('a')
751        gen.endDocument()
752
753        self.assertEqual(result.getvalue(),
754                         self.xml(
755                         '<a:g1 xmlns:a="http://example.com/ns">'
756                          '<a:g2 xml:lang="en">Hello</a:g2>'
757                         '</a:g1>'))
758
759    def test_no_close_file(self):
760        result = self.ioclass()
761        def func(out):
762            gen = XMLGenerator(out)
763            gen.startDocument()
764            gen.startElement("doc", {})
765        func(result)
766        self.assertFalse(result.closed)
767
768    def test_xmlgen_fragment(self):
769        result = self.ioclass()
770        gen = XMLGenerator(result)
771
772        # Don't call gen.startDocument()
773        gen.startElement("foo", {"a": "1.0"})
774        gen.characters("Hello")
775        gen.endElement("foo")
776        gen.startElement("bar", {"b": "2.0"})
777        gen.endElement("bar")
778        # Don't call gen.endDocument()
779
780        self.assertEqual(result.getvalue(),
781            self.xml('<foo a="1.0">Hello</foo><bar b="2.0"></bar>')[len(self.xml('')):])
782
783class StringXmlgenTest(XmlgenTest, unittest.TestCase):
784    ioclass = StringIO
785
786    def xml(self, doc, encoding='iso-8859-1'):
787        return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, doc)
788
789    test_xmlgen_unencodable = None
790
791class BytesXmlgenTest(XmlgenTest, unittest.TestCase):
792    ioclass = BytesIO
793
794    def xml(self, doc, encoding='iso-8859-1'):
795        return ('<?xml version="1.0" encoding="%s"?>\n%s' %
796                (encoding, doc)).encode(encoding, 'xmlcharrefreplace')
797
798class WriterXmlgenTest(BytesXmlgenTest):
799    class ioclass(list):
800        write = list.append
801        closed = False
802
803        def seekable(self):
804            return True
805
806        def tell(self):
807            # return 0 at start and not 0 after start
808            return len(self)
809
810        def getvalue(self):
811            return b''.join(self)
812
813class StreamWriterXmlgenTest(XmlgenTest, unittest.TestCase):
814    def ioclass(self):
815        raw = BytesIO()
816        writer = codecs.getwriter('ascii')(raw, 'xmlcharrefreplace')
817        writer.getvalue = raw.getvalue
818        return writer
819
820    def xml(self, doc, encoding='iso-8859-1'):
821        return ('<?xml version="1.0" encoding="%s"?>\n%s' %
822                (encoding, doc)).encode('ascii', 'xmlcharrefreplace')
823
824class StreamReaderWriterXmlgenTest(XmlgenTest, unittest.TestCase):
825    fname = support.TESTFN + '-codecs'
826
827    def ioclass(self):
828        writer = codecs.open(self.fname, 'w', encoding='ascii',
829                             errors='xmlcharrefreplace', buffering=0)
830        def cleanup():
831            writer.close()
832            support.unlink(self.fname)
833        self.addCleanup(cleanup)
834        def getvalue():
835            # Windows will not let use reopen without first closing
836            writer.close()
837            with open(writer.name, 'rb') as f:
838                return f.read()
839        writer.getvalue = getvalue
840        return writer
841
842    def xml(self, doc, encoding='iso-8859-1'):
843        return ('<?xml version="1.0" encoding="%s"?>\n%s' %
844                (encoding, doc)).encode('ascii', 'xmlcharrefreplace')
845
846start = b'<?xml version="1.0" encoding="iso-8859-1"?>\n'
847
848
849class XMLFilterBaseTest(unittest.TestCase):
850    def test_filter_basic(self):
851        result = BytesIO()
852        gen = XMLGenerator(result)
853        filter = XMLFilterBase()
854        filter.setContentHandler(gen)
855
856        filter.startDocument()
857        filter.startElement("doc", {})
858        filter.characters("content")
859        filter.ignorableWhitespace(" ")
860        filter.endElement("doc")
861        filter.endDocument()
862
863        self.assertEqual(result.getvalue(), start + b"<doc>content </doc>")
864
865# ===========================================================================
866#
867#   expatreader tests
868#
869# ===========================================================================
870
871with open(TEST_XMLFILE_OUT, 'rb') as f:
872    xml_test_out = f.read()
873
874class ExpatReaderTest(XmlTestBase):
875
876    # ===== XMLReader support
877
878    def test_expat_binary_file(self):
879        parser = create_parser()
880        result = BytesIO()
881        xmlgen = XMLGenerator(result)
882
883        parser.setContentHandler(xmlgen)
884        with open(TEST_XMLFILE, 'rb') as f:
885            parser.parse(f)
886
887        self.assertEqual(result.getvalue(), xml_test_out)
888
889    def test_expat_text_file(self):
890        parser = create_parser()
891        result = BytesIO()
892        xmlgen = XMLGenerator(result)
893
894        parser.setContentHandler(xmlgen)
895        with open(TEST_XMLFILE, 'rt', encoding='iso-8859-1') as f:
896            parser.parse(f)
897
898        self.assertEqual(result.getvalue(), xml_test_out)
899
900    @requires_nonascii_filenames
901    def test_expat_binary_file_nonascii(self):
902        fname = support.TESTFN_UNICODE
903        shutil.copyfile(TEST_XMLFILE, fname)
904        self.addCleanup(support.unlink, fname)
905
906        parser = create_parser()
907        result = BytesIO()
908        xmlgen = XMLGenerator(result)
909
910        parser.setContentHandler(xmlgen)
911        parser.parse(open(fname, 'rb'))
912
913        self.assertEqual(result.getvalue(), xml_test_out)
914
915    def test_expat_binary_file_bytes_name(self):
916        fname = os.fsencode(TEST_XMLFILE)
917        parser = create_parser()
918        result = BytesIO()
919        xmlgen = XMLGenerator(result)
920
921        parser.setContentHandler(xmlgen)
922        with open(fname, 'rb') as f:
923            parser.parse(f)
924
925        self.assertEqual(result.getvalue(), xml_test_out)
926
927    def test_expat_binary_file_int_name(self):
928        parser = create_parser()
929        result = BytesIO()
930        xmlgen = XMLGenerator(result)
931
932        parser.setContentHandler(xmlgen)
933        with open(TEST_XMLFILE, 'rb') as f:
934            with open(f.fileno(), 'rb', closefd=False) as f2:
935                parser.parse(f2)
936
937        self.assertEqual(result.getvalue(), xml_test_out)
938
939    # ===== DTDHandler support
940
941    class TestDTDHandler:
942
943        def __init__(self):
944            self._notations = []
945            self._entities  = []
946
947        def notationDecl(self, name, publicId, systemId):
948            self._notations.append((name, publicId, systemId))
949
950        def unparsedEntityDecl(self, name, publicId, systemId, ndata):
951            self._entities.append((name, publicId, systemId, ndata))
952
953
954    class TestEntityRecorder:
955        def __init__(self):
956            self.entities = []
957
958        def resolveEntity(self, publicId, systemId):
959            self.entities.append((publicId, systemId))
960            source = InputSource()
961            source.setPublicId(publicId)
962            source.setSystemId(systemId)
963            return source
964
965    def test_expat_dtdhandler(self):
966        parser = create_parser()
967        handler = self.TestDTDHandler()
968        parser.setDTDHandler(handler)
969
970        parser.feed('<!DOCTYPE doc [\n')
971        parser.feed('  <!ENTITY img SYSTEM "expat.gif" NDATA GIF>\n')
972        parser.feed('  <!NOTATION GIF PUBLIC "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN">\n')
973        parser.feed(']>\n')
974        parser.feed('<doc></doc>')
975        parser.close()
976
977        self.assertEqual(handler._notations,
978            [("GIF", "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN", None)])
979        self.assertEqual(handler._entities, [("img", None, "expat.gif", "GIF")])
980
981    def test_expat_external_dtd_enabled(self):
982        parser = create_parser()
983        parser.setFeature(feature_external_ges, True)
984        resolver = self.TestEntityRecorder()
985        parser.setEntityResolver(resolver)
986
987        with self.assertRaises(URLError):
988            parser.feed(
989                '<!DOCTYPE external SYSTEM "unsupported://non-existing">\n'
990            )
991        self.assertEqual(
992            resolver.entities, [(None, 'unsupported://non-existing')]
993        )
994
995    def test_expat_external_dtd_default(self):
996        parser = create_parser()
997        resolver = self.TestEntityRecorder()
998        parser.setEntityResolver(resolver)
999
1000        parser.feed(
1001            '<!DOCTYPE external SYSTEM "unsupported://non-existing">\n'
1002        )
1003        parser.feed('<doc />')
1004        parser.close()
1005        self.assertEqual(resolver.entities, [])
1006
1007    # ===== EntityResolver support
1008
1009    class TestEntityResolver:
1010
1011        def resolveEntity(self, publicId, systemId):
1012            inpsrc = InputSource()
1013            inpsrc.setByteStream(BytesIO(b"<entity/>"))
1014            return inpsrc
1015
1016    def test_expat_entityresolver_enabled(self):
1017        parser = create_parser()
1018        parser.setFeature(feature_external_ges, True)
1019        parser.setEntityResolver(self.TestEntityResolver())
1020        result = BytesIO()
1021        parser.setContentHandler(XMLGenerator(result))
1022
1023        parser.feed('<!DOCTYPE doc [\n')
1024        parser.feed('  <!ENTITY test SYSTEM "whatever">\n')
1025        parser.feed(']>\n')
1026        parser.feed('<doc>&test;</doc>')
1027        parser.close()
1028
1029        self.assertEqual(result.getvalue(), start +
1030                         b"<doc><entity></entity></doc>")
1031
1032    def test_expat_entityresolver_default(self):
1033        parser = create_parser()
1034        self.assertEqual(parser.getFeature(feature_external_ges), False)
1035        parser.setEntityResolver(self.TestEntityResolver())
1036        result = BytesIO()
1037        parser.setContentHandler(XMLGenerator(result))
1038
1039        parser.feed('<!DOCTYPE doc [\n')
1040        parser.feed('  <!ENTITY test SYSTEM "whatever">\n')
1041        parser.feed(']>\n')
1042        parser.feed('<doc>&test;</doc>')
1043        parser.close()
1044
1045        self.assertEqual(result.getvalue(), start +
1046                         b"<doc></doc>")
1047
1048    # ===== Attributes support
1049
1050    class AttrGatherer(ContentHandler):
1051
1052        def startElement(self, name, attrs):
1053            self._attrs = attrs
1054
1055        def startElementNS(self, name, qname, attrs):
1056            self._attrs = attrs
1057
1058    def test_expat_attrs_empty(self):
1059        parser = create_parser()
1060        gather = self.AttrGatherer()
1061        parser.setContentHandler(gather)
1062
1063        parser.feed("<doc/>")
1064        parser.close()
1065
1066        self.verify_empty_attrs(gather._attrs)
1067
1068    def test_expat_attrs_wattr(self):
1069        parser = create_parser()
1070        gather = self.AttrGatherer()
1071        parser.setContentHandler(gather)
1072
1073        parser.feed("<doc attr='val'/>")
1074        parser.close()
1075
1076        self.verify_attrs_wattr(gather._attrs)
1077
1078    def test_expat_nsattrs_empty(self):
1079        parser = create_parser(1)
1080        gather = self.AttrGatherer()
1081        parser.setContentHandler(gather)
1082
1083        parser.feed("<doc/>")
1084        parser.close()
1085
1086        self.verify_empty_nsattrs(gather._attrs)
1087
1088    def test_expat_nsattrs_wattr(self):
1089        parser = create_parser(1)
1090        gather = self.AttrGatherer()
1091        parser.setContentHandler(gather)
1092
1093        parser.feed("<doc xmlns:ns='%s' ns:attr='val'/>" % ns_uri)
1094        parser.close()
1095
1096        attrs = gather._attrs
1097
1098        self.assertEqual(attrs.getLength(), 1)
1099        self.assertEqual(attrs.getNames(), [(ns_uri, "attr")])
1100        self.assertTrue((attrs.getQNames() == [] or
1101                         attrs.getQNames() == ["ns:attr"]))
1102        self.assertEqual(len(attrs), 1)
1103        self.assertIn((ns_uri, "attr"), attrs)
1104        self.assertEqual(attrs.get((ns_uri, "attr")), "val")
1105        self.assertEqual(attrs.get((ns_uri, "attr"), 25), "val")
1106        self.assertEqual(list(attrs.items()), [((ns_uri, "attr"), "val")])
1107        self.assertEqual(list(attrs.values()), ["val"])
1108        self.assertEqual(attrs.getValue((ns_uri, "attr")), "val")
1109        self.assertEqual(attrs[(ns_uri, "attr")], "val")
1110
1111    # ===== InputSource support
1112
1113    def test_expat_inpsource_filename(self):
1114        parser = create_parser()
1115        result = BytesIO()
1116        xmlgen = XMLGenerator(result)
1117
1118        parser.setContentHandler(xmlgen)
1119        parser.parse(TEST_XMLFILE)
1120
1121        self.assertEqual(result.getvalue(), xml_test_out)
1122
1123    def test_expat_inpsource_sysid(self):
1124        parser = create_parser()
1125        result = BytesIO()
1126        xmlgen = XMLGenerator(result)
1127
1128        parser.setContentHandler(xmlgen)
1129        parser.parse(InputSource(TEST_XMLFILE))
1130
1131        self.assertEqual(result.getvalue(), xml_test_out)
1132
1133    @requires_nonascii_filenames
1134    def test_expat_inpsource_sysid_nonascii(self):
1135        fname = support.TESTFN_UNICODE
1136        shutil.copyfile(TEST_XMLFILE, fname)
1137        self.addCleanup(support.unlink, fname)
1138
1139        parser = create_parser()
1140        result = BytesIO()
1141        xmlgen = XMLGenerator(result)
1142
1143        parser.setContentHandler(xmlgen)
1144        parser.parse(InputSource(fname))
1145
1146        self.assertEqual(result.getvalue(), xml_test_out)
1147
1148    def test_expat_inpsource_byte_stream(self):
1149        parser = create_parser()
1150        result = BytesIO()
1151        xmlgen = XMLGenerator(result)
1152
1153        parser.setContentHandler(xmlgen)
1154        inpsrc = InputSource()
1155        with open(TEST_XMLFILE, 'rb') as f:
1156            inpsrc.setByteStream(f)
1157            parser.parse(inpsrc)
1158
1159        self.assertEqual(result.getvalue(), xml_test_out)
1160
1161    def test_expat_inpsource_character_stream(self):
1162        parser = create_parser()
1163        result = BytesIO()
1164        xmlgen = XMLGenerator(result)
1165
1166        parser.setContentHandler(xmlgen)
1167        inpsrc = InputSource()
1168        with open(TEST_XMLFILE, 'rt', encoding='iso-8859-1') as f:
1169            inpsrc.setCharacterStream(f)
1170            parser.parse(inpsrc)
1171
1172        self.assertEqual(result.getvalue(), xml_test_out)
1173
1174    # ===== IncrementalParser support
1175
1176    def test_expat_incremental(self):
1177        result = BytesIO()
1178        xmlgen = XMLGenerator(result)
1179        parser = create_parser()
1180        parser.setContentHandler(xmlgen)
1181
1182        parser.feed("<doc>")
1183        parser.feed("</doc>")
1184        parser.close()
1185
1186        self.assertEqual(result.getvalue(), start + b"<doc></doc>")
1187
1188    def test_expat_incremental_reset(self):
1189        result = BytesIO()
1190        xmlgen = XMLGenerator(result)
1191        parser = create_parser()
1192        parser.setContentHandler(xmlgen)
1193
1194        parser.feed("<doc>")
1195        parser.feed("text")
1196
1197        result = BytesIO()
1198        xmlgen = XMLGenerator(result)
1199        parser.setContentHandler(xmlgen)
1200        parser.reset()
1201
1202        parser.feed("<doc>")
1203        parser.feed("text")
1204        parser.feed("</doc>")
1205        parser.close()
1206
1207        self.assertEqual(result.getvalue(), start + b"<doc>text</doc>")
1208
1209    # ===== Locator support
1210
1211    def test_expat_locator_noinfo(self):
1212        result = BytesIO()
1213        xmlgen = XMLGenerator(result)
1214        parser = create_parser()
1215        parser.setContentHandler(xmlgen)
1216
1217        parser.feed("<doc>")
1218        parser.feed("</doc>")
1219        parser.close()
1220
1221        self.assertEqual(parser.getSystemId(), None)
1222        self.assertEqual(parser.getPublicId(), None)
1223        self.assertEqual(parser.getLineNumber(), 1)
1224
1225    def test_expat_locator_withinfo(self):
1226        result = BytesIO()
1227        xmlgen = XMLGenerator(result)
1228        parser = create_parser()
1229        parser.setContentHandler(xmlgen)
1230        parser.parse(TEST_XMLFILE)
1231
1232        self.assertEqual(parser.getSystemId(), TEST_XMLFILE)
1233        self.assertEqual(parser.getPublicId(), None)
1234
1235    @requires_nonascii_filenames
1236    def test_expat_locator_withinfo_nonascii(self):
1237        fname = support.TESTFN_UNICODE
1238        shutil.copyfile(TEST_XMLFILE, fname)
1239        self.addCleanup(support.unlink, fname)
1240
1241        result = BytesIO()
1242        xmlgen = XMLGenerator(result)
1243        parser = create_parser()
1244        parser.setContentHandler(xmlgen)
1245        parser.parse(fname)
1246
1247        self.assertEqual(parser.getSystemId(), fname)
1248        self.assertEqual(parser.getPublicId(), None)
1249
1250
1251# ===========================================================================
1252#
1253#   error reporting
1254#
1255# ===========================================================================
1256
1257class ErrorReportingTest(unittest.TestCase):
1258    def test_expat_inpsource_location(self):
1259        parser = create_parser()
1260        parser.setContentHandler(ContentHandler()) # do nothing
1261        source = InputSource()
1262        source.setByteStream(BytesIO(b"<foo bar foobar>"))   #ill-formed
1263        name = "a file name"
1264        source.setSystemId(name)
1265        try:
1266            parser.parse(source)
1267            self.fail()
1268        except SAXException as e:
1269            self.assertEqual(e.getSystemId(), name)
1270
1271    def test_expat_incomplete(self):
1272        parser = create_parser()
1273        parser.setContentHandler(ContentHandler()) # do nothing
1274        self.assertRaises(SAXParseException, parser.parse, StringIO("<foo>"))
1275        self.assertEqual(parser.getColumnNumber(), 5)
1276        self.assertEqual(parser.getLineNumber(), 1)
1277
1278    def test_sax_parse_exception_str(self):
1279        # pass various values from a locator to the SAXParseException to
1280        # make sure that the __str__() doesn't fall apart when None is
1281        # passed instead of an integer line and column number
1282        #
1283        # use "normal" values for the locator:
1284        str(SAXParseException("message", None,
1285                              self.DummyLocator(1, 1)))
1286        # use None for the line number:
1287        str(SAXParseException("message", None,
1288                              self.DummyLocator(None, 1)))
1289        # use None for the column number:
1290        str(SAXParseException("message", None,
1291                              self.DummyLocator(1, None)))
1292        # use None for both:
1293        str(SAXParseException("message", None,
1294                              self.DummyLocator(None, None)))
1295
1296    class DummyLocator:
1297        def __init__(self, lineno, colno):
1298            self._lineno = lineno
1299            self._colno = colno
1300
1301        def getPublicId(self):
1302            return "pubid"
1303
1304        def getSystemId(self):
1305            return "sysid"
1306
1307        def getLineNumber(self):
1308            return self._lineno
1309
1310        def getColumnNumber(self):
1311            return self._colno
1312
1313# ===========================================================================
1314#
1315#   xmlreader tests
1316#
1317# ===========================================================================
1318
1319class XmlReaderTest(XmlTestBase):
1320
1321    # ===== AttributesImpl
1322    def test_attrs_empty(self):
1323        self.verify_empty_attrs(AttributesImpl({}))
1324
1325    def test_attrs_wattr(self):
1326        self.verify_attrs_wattr(AttributesImpl({"attr" : "val"}))
1327
1328    def test_nsattrs_empty(self):
1329        self.verify_empty_nsattrs(AttributesNSImpl({}, {}))
1330
1331    def test_nsattrs_wattr(self):
1332        attrs = AttributesNSImpl({(ns_uri, "attr") : "val"},
1333                                 {(ns_uri, "attr") : "ns:attr"})
1334
1335        self.assertEqual(attrs.getLength(), 1)
1336        self.assertEqual(attrs.getNames(), [(ns_uri, "attr")])
1337        self.assertEqual(attrs.getQNames(), ["ns:attr"])
1338        self.assertEqual(len(attrs), 1)
1339        self.assertIn((ns_uri, "attr"), attrs)
1340        self.assertEqual(list(attrs.keys()), [(ns_uri, "attr")])
1341        self.assertEqual(attrs.get((ns_uri, "attr")), "val")
1342        self.assertEqual(attrs.get((ns_uri, "attr"), 25), "val")
1343        self.assertEqual(list(attrs.items()), [((ns_uri, "attr"), "val")])
1344        self.assertEqual(list(attrs.values()), ["val"])
1345        self.assertEqual(attrs.getValue((ns_uri, "attr")), "val")
1346        self.assertEqual(attrs.getValueByQName("ns:attr"), "val")
1347        self.assertEqual(attrs.getNameByQName("ns:attr"), (ns_uri, "attr"))
1348        self.assertEqual(attrs[(ns_uri, "attr")], "val")
1349        self.assertEqual(attrs.getQNameByName((ns_uri, "attr")), "ns:attr")
1350
1351
1352def test_main():
1353    run_unittest(MakeParserTest,
1354                 ParseTest,
1355                 SaxutilsTest,
1356                 PrepareInputSourceTest,
1357                 StringXmlgenTest,
1358                 BytesXmlgenTest,
1359                 WriterXmlgenTest,
1360                 StreamWriterXmlgenTest,
1361                 StreamReaderWriterXmlgenTest,
1362                 ExpatReaderTest,
1363                 ErrorReportingTest,
1364                 XmlReaderTest)
1365
1366if __name__ == "__main__":
1367    test_main()
1368