1
# regression test for SAX 2.0 -*- coding: iso-8859-1 -*-
2
# $Id: test_sax.py 66203 2008-09-04 02:22:52Z benjamin.peterson $
4
from xml.sax import make_parser, ContentHandler, \
5
SAXException, SAXReaderNotAvailable, SAXParseException
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, \
13
from xml.sax.expatreader import create_parser
14
from xml.sax.xmlreader import InputSource, AttributesImpl, AttributesNSImpl
15
from io import StringIO
16
from test.support import findfile, run_unittest
20
ns_uri = "http://www.python.org/xml-ns/saxtest/"
22
class XmlTestBase(unittest.TestCase):
23
def verify_empty_attrs(self, attrs):
24
self.assertRaises(KeyError, attrs.getValue, "attr")
25
self.assertRaises(KeyError, attrs.getValueByQName, "attr")
26
self.assertRaises(KeyError, attrs.getNameByQName, "attr")
27
self.assertRaises(KeyError, attrs.getQNameByName, "attr")
28
self.assertRaises(KeyError, attrs.__getitem__, "attr")
29
self.assertEquals(attrs.getLength(), 0)
30
self.assertEquals(attrs.getNames(), [])
31
self.assertEquals(attrs.getQNames(), [])
32
self.assertEquals(len(attrs), 0)
33
self.assertFalse("attr" in attrs)
34
self.assertEquals(list(attrs.keys()), [])
35
self.assertEquals(attrs.get("attrs"), None)
36
self.assertEquals(attrs.get("attrs", 25), 25)
37
self.assertEquals(list(attrs.items()), [])
38
self.assertEquals(list(attrs.values()), [])
40
def verify_empty_nsattrs(self, attrs):
41
self.assertRaises(KeyError, attrs.getValue, (ns_uri, "attr"))
42
self.assertRaises(KeyError, attrs.getValueByQName, "ns:attr")
43
self.assertRaises(KeyError, attrs.getNameByQName, "ns:attr")
44
self.assertRaises(KeyError, attrs.getQNameByName, (ns_uri, "attr"))
45
self.assertRaises(KeyError, attrs.__getitem__, (ns_uri, "attr"))
46
self.assertEquals(attrs.getLength(), 0)
47
self.assertEquals(attrs.getNames(), [])
48
self.assertEquals(attrs.getQNames(), [])
49
self.assertEquals(len(attrs), 0)
50
self.assertFalse((ns_uri, "attr") in attrs)
51
self.assertEquals(list(attrs.keys()), [])
52
self.assertEquals(attrs.get((ns_uri, "attr")), None)
53
self.assertEquals(attrs.get((ns_uri, "attr"), 25), 25)
54
self.assertEquals(list(attrs.items()), [])
55
self.assertEquals(list(attrs.values()), [])
57
def verify_attrs_wattr(self, attrs):
58
self.assertEquals(attrs.getLength(), 1)
59
self.assertEquals(attrs.getNames(), ["attr"])
60
self.assertEquals(attrs.getQNames(), ["attr"])
61
self.assertEquals(len(attrs), 1)
62
self.assertTrue("attr" in attrs)
63
self.assertEquals(list(attrs.keys()), ["attr"])
64
self.assertEquals(attrs.get("attr"), "val")
65
self.assertEquals(attrs.get("attr", 25), "val")
66
self.assertEquals(list(attrs.items()), [("attr", "val")])
67
self.assertEquals(list(attrs.values()), ["val"])
68
self.assertEquals(attrs.getValue("attr"), "val")
69
self.assertEquals(attrs.getValueByQName("attr"), "val")
70
self.assertEquals(attrs.getNameByQName("attr"), "attr")
71
self.assertEquals(attrs["attr"], "val")
72
self.assertEquals(attrs.getQNameByName("attr"), "attr")
74
class MakeParserTest(unittest.TestCase):
75
def test_make_parser2(self):
76
# Creating parsers several times in a row should succeed.
77
# Testing this because there have been failures of this kind
79
from xml.sax import make_parser
81
from xml.sax import make_parser
83
from xml.sax import make_parser
85
from xml.sax import make_parser
87
from xml.sax import make_parser
89
from xml.sax import make_parser
93
# ===========================================================================
97
# ===========================================================================
99
class SaxutilsTest(unittest.TestCase):
101
def test_escape_basic(self):
102
self.assertEquals(escape("Donald Duck & Co"), "Donald Duck & Co")
104
def test_escape_all(self):
105
self.assertEquals(escape("<Donald Duck & Co>"),
106
"<Donald Duck & Co>")
108
def test_escape_extra(self):
109
self.assertEquals(escape("Hei pļæ½ deg", {"ļæ½" : "å"}),
113
def test_unescape_basic(self):
114
self.assertEquals(unescape("Donald Duck & Co"), "Donald Duck & Co")
116
def test_unescape_all(self):
117
self.assertEquals(unescape("<Donald Duck & Co>"),
118
"<Donald Duck & Co>")
120
def test_unescape_extra(self):
121
self.assertEquals(unescape("Hei pļæ½ deg", {"ļæ½" : "å"}),
124
def test_unescape_amp_extra(self):
125
self.assertEquals(unescape("&foo;", {"&foo;": "splat"}), "&foo;")
128
def test_quoteattr_basic(self):
129
self.assertEquals(quoteattr("Donald Duck & Co"),
130
'"Donald Duck & Co"')
132
def test_single_quoteattr(self):
133
self.assertEquals(quoteattr('Includes "double" quotes'),
134
'\'Includes "double" quotes\'')
136
def test_double_quoteattr(self):
137
self.assertEquals(quoteattr("Includes 'single' quotes"),
138
"\"Includes 'single' quotes\"")
140
def test_single_double_quoteattr(self):
141
self.assertEquals(quoteattr("Includes 'single' and \"double\" quotes"),
142
"\"Includes 'single' and "double" quotes\"")
145
def test_make_parser(self):
146
# Creating a parser should succeed - it should fall back
148
p = make_parser(['xml.parsers.no_such_parser'])
153
start = '<?xml version="1.0" encoding="iso-8859-1"?>\n'
155
class XmlgenTest(unittest.TestCase):
156
def test_xmlgen_basic(self):
158
gen = XMLGenerator(result)
160
gen.startElement("doc", {})
161
gen.endElement("doc")
164
self.assertEquals(result.getvalue(), start + "<doc></doc>")
166
def test_xmlgen_content(self):
168
gen = XMLGenerator(result)
171
gen.startElement("doc", {})
172
gen.characters("huhei")
173
gen.endElement("doc")
176
self.assertEquals(result.getvalue(), start + "<doc>huhei</doc>")
178
def test_xmlgen_pi(self):
180
gen = XMLGenerator(result)
183
gen.processingInstruction("test", "data")
184
gen.startElement("doc", {})
185
gen.endElement("doc")
188
self.assertEquals(result.getvalue(), start + "<?test data?><doc></doc>")
190
def test_xmlgen_content_escape(self):
192
gen = XMLGenerator(result)
195
gen.startElement("doc", {})
196
gen.characters("<huhei&")
197
gen.endElement("doc")
200
self.assertEquals(result.getvalue(),
201
start + "<doc><huhei&</doc>")
203
def test_xmlgen_attr_escape(self):
205
gen = XMLGenerator(result)
208
gen.startElement("doc", {"a": '"'})
209
gen.startElement("e", {"a": "'"})
211
gen.startElement("e", {"a": "'\""})
213
gen.startElement("e", {"a": "\n\r\t"})
215
gen.endElement("doc")
218
self.assertEquals(result.getvalue(), start +
219
("<doc a='\"'><e a=\"'\"></e>"
220
"<e a=\"'"\"></e>"
221
"<e a=\" 	\"></e></doc>"))
223
def test_xmlgen_ignorable(self):
225
gen = XMLGenerator(result)
228
gen.startElement("doc", {})
229
gen.ignorableWhitespace(" ")
230
gen.endElement("doc")
233
self.assertEquals(result.getvalue(), start + "<doc> </doc>")
235
def test_xmlgen_ns(self):
237
gen = XMLGenerator(result)
240
gen.startPrefixMapping("ns1", ns_uri)
241
gen.startElementNS((ns_uri, "doc"), "ns1:doc", {})
242
# add an unqualified name
243
gen.startElementNS((None, "udoc"), None, {})
244
gen.endElementNS((None, "udoc"), None)
245
gen.endElementNS((ns_uri, "doc"), "ns1:doc")
246
gen.endPrefixMapping("ns1")
249
self.assertEquals(result.getvalue(), start + \
250
('<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
253
def test_1463026_1(self):
255
gen = XMLGenerator(result)
258
gen.startElementNS((None, 'a'), 'a', {(None, 'b'):'c'})
259
gen.endElementNS((None, 'a'), 'a')
262
self.assertEquals(result.getvalue(), start+'<a b="c"></a>')
264
def test_1463026_2(self):
266
gen = XMLGenerator(result)
269
gen.startPrefixMapping(None, 'qux')
270
gen.startElementNS(('qux', 'a'), 'a', {})
271
gen.endElementNS(('qux', 'a'), 'a')
272
gen.endPrefixMapping(None)
275
self.assertEquals(result.getvalue(), start+'<a xmlns="qux"></a>')
277
def test_1463026_3(self):
279
gen = XMLGenerator(result)
282
gen.startPrefixMapping('my', 'qux')
283
gen.startElementNS(('qux', 'a'), 'a', {(None, 'b'):'c'})
284
gen.endElementNS(('qux', 'a'), 'a')
285
gen.endPrefixMapping('my')
288
self.assertEquals(result.getvalue(),
289
start+'<my:a xmlns:my="qux" b="c"></my:a>')
292
class XMLFilterBaseTest(unittest.TestCase):
293
def test_filter_basic(self):
295
gen = XMLGenerator(result)
296
filter = XMLFilterBase()
297
filter.setContentHandler(gen)
299
filter.startDocument()
300
filter.startElement("doc", {})
301
filter.characters("content")
302
filter.ignorableWhitespace(" ")
303
filter.endElement("doc")
306
self.assertEquals(result.getvalue(), start + "<doc>content </doc>")
308
# ===========================================================================
312
# ===========================================================================
314
xml_test_out = open(findfile("test.xml.out")).read()
316
class ExpatReaderTest(XmlTestBase):
318
# ===== XMLReader support
320
def test_expat_file(self):
321
parser = create_parser()
323
xmlgen = XMLGenerator(result)
325
parser.setContentHandler(xmlgen)
326
parser.parse(open(findfile("test.xml")))
328
self.assertEquals(result.getvalue(), xml_test_out)
330
# ===== DTDHandler support
332
class TestDTDHandler:
338
def notationDecl(self, name, publicId, systemId):
339
self._notations.append((name, publicId, systemId))
341
def unparsedEntityDecl(self, name, publicId, systemId, ndata):
342
self._entities.append((name, publicId, systemId, ndata))
344
def test_expat_dtdhandler(self):
345
parser = create_parser()
346
handler = self.TestDTDHandler()
347
parser.setDTDHandler(handler)
349
parser.feed('<!DOCTYPE doc [\n')
350
parser.feed(' <!ENTITY img SYSTEM "expat.gif" NDATA GIF>\n')
351
parser.feed(' <!NOTATION GIF PUBLIC "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN">\n')
353
parser.feed('<doc></doc>')
356
self.assertEquals(handler._notations,
357
[("GIF", "-//CompuServe//NOTATION Graphics Interchange Format 89a//EN", None)])
358
self.assertEquals(handler._entities, [("img", None, "expat.gif", "GIF")])
360
# ===== EntityResolver support
362
class TestEntityResolver:
364
def resolveEntity(self, publicId, systemId):
365
inpsrc = InputSource()
366
inpsrc.setByteStream(StringIO("<entity/>"))
369
def test_expat_entityresolver(self):
370
parser = create_parser()
371
parser.setEntityResolver(self.TestEntityResolver())
373
parser.setContentHandler(XMLGenerator(result))
375
parser.feed('<!DOCTYPE doc [\n')
376
parser.feed(' <!ENTITY test SYSTEM "whatever">\n')
378
parser.feed('<doc>&test;</doc>')
381
self.assertEquals(result.getvalue(), start +
382
"<doc><entity></entity></doc>")
384
# ===== Attributes support
386
class AttrGatherer(ContentHandler):
388
def startElement(self, name, attrs):
391
def startElementNS(self, name, qname, attrs):
394
def test_expat_attrs_empty(self):
395
parser = create_parser()
396
gather = self.AttrGatherer()
397
parser.setContentHandler(gather)
399
parser.feed("<doc/>")
402
self.verify_empty_attrs(gather._attrs)
404
def test_expat_attrs_wattr(self):
405
parser = create_parser()
406
gather = self.AttrGatherer()
407
parser.setContentHandler(gather)
409
parser.feed("<doc attr='val'/>")
412
self.verify_attrs_wattr(gather._attrs)
414
def test_expat_nsattrs_empty(self):
415
parser = create_parser(1)
416
gather = self.AttrGatherer()
417
parser.setContentHandler(gather)
419
parser.feed("<doc/>")
422
self.verify_empty_nsattrs(gather._attrs)
424
def test_expat_nsattrs_wattr(self):
425
parser = create_parser(1)
426
gather = self.AttrGatherer()
427
parser.setContentHandler(gather)
429
parser.feed("<doc xmlns:ns='%s' ns:attr='val'/>" % ns_uri)
432
attrs = gather._attrs
434
self.assertEquals(attrs.getLength(), 1)
435
self.assertEquals(attrs.getNames(), [(ns_uri, "attr")])
436
self.assertTrue((attrs.getQNames() == [] or
437
attrs.getQNames() == ["ns:attr"]))
438
self.assertEquals(len(attrs), 1)
439
self.assertTrue((ns_uri, "attr") in attrs)
440
self.assertEquals(attrs.get((ns_uri, "attr")), "val")
441
self.assertEquals(attrs.get((ns_uri, "attr"), 25), "val")
442
self.assertEquals(list(attrs.items()), [((ns_uri, "attr"), "val")])
443
self.assertEquals(list(attrs.values()), ["val"])
444
self.assertEquals(attrs.getValue((ns_uri, "attr")), "val")
445
self.assertEquals(attrs[(ns_uri, "attr")], "val")
447
# ===== InputSource support
449
def test_expat_inpsource_filename(self):
450
parser = create_parser()
452
xmlgen = XMLGenerator(result)
454
parser.setContentHandler(xmlgen)
455
parser.parse(findfile("test.xml"))
457
self.assertEquals(result.getvalue(), xml_test_out)
459
def test_expat_inpsource_sysid(self):
460
parser = create_parser()
462
xmlgen = XMLGenerator(result)
464
parser.setContentHandler(xmlgen)
465
parser.parse(InputSource(findfile("test.xml")))
467
self.assertEquals(result.getvalue(), xml_test_out)
469
def test_expat_inpsource_stream(self):
470
parser = create_parser()
472
xmlgen = XMLGenerator(result)
474
parser.setContentHandler(xmlgen)
475
inpsrc = InputSource()
476
inpsrc.setByteStream(open(findfile("test.xml")))
479
self.assertEquals(result.getvalue(), xml_test_out)
481
# ===== IncrementalParser support
483
def test_expat_incremental(self):
485
xmlgen = XMLGenerator(result)
486
parser = create_parser()
487
parser.setContentHandler(xmlgen)
490
parser.feed("</doc>")
493
self.assertEquals(result.getvalue(), start + "<doc></doc>")
495
def test_expat_incremental_reset(self):
497
xmlgen = XMLGenerator(result)
498
parser = create_parser()
499
parser.setContentHandler(xmlgen)
505
xmlgen = XMLGenerator(result)
506
parser.setContentHandler(xmlgen)
511
parser.feed("</doc>")
514
self.assertEquals(result.getvalue(), start + "<doc>text</doc>")
516
# ===== Locator support
518
def test_expat_locator_noinfo(self):
520
xmlgen = XMLGenerator(result)
521
parser = create_parser()
522
parser.setContentHandler(xmlgen)
525
parser.feed("</doc>")
528
self.assertEquals(parser.getSystemId(), None)
529
self.assertEquals(parser.getPublicId(), None)
530
self.assertEquals(parser.getLineNumber(), 1)
532
def test_expat_locator_withinfo(self):
534
xmlgen = XMLGenerator(result)
535
parser = create_parser()
536
parser.setContentHandler(xmlgen)
537
parser.parse(findfile("test.xml"))
539
self.assertEquals(parser.getSystemId(), findfile("test.xml"))
540
self.assertEquals(parser.getPublicId(), None)
543
# ===========================================================================
547
# ===========================================================================
549
class ErrorReportingTest(unittest.TestCase):
550
def test_expat_inpsource_location(self):
551
parser = create_parser()
552
parser.setContentHandler(ContentHandler()) # do nothing
553
source = InputSource()
554
source.setByteStream(StringIO("<foo bar foobar>")) #ill-formed
556
source.setSystemId(name)
560
except SAXException as e:
561
self.assertEquals(e.getSystemId(), name)
563
def test_expat_incomplete(self):
564
parser = create_parser()
565
parser.setContentHandler(ContentHandler()) # do nothing
566
self.assertRaises(SAXParseException, parser.parse, StringIO("<foo>"))
568
def test_sax_parse_exception_str(self):
569
# pass various values from a locator to the SAXParseException to
570
# make sure that the __str__() doesn't fall apart when None is
571
# passed instead of an integer line and column number
573
# use "normal" values for the locator:
574
str(SAXParseException("message", None,
575
self.DummyLocator(1, 1)))
576
# use None for the line number:
577
str(SAXParseException("message", None,
578
self.DummyLocator(None, 1)))
579
# use None for the column number:
580
str(SAXParseException("message", None,
581
self.DummyLocator(1, None)))
583
str(SAXParseException("message", None,
584
self.DummyLocator(None, None)))
587
def __init__(self, lineno, colno):
588
self._lineno = lineno
591
def getPublicId(self):
594
def getSystemId(self):
597
def getLineNumber(self):
600
def getColumnNumber(self):
603
# ===========================================================================
607
# ===========================================================================
609
class XmlReaderTest(XmlTestBase):
611
# ===== AttributesImpl
612
def test_attrs_empty(self):
613
self.verify_empty_attrs(AttributesImpl({}))
615
def test_attrs_wattr(self):
616
self.verify_attrs_wattr(AttributesImpl({"attr" : "val"}))
618
def test_nsattrs_empty(self):
619
self.verify_empty_nsattrs(AttributesNSImpl({}, {}))
621
def test_nsattrs_wattr(self):
622
attrs = AttributesNSImpl({(ns_uri, "attr") : "val"},
623
{(ns_uri, "attr") : "ns:attr"})
625
self.assertEquals(attrs.getLength(), 1)
626
self.assertEquals(attrs.getNames(), [(ns_uri, "attr")])
627
self.assertEquals(attrs.getQNames(), ["ns:attr"])
628
self.assertEquals(len(attrs), 1)
629
self.assertTrue((ns_uri, "attr") in attrs)
630
self.assertEquals(list(attrs.keys()), [(ns_uri, "attr")])
631
self.assertEquals(attrs.get((ns_uri, "attr")), "val")
632
self.assertEquals(attrs.get((ns_uri, "attr"), 25), "val")
633
self.assertEquals(list(attrs.items()), [((ns_uri, "attr"), "val")])
634
self.assertEquals(list(attrs.values()), ["val"])
635
self.assertEquals(attrs.getValue((ns_uri, "attr")), "val")
636
self.assertEquals(attrs.getValueByQName("ns:attr"), "val")
637
self.assertEquals(attrs.getNameByQName("ns:attr"), (ns_uri, "attr"))
638
self.assertEquals(attrs[(ns_uri, "attr")], "val")
639
self.assertEquals(attrs.getQNameByName((ns_uri, "attr")), "ns:attr")
642
# During the development of Python 2.5, an attempt to move the "xml"
643
# package implementation to a new package ("xmlcore") proved painful.
644
# The goal of this change was to allow applications to be able to
645
# obtain and rely on behavior in the standard library implementation
646
# of the XML support without needing to be concerned about the
647
# availability of the PyXML implementation.
649
# While the existing import hackery in Lib/xml/__init__.py can cause
650
# PyXML's _xmlpus package to supplant the "xml" package, that only
651
# works because either implementation uses the "xml" package name for
654
# The move resulted in a number of problems related to the fact that
655
# the import machinery's "package context" is based on the name that's
656
# being imported rather than the __name__ of the actual package
657
# containment; it wasn't possible for the "xml" package to be replaced
658
# by a simple module that indirected imports to the "xmlcore" package.
660
# The following two tests exercised bugs that were introduced in that
661
# attempt. Keeping these tests around will help detect problems with
662
# other attempts to provide reliable access to the standard library's
663
# implementation of the XML support.
665
def test_sf_1511497(self):
666
# Bug report: http://www.python.org/sf/1511497
668
old_modules = sys.modules.copy()
669
for modname in list(sys.modules.keys()):
670
if modname.startswith("xml."):
671
del sys.modules[modname]
673
import xml.sax.expatreader
674
module = xml.sax.expatreader
675
self.assertEquals(module.__name__, "xml.sax.expatreader")
677
sys.modules.update(old_modules)
679
def test_sf_1513611(self):
680
# Bug report: http://www.python.org/sf/1513611
681
sio = StringIO("invalid")
682
parser = make_parser()
683
from xml.sax import SAXParseException
684
self.assertRaises(SAXParseException, parser.parse, sio)
688
run_unittest(MakeParserTest,
695
if __name__ == "__main__":