# IMPORTANT: the same tests are run from "test_xml_etree_c" in order
# to ensure consistency between the C implementation and the Python
# implementation.
#
# For this purpose, the module-level "ET" symbol is temporarily
# monkey-patched when running the "test_xml_etree_c" test suite.
import copy
import functools
import html
import io
import itertools
import operator
import os
import pickle
import pyexpat
import sys
import textwrap
import types
import unittest
import warnings
import weakref
from functools import partial
from itertools import product, islice
from test import support
from test.support import os_helper
from test.support import warnings_helper
from test.support import findfile, gc_collect, swap_attr, swap_item
from test.support.import_helper import import_fresh_module
from test.support.os_helper import TESTFN
# pyET is the pure-Python implementation.
#
# ET is pyET in test_xml_etree and is the C accelerated version in
# test_xml_etree_c.
pyET = None
ET = None
SIMPLE_XMLFILE = findfile("simple.xml", subdir="xmltestdata")
try:
SIMPLE_XMLFILE.encode("utf-8")
except UnicodeEncodeError:
raise unittest.SkipTest("filename is not encodable to utf8")
SIMPLE_NS_XMLFILE = findfile("simple-ns.xml", subdir="xmltestdata")
UTF8_BUG_XMLFILE = findfile("expat224_utf8_bug.xml", subdir="xmltestdata")
SAMPLE_XML = """\
textsubtext
"""
SAMPLE_SECTION = """\
subtext
"""
SAMPLE_XML_NS = """
textsubtext
"""
SAMPLE_XML_NS_ELEMS = """
ApplesBananasAfrican Coffee Table80120
"""
ENTITY_XML = """\
%user-entities;
]>
&entity;
"""
EXTERNAL_ENTITY_XML = """\
]>
&entity;
"""
ATTLIST_XML = """\
]>
&qux;
"""
fails_with_expat_2_6_0 = (unittest.expectedFailure
if pyexpat.version_info >= (2, 6, 0) else
lambda test: test)
def checkwarnings(*filters, quiet=False):
def decorator(test):
def newtest(*args, **kwargs):
with warnings_helper.check_warnings(*filters, quiet=quiet):
test(*args, **kwargs)
functools.update_wrapper(newtest, test)
return newtest
return decorator
def convlinesep(data):
return data.replace(b'\n', os.linesep.encode())
class ModuleTest(unittest.TestCase):
def test_sanity(self):
# Import sanity.
from xml.etree import ElementTree
from xml.etree import ElementInclude
from xml.etree import ElementPath
def test_all(self):
names = ("xml.etree.ElementTree", "_elementtree")
support.check__all__(self, ET, names, not_exported=("HTML_EMPTY",))
def serialize(elem, to_string=True, encoding='unicode', **options):
if encoding != 'unicode':
file = io.BytesIO()
else:
file = io.StringIO()
tree = ET.ElementTree(elem)
tree.write(file, encoding=encoding, **options)
if to_string:
return file.getvalue()
else:
file.seek(0)
return file
def summarize_list(seq):
return [elem.tag for elem in seq]
class ElementTestCase:
@classmethod
def setUpClass(cls):
cls.modules = {pyET, ET}
def pickleRoundTrip(self, obj, name, dumper, loader, proto):
try:
with swap_item(sys.modules, name, dumper):
temp = pickle.dumps(obj, proto)
with swap_item(sys.modules, name, loader):
result = pickle.loads(temp)
except pickle.PicklingError as pe:
# pyET must be second, because pyET may be (equal to) ET.
human = dict([(ET, "cET"), (pyET, "pyET")])
raise support.TestFailed("Failed to round-trip %r from %r to %r"
% (obj,
human.get(dumper, dumper),
human.get(loader, loader))) from pe
return result
def assertEqualElements(self, alice, bob):
self.assertIsInstance(alice, (ET.Element, pyET.Element))
self.assertIsInstance(bob, (ET.Element, pyET.Element))
self.assertEqual(len(list(alice)), len(list(bob)))
for x, y in zip(alice, bob):
self.assertEqualElements(x, y)
properties = operator.attrgetter('tag', 'tail', 'text', 'attrib')
self.assertEqual(properties(alice), properties(bob))
# --------------------------------------------------------------------
# element tree tests
class ElementTreeTest(unittest.TestCase):
def serialize_check(self, elem, expected):
self.assertEqual(serialize(elem), expected)
def test_interface(self):
# Test element tree interface.
def check_element(element):
self.assertTrue(ET.iselement(element), msg="not an element")
direlem = dir(element)
for attr in 'tag', 'attrib', 'text', 'tail':
self.assertTrue(hasattr(element, attr),
msg='no %s member' % attr)
self.assertIn(attr, direlem,
msg='no %s visible by dir' % attr)
self.assertIsInstance(element.tag, str)
self.assertIsInstance(element.attrib, dict)
if element.text is not None:
self.assertIsInstance(element.text, str)
if element.tail is not None:
self.assertIsInstance(element.tail, str)
for elem in element:
check_element(elem)
element = ET.Element("tag")
check_element(element)
tree = ET.ElementTree(element)
check_element(tree.getroot())
element = ET.Element("t\xe4g", key="value")
tree = ET.ElementTree(element)
self.assertRegex(repr(element), r"^$")
element = ET.Element("tag", key="value")
# Make sure all standard element methods exist.
def check_method(method):
self.assertTrue(hasattr(method, '__call__'),
msg="%s not callable" % method)
check_method(element.append)
check_method(element.extend)
check_method(element.insert)
check_method(element.remove)
check_method(element.find)
check_method(element.iterfind)
check_method(element.findall)
check_method(element.findtext)
check_method(element.clear)
check_method(element.get)
check_method(element.set)
check_method(element.keys)
check_method(element.items)
check_method(element.iter)
check_method(element.itertext)
# These methods return an iterable. See bug 6472.
def check_iter(it):
check_method(it.__next__)
check_iter(element.iterfind("tag"))
check_iter(element.iterfind("*"))
check_iter(tree.iterfind("tag"))
check_iter(tree.iterfind("*"))
# These aliases are provided:
self.assertEqual(ET.XML, ET.fromstring)
self.assertEqual(ET.PI, ET.ProcessingInstruction)
def test_set_attribute(self):
element = ET.Element('tag')
self.assertEqual(element.tag, 'tag')
element.tag = 'Tag'
self.assertEqual(element.tag, 'Tag')
element.tag = 'TAG'
self.assertEqual(element.tag, 'TAG')
self.assertIsNone(element.text)
element.text = 'Text'
self.assertEqual(element.text, 'Text')
element.text = 'TEXT'
self.assertEqual(element.text, 'TEXT')
self.assertIsNone(element.tail)
element.tail = 'Tail'
self.assertEqual(element.tail, 'Tail')
element.tail = 'TAIL'
self.assertEqual(element.tail, 'TAIL')
self.assertEqual(element.attrib, {})
element.attrib = {'a': 'b', 'c': 'd'}
self.assertEqual(element.attrib, {'a': 'b', 'c': 'd'})
element.attrib = {'A': 'B', 'C': 'D'}
self.assertEqual(element.attrib, {'A': 'B', 'C': 'D'})
def test_simpleops(self):
# Basic method sanity checks.
elem = ET.XML("")
self.serialize_check(elem, '')
e = ET.Element("tag2")
elem.append(e)
self.serialize_check(elem, '')
elem.remove(e)
self.serialize_check(elem, '')
elem.insert(0, e)
self.serialize_check(elem, '')
elem.remove(e)
elem.extend([e])
self.serialize_check(elem, '')
elem.remove(e)
elem.extend(iter([e]))
self.serialize_check(elem, '')
elem.remove(e)
element = ET.Element("tag", key="value")
self.serialize_check(element, '') # 1
subelement = ET.Element("subtag")
element.append(subelement)
self.serialize_check(element, '') # 2
element.insert(0, subelement)
self.serialize_check(element,
'') # 3
element.remove(subelement)
self.serialize_check(element, '') # 4
element.remove(subelement)
self.serialize_check(element, '') # 5
with self.assertRaises(ValueError) as cm:
element.remove(subelement)
self.assertIn('not in list', str(cm.exception))
self.serialize_check(element, '') # 6
element[0:0] = [subelement, subelement, subelement]
self.serialize_check(element[1], '')
self.assertEqual(element[1:9], [element[1], element[2]])
self.assertEqual(element[:9:2], [element[0], element[2]])
del element[1:2]
self.serialize_check(element,
'')
def test_cdata(self):
# Test CDATA handling (etc).
self.serialize_check(ET.XML("hello"),
'hello')
self.serialize_check(ET.XML("hello"),
'hello')
self.serialize_check(ET.XML(""),
'hello')
def test_file_init(self):
stringfile = io.BytesIO(SAMPLE_XML.encode("utf-8"))
tree = ET.ElementTree(file=stringfile)
self.assertEqual(tree.find("tag").tag, 'tag')
self.assertEqual(tree.find("section/tag").tag, 'tag')
tree = ET.ElementTree(file=SIMPLE_XMLFILE)
self.assertEqual(tree.find("element").tag, 'element')
self.assertEqual(tree.find("element/../empty-element").tag,
'empty-element')
def test_path_cache(self):
# Check that the path cache behaves sanely.
from xml.etree import ElementPath
elem = ET.XML(SAMPLE_XML)
ElementPath._cache.clear()
for i in range(10): ET.ElementTree(elem).find('./'+str(i))
cache_len_10 = len(ElementPath._cache)
for i in range(10): ET.ElementTree(elem).find('./'+str(i))
self.assertEqual(len(ElementPath._cache), cache_len_10)
for i in range(20): ET.ElementTree(elem).find('./'+str(i))
self.assertGreater(len(ElementPath._cache), cache_len_10)
for i in range(600): ET.ElementTree(elem).find('./'+str(i))
self.assertLess(len(ElementPath._cache), 500)
def test_copy(self):
# Test copy handling (etc).
import copy
e1 = ET.XML("hello")
e2 = copy.copy(e1)
e3 = copy.deepcopy(e1)
e1.find("foo").tag = "bar"
self.serialize_check(e1, 'hello')
self.serialize_check(e2, 'hello')
self.serialize_check(e3, 'hello')
def test_attrib(self):
# Test attribute handling.
elem = ET.Element("tag")
elem.get("key") # 1.1
self.assertEqual(elem.get("key", "default"), 'default') # 1.2
elem.set("key", "value")
self.assertEqual(elem.get("key"), 'value') # 1.3
elem = ET.Element("tag", key="value")
self.assertEqual(elem.get("key"), 'value') # 2.1
self.assertEqual(elem.attrib, {'key': 'value'}) # 2.2
attrib = {"key": "value"}
elem = ET.Element("tag", attrib)
attrib.clear() # check for aliasing issues
self.assertEqual(elem.get("key"), 'value') # 3.1
self.assertEqual(elem.attrib, {'key': 'value'}) # 3.2
attrib = {"key": "value"}
elem = ET.Element("tag", **attrib)
attrib.clear() # check for aliasing issues
self.assertEqual(elem.get("key"), 'value') # 4.1
self.assertEqual(elem.attrib, {'key': 'value'}) # 4.2
elem = ET.Element("tag", {"key": "other"}, key="value")
self.assertEqual(elem.get("key"), 'value') # 5.1
self.assertEqual(elem.attrib, {'key': 'value'}) # 5.2
elem = ET.Element('test')
elem.text = "aa"
elem.set('testa', 'testval')
elem.set('testb', 'test2')
self.assertEqual(ET.tostring(elem),
b'aa')
self.assertEqual(sorted(elem.keys()), ['testa', 'testb'])
self.assertEqual(sorted(elem.items()),
[('testa', 'testval'), ('testb', 'test2')])
self.assertEqual(elem.attrib['testb'], 'test2')
elem.attrib['testb'] = 'test1'
elem.attrib['testc'] = 'test2'
self.assertEqual(ET.tostring(elem),
b'aa')
# Test preserving white space chars in attributes
elem = ET.Element('test')
elem.set('a', '\r')
elem.set('b', '\r\n')
elem.set('c', '\t\n\r ')
elem.set('d', '\n\n\r\r\t\t ')
self.assertEqual(ET.tostring(elem),
b'')
def test_makeelement(self):
# Test makeelement handling.
elem = ET.Element("tag")
attrib = {"key": "value"}
subelem = elem.makeelement("subtag", attrib)
self.assertIsNot(subelem.attrib, attrib, msg="attrib aliasing")
elem.append(subelem)
self.serialize_check(elem, '')
elem.clear()
self.serialize_check(elem, '')
elem.append(subelem)
self.serialize_check(elem, '')
elem.extend([subelem, subelem])
self.serialize_check(elem,
'')
elem[:] = [subelem]
self.serialize_check(elem, '')
elem[:] = tuple([subelem])
self.serialize_check(elem, '')
def test_parsefile(self):
# Test parsing from file.
tree = ET.parse(SIMPLE_XMLFILE)
stream = io.StringIO()
tree.write(stream, encoding='unicode')
self.assertEqual(stream.getvalue(),
'\n'
' text\n'
' texttail\n'
' \n'
'')
tree = ET.parse(SIMPLE_NS_XMLFILE)
stream = io.StringIO()
tree.write(stream, encoding='unicode')
self.assertEqual(stream.getvalue(),
'\n'
' text\n'
' texttail\n'
' \n'
'')
with open(SIMPLE_XMLFILE) as f:
data = f.read()
parser = ET.XMLParser()
self.assertRegex(parser.version, r'^Expat ')
parser.feed(data)
self.serialize_check(parser.close(),
'\n'
' text\n'
' texttail\n'
' \n'
'')
target = ET.TreeBuilder()
parser = ET.XMLParser(target=target)
parser.feed(data)
self.serialize_check(parser.close(),
'\n'
' text\n'
' texttail\n'
' \n'
'')
def test_parseliteral(self):
element = ET.XML("text")
self.assertEqual(ET.tostring(element, encoding='unicode'),
'text')
element = ET.fromstring("text")
self.assertEqual(ET.tostring(element, encoding='unicode'),
'text')
sequence = ["", "text"]
element = ET.fromstringlist(sequence)
self.assertEqual(ET.tostring(element),
b'text')
self.assertEqual(b"".join(ET.tostringlist(element)),
b'text')
self.assertEqual(ET.tostring(element, "ascii"),
b"\n"
b"text")
_, ids = ET.XMLID("text")
self.assertEqual(len(ids), 0)
_, ids = ET.XMLID("text")
self.assertEqual(len(ids), 1)
self.assertEqual(ids["body"].tag, 'body')
def test_iterparse(self):
# Test iterparse interface.
iterparse = ET.iterparse
context = iterparse(SIMPLE_XMLFILE)
self.assertIsNone(context.root)
action, elem = next(context)
self.assertIsNone(context.root)
self.assertEqual((action, elem.tag), ('end', 'element'))
self.assertEqual([(action, elem.tag) for action, elem in context], [
('end', 'element'),
('end', 'empty-element'),
('end', 'root'),
])
self.assertEqual(context.root.tag, 'root')
context = iterparse(SIMPLE_NS_XMLFILE)
self.assertEqual([(action, elem.tag) for action, elem in context], [
('end', '{namespace}element'),
('end', '{namespace}element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
])
with open(SIMPLE_XMLFILE, 'rb') as source:
context = iterparse(source)
action, elem = next(context)
self.assertEqual((action, elem.tag), ('end', 'element'))
self.assertEqual([(action, elem.tag) for action, elem in context], [
('end', 'element'),
('end', 'empty-element'),
('end', 'root'),
])
self.assertEqual(context.root.tag, 'root')
events = ()
context = iterparse(SIMPLE_XMLFILE, events)
self.assertEqual([(action, elem.tag) for action, elem in context], [])
events = ()
context = iterparse(SIMPLE_XMLFILE, events=events)
self.assertEqual([(action, elem.tag) for action, elem in context], [])
events = ("start", "end")
context = iterparse(SIMPLE_XMLFILE, events)
self.assertEqual([(action, elem.tag) for action, elem in context], [
('start', 'root'),
('start', 'element'),
('end', 'element'),
('start', 'element'),
('end', 'element'),
('start', 'empty-element'),
('end', 'empty-element'),
('end', 'root'),
])
events = ("start", "end", "start-ns", "end-ns")
context = iterparse(SIMPLE_NS_XMLFILE, events)
self.assertEqual([(action, elem.tag) if action in ("start", "end")
else (action, elem)
for action, elem in context], [
('start-ns', ('', 'namespace')),
('start', '{namespace}root'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}empty-element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
('end-ns', None),
])
events = ('start-ns', 'end-ns')
context = iterparse(io.StringIO(r""), events)
res = [action for action, elem in context]
self.assertEqual(res, ['start-ns', 'end-ns'])
events = ("start", "end", "bogus")
with open(SIMPLE_XMLFILE, "rb") as f:
with self.assertRaises(ValueError) as cm:
iterparse(f, events)
self.assertFalse(f.closed)
self.assertEqual(str(cm.exception), "unknown event 'bogus'")
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ValueError) as cm:
iterparse(SIMPLE_XMLFILE, events)
self.assertEqual(str(cm.exception), "unknown event 'bogus'")
del cm
source = io.BytesIO(
b"\n"
b"text\n")
events = ("start-ns",)
context = iterparse(source, events)
self.assertEqual([(action, elem) for action, elem in context], [
('start-ns', ('', 'http://\xe9ffbot.org/ns')),
('start-ns', ('cl\xe9', 'http://effbot.org/ns')),
])
source = io.StringIO("junk")
it = iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
self.addCleanup(os_helper.unlink, TESTFN)
with open(TESTFN, "wb") as f:
f.write(b"junk")
it = iterparse(TESTFN)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
del cm, it
# Not exhausting the iterator still closes the resource (bpo-43292)
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
del it
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
it.close()
del it
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
it.close()
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
with self.assertRaises(FileNotFoundError):
iterparse("nonexistent")
def test_iterparse_close(self):
iterparse = ET.iterparse
it = iterparse(SIMPLE_XMLFILE)
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
it = iterparse(SIMPLE_XMLFILE)
list(it)
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
list(it)
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
def test_writefile(self):
elem = ET.Element("tag")
elem.text = "text"
self.serialize_check(elem, 'text')
ET.SubElement(elem, "subtag").text = "subtext"
self.serialize_check(elem, 'textsubtext')
# Test tag suppression
elem.tag = None
self.serialize_check(elem, 'textsubtext')
elem.insert(0, ET.Comment("comment"))
self.serialize_check(elem,
'textsubtext') # assumes 1.3
elem[0] = ET.PI("key", "value")
self.serialize_check(elem, 'textsubtext')
def test_custom_builder(self):
# Test parser w. custom builder.
with open(SIMPLE_XMLFILE) as f:
data = f.read()
class Builder(list):
def start(self, tag, attrib):
self.append(("start", tag))
def end(self, tag):
self.append(("end", tag))
def data(self, text):
pass
builder = Builder()
parser = ET.XMLParser(target=builder)
parser.feed(data)
self.assertEqual(builder, [
('start', 'root'),
('start', 'element'),
('end', 'element'),
('start', 'element'),
('end', 'element'),
('start', 'empty-element'),
('end', 'empty-element'),
('end', 'root'),
])
with open(SIMPLE_NS_XMLFILE) as f:
data = f.read()
class Builder(list):
def start(self, tag, attrib):
self.append(("start", tag))
def end(self, tag):
self.append(("end", tag))
def data(self, text):
pass
def pi(self, target, data):
self.append(("pi", target, data))
def comment(self, data):
self.append(("comment", data))
def start_ns(self, prefix, uri):
self.append(("start-ns", prefix, uri))
def end_ns(self, prefix):
self.append(("end-ns", prefix))
builder = Builder()
parser = ET.XMLParser(target=builder)
parser.feed(data)
self.assertEqual(builder, [
('pi', 'pi', 'data'),
('comment', ' comment '),
('start-ns', '', 'namespace'),
('start', '{namespace}root'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}empty-element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
('end-ns', ''),
])
def test_custom_builder_only_end_ns(self):
class Builder(list):
def end_ns(self, prefix):
self.append(("end-ns", prefix))
builder = Builder()
parser = ET.XMLParser(target=builder)
parser.feed(textwrap.dedent("""\
texttexttail
"""))
self.assertEqual(builder, [
('end-ns', 'a'),
('end-ns', 'p'),
('end-ns', ''),
])
def test_initialize_parser_without_target(self):
# Explicit None
parser = ET.XMLParser(target=None)
self.assertIsInstance(parser.target, ET.TreeBuilder)
# Implicit None
parser2 = ET.XMLParser()
self.assertIsInstance(parser2.target, ET.TreeBuilder)
def test_children(self):
# Test Element children iteration
with open(SIMPLE_XMLFILE, "rb") as f:
tree = ET.parse(f)
self.assertEqual([summarize_list(elem)
for elem in tree.getroot().iter()], [
['element', 'element', 'empty-element'],
[],
[],
[],
])
self.assertEqual([summarize_list(elem)
for elem in tree.iter()], [
['element', 'element', 'empty-element'],
[],
[],
[],
])
elem = ET.XML(SAMPLE_XML)
self.assertEqual(len(list(elem)), 3)
self.assertEqual(len(list(elem[2])), 1)
self.assertEqual(elem[:], list(elem))
child1 = elem[0]
child2 = elem[2]
del elem[1:2]
self.assertEqual(len(list(elem)), 2)
self.assertEqual(child1, elem[0])
self.assertEqual(child2, elem[1])
elem[0:2] = [child2, child1]
self.assertEqual(child2, elem[0])
self.assertEqual(child1, elem[1])
self.assertNotEqual(child1, elem[0])
elem.clear()
self.assertEqual(list(elem), [])
def test_writestring(self):
elem = ET.XML("text")
self.assertEqual(ET.tostring(elem), b'text')
elem = ET.fromstring("text")
self.assertEqual(ET.tostring(elem), b'text')
def test_indent(self):
elem = ET.XML("")
ET.indent(elem)
self.assertEqual(ET.tostring(elem), b'')
elem = ET.XML("text")
ET.indent(elem)
self.assertEqual(ET.tostring(elem), b'\n text\n')
elem = ET.XML(" text ")
ET.indent(elem)
self.assertEqual(ET.tostring(elem), b'\n text\n')
elem = ET.XML("texttail")
ET.indent(elem)
self.assertEqual(ET.tostring(elem), b'\n texttail')
elem = ET.XML("
par
\n
text
\t
")
ET.indent(elem)
self.assertEqual(
ET.tostring(elem),
b'\n'
b' \n'
b'
par
\n'
b'
text
\n'
b'
\n'
b' \n'
b'
\n'
b' \n'
b''
)
elem = ET.XML("
pre post
text
")
ET.indent(elem)
self.assertEqual(
ET.tostring(elem),
b'\n'
b' \n'
b'
pre post
\n'
b'
text
\n'
b' \n'
b''
)
def test_indent_space(self):
elem = ET.XML("
")
ET.indent(elem)
self.assertEqual(
{el.tail for el in elem.iter()},
{None, "\n", "\n ", "\n "}
)
self.assertEqual(
{el.text for el in elem.iter()},
{None, "\n ", "\n ", "\n ", "par", "text"}
)
self.assertEqual(
len({el.tail for el in elem.iter()}),
len({id(el.tail) for el in elem.iter()}),
)
def test_indent_level(self):
elem = ET.XML("
pre post
text
")
with self.assertRaises(ValueError):
ET.indent(elem, level=-1)
self.assertEqual(
ET.tostring(elem),
b"
pre post
text
"
)
ET.indent(elem, level=2)
self.assertEqual(
ET.tostring(elem),
b'\n'
b' \n'
b'
pre post
\n'
b'
text
\n'
b' \n'
b' '
)
elem = ET.XML("
pre post
text
")
ET.indent(elem, level=1, space=' ')
self.assertEqual(
ET.tostring(elem),
b'\n'
b' \n'
b'
The following is the source code of Recursive2.xml:
"""
XINCLUDE["Recursive2.xml"] = """\
The following is the source code of Recursive3.xml:
"""
XINCLUDE["Recursive3.xml"] = """\
The following is the source code of Recursive1.xml:
"""
class XIncludeTest(unittest.TestCase):
def xinclude_loader(self, href, parse="xml", encoding=None):
try:
data = XINCLUDE[href]
except KeyError:
raise OSError("resource not found")
if parse == "xml":
data = ET.XML(data)
return data
def none_loader(self, href, parser, encoding=None):
return None
def _my_loader(self, href, parse):
# Used to avoid a test-dependency problem where the default loader
# of ElementInclude uses the pyET parser for cET tests.
if parse == 'xml':
with open(href, 'rb') as f:
return ET.parse(f).getroot()
else:
return None
def test_xinclude_default(self):
from xml.etree import ElementInclude
doc = self.xinclude_loader('default.xml')
ElementInclude.include(doc, self._my_loader)
self.assertEqual(serialize(doc),
'\n'
'
The opinions represented herein represent those of the individual\n'
' and should not be interpreted as official policy endorsed by this\n'
' organization.
') # C5
def test_xinclude_repeated(self):
from xml.etree import ElementInclude
document = self.xinclude_loader("include_c1_repeated.xml")
ElementInclude.include(document, self.xinclude_loader)
self.assertEqual(1+4*2, len(document.findall(".//p")))
def test_xinclude_failures(self):
from xml.etree import ElementInclude
# Test failure to locate included XML file.
document = ET.XML(XINCLUDE["C1.xml"])
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, loader=self.none_loader)
self.assertEqual(str(cm.exception),
"cannot load 'disclaimer.xml' as 'xml'")
# Test failure to locate included text file.
document = ET.XML(XINCLUDE["C2.xml"])
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, loader=self.none_loader)
self.assertEqual(str(cm.exception),
"cannot load 'count.txt' as 'text'")
# Test bad parse type.
document = ET.XML(XINCLUDE_BAD["B1.xml"])
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, loader=self.none_loader)
self.assertEqual(str(cm.exception),
"unknown parse type in xi:include tag ('BAD_TYPE')")
# Test xi:fallback outside xi:include.
document = ET.XML(XINCLUDE_BAD["B2.xml"])
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, loader=self.none_loader)
self.assertEqual(str(cm.exception),
"xi:fallback tag must be child of xi:include "
"('{http://www.w3.org/2001/XInclude}fallback')")
# Test infinitely recursive includes.
document = self.xinclude_loader("Recursive1.xml")
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, self.xinclude_loader)
self.assertEqual(str(cm.exception),
"recursive include of Recursive2.xml")
# Test 'max_depth' limitation.
document = self.xinclude_loader("Recursive1.xml")
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, self.xinclude_loader, max_depth=None)
self.assertEqual(str(cm.exception),
"recursive include of Recursive2.xml")
document = self.xinclude_loader("Recursive1.xml")
with self.assertRaises(ElementInclude.LimitedRecursiveIncludeError) as cm:
ElementInclude.include(document, self.xinclude_loader, max_depth=0)
self.assertEqual(str(cm.exception),
"maximum xinclude depth reached when including file Recursive2.xml")
document = self.xinclude_loader("Recursive1.xml")
with self.assertRaises(ElementInclude.LimitedRecursiveIncludeError) as cm:
ElementInclude.include(document, self.xinclude_loader, max_depth=1)
self.assertEqual(str(cm.exception),
"maximum xinclude depth reached when including file Recursive3.xml")
document = self.xinclude_loader("Recursive1.xml")
with self.assertRaises(ElementInclude.LimitedRecursiveIncludeError) as cm:
ElementInclude.include(document, self.xinclude_loader, max_depth=2)
self.assertEqual(str(cm.exception),
"maximum xinclude depth reached when including file Recursive1.xml")
document = self.xinclude_loader("Recursive1.xml")
with self.assertRaises(ElementInclude.FatalIncludeError) as cm:
ElementInclude.include(document, self.xinclude_loader, max_depth=3)
self.assertEqual(str(cm.exception),
"recursive include of Recursive2.xml")
# --------------------------------------------------------------------
# reported bugs
class BugsTest(unittest.TestCase):
def test_bug_xmltoolkit21(self):
# marshaller gives obscure errors for non-string values
def check(elem):
with self.assertRaises(TypeError) as cm:
serialize(elem)
self.assertEqual(str(cm.exception),
'cannot serialize 123 (type int)')
elem = ET.Element(123)
check(elem) # tag
elem = ET.Element("elem")
elem.text = 123
check(elem) # text
elem = ET.Element("elem")
elem.tail = 123
check(elem) # tail
elem = ET.Element("elem")
elem.set(123, "123")
check(elem) # attribute key
elem = ET.Element("elem")
elem.set("123", 123)
check(elem) # attribute value
def test_bug_xmltoolkit25(self):
# typo in ElementTree.findtext
elem = ET.XML(SAMPLE_XML)
tree = ET.ElementTree(elem)
self.assertEqual(tree.findtext("tag"), 'text')
self.assertEqual(tree.findtext("section/tag"), 'subtext')
def test_bug_xmltoolkit28(self):
# .//tag causes exceptions
tree = ET.XML("
")
self.assertEqual(summarize_list(tree.findall(".//thead")), [])
self.assertEqual(summarize_list(tree.findall(".//tbody")), ['tbody'])
def test_bug_xmltoolkitX1(self):
# dump() doesn't flush the output buffer
tree = ET.XML("
")
with support.captured_stdout() as stdout:
ET.dump(tree)
self.assertEqual(stdout.getvalue(), '
\n')
def test_bug_xmltoolkit39(self):
# non-ascii element and attribute names doesn't work
tree = ET.XML(b"")
self.assertEqual(ET.tostring(tree, "utf-8"), b'')
tree = ET.XML(b""
b"")
self.assertEqual(tree.attrib, {'\xe4ttr': 'v\xe4lue'})
self.assertEqual(ET.tostring(tree, "utf-8"),
b'')
tree = ET.XML(b""
b'text')
self.assertEqual(ET.tostring(tree, "utf-8"),
b'text')
tree = ET.Element("t\u00e4g")
self.assertEqual(ET.tostring(tree, "utf-8"), b'')
tree = ET.Element("tag")
tree.set("\u00e4ttr", "v\u00e4lue")
self.assertEqual(ET.tostring(tree, "utf-8"),
b'')
def test_bug_xmltoolkit54(self):
# problems handling internally defined entities
e = ET.XML("]>"
'&ldots;')
self.assertEqual(serialize(e, encoding="us-ascii"),
b'舰')
self.assertEqual(serialize(e), '\u8230')
def test_bug_xmltoolkit55(self):
# make sure we're reporting the first error, not the last
with self.assertRaises(ET.ParseError) as cm:
ET.XML(b""
b'&ldots;&ndots;&rdots;')
self.assertEqual(str(cm.exception),
'undefined entity &ldots;: line 1, column 36')
def test_bug_xmltoolkit60(self):
# Handle crash in stream source.
class ExceptionFile:
def read(self, x):
raise OSError
self.assertRaises(OSError, ET.parse, ExceptionFile())
def test_bug_xmltoolkit62(self):
# Don't crash when using custom entities.
ENTITIES = {'rsquo': '\u2019', 'lsquo': '\u2018'}
parser = ET.XMLParser()
parser.entity.update(ENTITIES)
parser.feed("""
A new cultivar of Begonia plant named ‘BCT9801BEG’.""")
t = parser.close()
self.assertEqual(t.find('.//paragraph').text,
'A new cultivar of Begonia plant named \u2018BCT9801BEG\u2019.')
@unittest.skipIf(sys.gettrace(), "Skips under coverage.")
def test_bug_xmltoolkit63(self):
# Check reference leak.
def xmltoolkit63():
tree = ET.TreeBuilder()
tree.start("tag", {})
tree.data("text")
tree.end("tag")
xmltoolkit63()
count = sys.getrefcount(None)
for i in range(1000):
xmltoolkit63()
self.assertEqual(sys.getrefcount(None), count)
def test_bug_200708_newline(self):
# Preserve newlines in attributes.
e = ET.Element('SomeTag', text="def _f():\n return 3\n")
self.assertEqual(ET.tostring(e),
b'')
self.assertEqual(ET.XML(ET.tostring(e)).get("text"),
'def _f():\n return 3\n')
self.assertEqual(ET.tostring(ET.XML(ET.tostring(e))),
b'')
def test_bug_200708_close(self):
# Test default builder.
parser = ET.XMLParser() # default
parser.feed("some text")
self.assertEqual(parser.close().tag, 'element')
# Test custom builder.
class EchoTarget:
def close(self):
return ET.Element("element") # simulate root
parser = ET.XMLParser(target=EchoTarget())
parser.feed("some text")
self.assertEqual(parser.close().tag, 'element')
def test_bug_200709_default_namespace(self):
e = ET.Element("{default}elem")
s = ET.SubElement(e, "{default}elem")
self.assertEqual(serialize(e, default_namespace="default"), # 1
'')
e = ET.Element("{default}elem")
s = ET.SubElement(e, "{default}elem")
s = ET.SubElement(e, "{not-default}elem")
self.assertEqual(serialize(e, default_namespace="default"), # 2
''
''
''
'')
e = ET.Element("{default}elem")
s = ET.SubElement(e, "{default}elem")
s = ET.SubElement(e, "elem") # unprefixed name
with self.assertRaises(ValueError) as cm:
serialize(e, default_namespace="default") # 3
self.assertEqual(str(cm.exception),
'cannot use non-qualified names with default_namespace option')
def test_bug_200709_register_namespace(self):
e = ET.Element("{http://namespace.invalid/does/not/exist/}title")
self.assertEqual(ET.tostring(e),
b'')
ET.register_namespace("foo", "http://namespace.invalid/does/not/exist/")
e = ET.Element("{http://namespace.invalid/does/not/exist/}title")
self.assertEqual(ET.tostring(e),
b'')
# And the Dublin Core namespace is in the default list:
e = ET.Element("{http://purl.org/dc/elements/1.1/}title")
self.assertEqual(ET.tostring(e),
b'')
def test_bug_200709_element_comment(self):
# Not sure if this can be fixed, really (since the serializer needs
# ET.Comment, not cET.comment).
a = ET.Element('a')
a.append(ET.Comment('foo'))
self.assertEqual(a[0].tag, ET.Comment)
a = ET.Element('a')
a.append(ET.PI('foo'))
self.assertEqual(a[0].tag, ET.PI)
def test_bug_200709_element_insert(self):
a = ET.Element('a')
b = ET.SubElement(a, 'b')
c = ET.SubElement(a, 'c')
d = ET.Element('d')
a.insert(0, d)
self.assertEqual(summarize_list(a), ['d', 'b', 'c'])
a.insert(-1, d)
self.assertEqual(summarize_list(a), ['d', 'b', 'd', 'c'])
def test_bug_200709_iter_comment(self):
a = ET.Element('a')
b = ET.SubElement(a, 'b')
comment_b = ET.Comment("TEST-b")
b.append(comment_b)
self.assertEqual(summarize_list(a.iter(ET.Comment)), [ET.Comment])
# --------------------------------------------------------------------
# reported on bugs.python.org
def test_bug_1534630(self):
bob = ET.TreeBuilder()
e = bob.data("data")
e = bob.start("tag", {})
e = bob.end("tag")
e = bob.close()
self.assertEqual(serialize(e), '')
def test_issue6233(self):
e = ET.XML(b""
b't\xc3\xa3g')
self.assertEqual(ET.tostring(e, 'ascii'),
b"\n"
b'tãg')
e = ET.XML(b""
b't\xe3g')
self.assertEqual(ET.tostring(e, 'ascii'),
b"\n"
b'tãg')
def test_issue6565(self):
elem = ET.XML("")
self.assertEqual(summarize_list(elem), ['tag'])
newelem = ET.XML(SAMPLE_XML)
elem[:] = newelem[:]
self.assertEqual(summarize_list(elem), ['tag', 'tag', 'section'])
def test_issue10777(self):
# Registering a namespace twice caused a "dictionary changed size during
# iteration" bug.
ET.register_namespace('test10777', 'http://myuri/')
ET.register_namespace('test10777', 'http://myuri/')
def test_lost_text(self):
# Issue #25902: Borrowed text can disappear
class Text:
def __bool__(self):
e.text = 'changed'
return True
e = ET.Element('tag')
e.text = Text()
i = e.itertext()
t = next(i)
self.assertIsInstance(t, Text)
self.assertIsInstance(e.text, str)
self.assertEqual(e.text, 'changed')
def test_lost_tail(self):
# Issue #25902: Borrowed tail can disappear
class Text:
def __bool__(self):
e[0].tail = 'changed'
return True
e = ET.Element('root')
e.append(ET.Element('tag'))
e[0].tail = Text()
i = e.itertext()
t = next(i)
self.assertIsInstance(t, Text)
self.assertIsInstance(e[0].tail, str)
self.assertEqual(e[0].tail, 'changed')
def test_lost_elem(self):
# Issue #25902: Borrowed element can disappear
class Tag:
def __eq__(self, other):
e[0] = ET.Element('changed')
next(i)
return True
e = ET.Element('root')
e.append(ET.Element(Tag()))
e.append(ET.Element('tag'))
i = e.iter('tag')
try:
t = next(i)
except ValueError:
self.skipTest('generators are not reentrant')
self.assertIsInstance(t.tag, Tag)
self.assertIsInstance(e[0].tag, str)
self.assertEqual(e[0].tag, 'changed')
def check_expat224_utf8_bug(self, text):
xml = b'' % text
root = ET.XML(xml)
self.assertEqual(root.get('b'), text.decode('utf-8'))
def test_expat224_utf8_bug(self):
# bpo-31170: Expat 2.2.3 had a bug in its UTF-8 decoder.
# Check that Expat 2.2.4 fixed the bug.
#
# Test buffer bounds at odd and even positions.
text = b'\xc3\xa0' * 1024
self.check_expat224_utf8_bug(text)
text = b'x' + b'\xc3\xa0' * 1024
self.check_expat224_utf8_bug(text)
def test_expat224_utf8_bug_file(self):
with open(UTF8_BUG_XMLFILE, 'rb') as fp:
raw = fp.read()
root = ET.fromstring(raw)
xmlattr = root.get('b')
# "Parse" manually the XML file to extract the value of the 'b'
# attribute of the XML element
text = raw.decode('utf-8').strip()
text = text.replace('\r\n', ' ')
text = text[6:-4]
self.assertEqual(root.get('b'), text)
def test_39495_treebuilder_start(self):
self.assertRaises(TypeError, ET.TreeBuilder().start, "tag")
self.assertRaises(TypeError, ET.TreeBuilder().start, "tag", None)
# --------------------------------------------------------------------
class BasicElementTest(ElementTestCase, unittest.TestCase):
def test___init__(self):
tag = "foo"
attrib = { "zix": "wyp" }
element_foo = ET.Element(tag, attrib)
# traits of an element
self.assertIsInstance(element_foo, ET.Element)
self.assertIn("tag", dir(element_foo))
self.assertIn("attrib", dir(element_foo))
self.assertIn("text", dir(element_foo))
self.assertIn("tail", dir(element_foo))
# string attributes have expected values
self.assertEqual(element_foo.tag, tag)
self.assertIsNone(element_foo.text)
self.assertIsNone(element_foo.tail)
# attrib is a copy
self.assertIsNot(element_foo.attrib, attrib)
self.assertEqual(element_foo.attrib, attrib)
# attrib isn't linked
attrib["bar"] = "baz"
self.assertIsNot(element_foo.attrib, attrib)
self.assertNotEqual(element_foo.attrib, attrib)
def test___copy__(self):
element_foo = ET.Element("foo", { "zix": "wyp" })
element_foo.append(ET.Element("bar", { "baz": "qix" }))
element_foo2 = copy.copy(element_foo)
# elements are not the same
self.assertIsNot(element_foo2, element_foo)
# string attributes are equal
self.assertEqual(element_foo2.tag, element_foo.tag)
self.assertEqual(element_foo2.text, element_foo.text)
self.assertEqual(element_foo2.tail, element_foo.tail)
# number of children is the same
self.assertEqual(len(element_foo2), len(element_foo))
# children are the same
for (child1, child2) in itertools.zip_longest(element_foo, element_foo2):
self.assertIs(child1, child2)
# attrib is a copy
self.assertEqual(element_foo2.attrib, element_foo.attrib)
def test___deepcopy__(self):
element_foo = ET.Element("foo", { "zix": "wyp" })
element_foo.append(ET.Element("bar", { "baz": "qix" }))
element_foo2 = copy.deepcopy(element_foo)
# elements are not the same
self.assertIsNot(element_foo2, element_foo)
# string attributes are equal
self.assertEqual(element_foo2.tag, element_foo.tag)
self.assertEqual(element_foo2.text, element_foo.text)
self.assertEqual(element_foo2.tail, element_foo.tail)
# number of children is the same
self.assertEqual(len(element_foo2), len(element_foo))
# children are not the same
for (child1, child2) in itertools.zip_longest(element_foo, element_foo2):
self.assertIsNot(child1, child2)
# attrib is a copy
self.assertIsNot(element_foo2.attrib, element_foo.attrib)
self.assertEqual(element_foo2.attrib, element_foo.attrib)
# attrib isn't linked
element_foo.attrib["bar"] = "baz"
self.assertIsNot(element_foo2.attrib, element_foo.attrib)
self.assertNotEqual(element_foo2.attrib, element_foo.attrib)
def test_augmentation_type_errors(self):
e = ET.Element('joe')
self.assertRaises(TypeError, e.append, 'b')
self.assertRaises(TypeError, e.extend, [ET.Element('bar'), 'foo'])
self.assertRaises(TypeError, e.insert, 0, 'foo')
e[:] = [ET.Element('bar')]
with self.assertRaises(TypeError):
e[0] = 'foo'
with self.assertRaises(TypeError):
e[:] = [ET.Element('bar'), 'foo']
if hasattr(e, '__setstate__'):
state = {
'tag': 'tag',
'_children': [None], # non-Element
'attrib': 'attr',
'tail': 'tail',
'text': 'text',
}
self.assertRaises(TypeError, e.__setstate__, state)
if hasattr(e, '__deepcopy__'):
class E(ET.Element):
def __deepcopy__(self, memo):
return None # non-Element
e[:] = [E('bar')]
self.assertRaises(TypeError, copy.deepcopy, e)
def test_cyclic_gc(self):
class Dummy:
pass
# Test the shortest cycle: d->element->d
d = Dummy()
d.dummyref = ET.Element('joe', attr=d)
wref = weakref.ref(d)
del d
gc_collect()
self.assertIsNone(wref())
# A longer cycle: d->e->e2->d
e = ET.Element('joe')
d = Dummy()
d.dummyref = e
wref = weakref.ref(d)
e2 = ET.SubElement(e, 'foo', attr=d)
del d, e, e2
gc_collect()
self.assertIsNone(wref())
# A cycle between Element objects as children of one another
# e1->e2->e3->e1
e1 = ET.Element('e1')
e2 = ET.Element('e2')
e3 = ET.Element('e3')
e3.append(e1)
e2.append(e3)
e1.append(e2)
wref = weakref.ref(e1)
del e1, e2, e3
gc_collect()
self.assertIsNone(wref())
def test_weakref(self):
flag = False
def wref_cb(w):
nonlocal flag
flag = True
e = ET.Element('e')
wref = weakref.ref(e, wref_cb)
self.assertEqual(wref().tag, 'e')
del e
gc_collect() # For PyPy or other GCs.
self.assertEqual(flag, True)
self.assertEqual(wref(), None)
def test_get_keyword_args(self):
e1 = ET.Element('foo' , x=1, y=2, z=3)
self.assertEqual(e1.get('x', default=7), 1)
self.assertEqual(e1.get('w', default=7), 7)
def test_pickle(self):
# issue #16076: the C implementation wasn't pickleable.
for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
for dumper, loader in product(self.modules, repeat=2):
e = dumper.Element('foo', bar=42)
e.text = "text goes here"
e.tail = "opposite of head"
dumper.SubElement(e, 'child').append(dumper.Element('grandchild'))
e.append(dumper.Element('child'))
e.findall('.//grandchild')[0].set('attr', 'other value')
e2 = self.pickleRoundTrip(e, 'xml.etree.ElementTree',
dumper, loader, proto)
self.assertEqual(e2.tag, 'foo')
self.assertEqual(e2.attrib['bar'], 42)
self.assertEqual(len(e2), 2)
self.assertEqualElements(e, e2)
def test_pickle_issue18997(self):
for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
for dumper, loader in product(self.modules, repeat=2):
XMLTEXT = """
4"""
e1 = dumper.fromstring(XMLTEXT)
self.assertEqual(e1.__getstate__()['tag'], 'group')
e2 = self.pickleRoundTrip(e1, 'xml.etree.ElementTree',
dumper, loader, proto)
self.assertEqual(e2.tag, 'group')
self.assertEqual(e2[0].tag, 'dogs')
class BadElementTest(ElementTestCase, unittest.TestCase):
def test_extend_mutable_list(self):
class X:
@property
def __class__(self):
L[:] = [ET.Element('baz')]
return ET.Element
L = [X()]
e = ET.Element('foo')
try:
e.extend(L)
except TypeError:
pass
class Y(X, ET.Element):
pass
L = [Y('x')]
e = ET.Element('foo')
e.extend(L)
def test_extend_mutable_list2(self):
class X:
@property
def __class__(self):
del L[:]
return ET.Element
L = [X(), ET.Element('baz')]
e = ET.Element('foo')
try:
e.extend(L)
except TypeError:
pass
class Y(X, ET.Element):
pass
L = [Y('bar'), ET.Element('baz')]
e = ET.Element('foo')
e.extend(L)
def test_remove_with_mutating(self):
class X(ET.Element):
def __eq__(self, o):
del e[:]
return False
e = ET.Element('foo')
e.extend([X('bar')])
self.assertRaises(ValueError, e.remove, ET.Element('baz'))
e = ET.Element('foo')
e.extend([ET.Element('bar')])
self.assertRaises(ValueError, e.remove, X('baz'))
@support.infinite_recursion(25)
def test_recursive_repr(self):
# Issue #25455
e = ET.Element('foo')
with swap_attr(e, 'tag', e):
with self.assertRaises(RuntimeError):
repr(e) # Should not crash
def test_element_get_text(self):
# Issue #27863
class X(str):
def __del__(self):
try:
elem.text
except NameError:
pass
b = ET.TreeBuilder()
b.start('tag', {})
b.data('ABCD')
b.data(X('EFGH'))
b.data('IJKL')
b.end('tag')
elem = b.close()
self.assertEqual(elem.text, 'ABCDEFGHIJKL')
def test_element_get_tail(self):
# Issue #27863
class X(str):
def __del__(self):
try:
elem[0].tail
except NameError:
pass
b = ET.TreeBuilder()
b.start('root', {})
b.start('tag', {})
b.end('tag')
b.data('ABCD')
b.data(X('EFGH'))
b.data('IJKL')
b.end('root')
elem = b.close()
self.assertEqual(elem[0].tail, 'ABCDEFGHIJKL')
def test_subscr(self):
# Issue #27863
class X:
def __index__(self):
del e[:]
return 1
e = ET.Element('elem')
e.append(ET.Element('child'))
e[:X()] # shouldn't crash
e.append(ET.Element('child'))
e[0:10:X()] # shouldn't crash
def test_ass_subscr(self):
# Issue #27863
class X:
def __index__(self):
e[:] = []
return 1
e = ET.Element('elem')
for _ in range(10):
e.insert(0, ET.Element('child'))
e[0:10:X()] = [] # shouldn't crash
def test_treebuilder_start(self):
# Issue #27863
def element_factory(x, y):
return []
b = ET.TreeBuilder(element_factory=element_factory)
b.start('tag', {})
b.data('ABCD')
self.assertRaises(AttributeError, b.start, 'tag2', {})
del b
gc_collect()
def test_treebuilder_end(self):
# Issue #27863
def element_factory(x, y):
return []
b = ET.TreeBuilder(element_factory=element_factory)
b.start('tag', {})
b.data('ABCD')
self.assertRaises(AttributeError, b.end, 'tag')
del b
gc_collect()
class MutatingElementPath(str):
def __new__(cls, elem, *args):
self = str.__new__(cls, *args)
self.elem = elem
return self
def __eq__(self, o):
del self.elem[:]
return True
MutatingElementPath.__hash__ = str.__hash__
class BadElementPath(str):
def __eq__(self, o):
raise 1/0
BadElementPath.__hash__ = str.__hash__
class BadElementPathTest(ElementTestCase, unittest.TestCase):
def setUp(self):
super().setUp()
from xml.etree import ElementPath
self.path_cache = ElementPath._cache
ElementPath._cache = {}
def tearDown(self):
from xml.etree import ElementPath
ElementPath._cache = self.path_cache
super().tearDown()
def test_find_with_mutating(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
e.find(MutatingElementPath(e, 'x'))
def test_find_with_error(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
try:
e.find(BadElementPath('x'))
except ZeroDivisionError:
pass
def test_findtext_with_mutating(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
e.findtext(MutatingElementPath(e, 'x'))
def test_findtext_with_error(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
try:
e.findtext(BadElementPath('x'))
except ZeroDivisionError:
pass
def test_findtext_with_falsey_text_attribute(self):
root_elem = ET.Element('foo')
sub_elem = ET.SubElement(root_elem, 'bar')
falsey = ["", 0, False, [], (), {}]
for val in falsey:
sub_elem.text = val
self.assertEqual(root_elem.findtext('./bar'), val)
def test_findtext_with_none_text_attribute(self):
root_elem = ET.Element('foo')
sub_elem = ET.SubElement(root_elem, 'bar')
sub_elem.text = None
self.assertEqual(root_elem.findtext('./bar'), '')
def test_findall_with_mutating(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
e.findall(MutatingElementPath(e, 'x'))
def test_findall_with_error(self):
e = ET.Element('foo')
e.extend([ET.Element('bar')])
try:
e.findall(BadElementPath('x'))
except ZeroDivisionError:
pass
class ElementTreeTypeTest(unittest.TestCase):
def test_istype(self):
self.assertIsInstance(ET.ParseError, type)
self.assertIsInstance(ET.QName, type)
self.assertIsInstance(ET.ElementTree, type)
self.assertIsInstance(ET.Element, type)
self.assertIsInstance(ET.TreeBuilder, type)
self.assertIsInstance(ET.XMLParser, type)
def test_Element_subclass_trivial(self):
class MyElement(ET.Element):
pass
mye = MyElement('foo')
self.assertIsInstance(mye, ET.Element)
self.assertIsInstance(mye, MyElement)
self.assertEqual(mye.tag, 'foo')
# test that attribute assignment works (issue 14849)
mye.text = "joe"
self.assertEqual(mye.text, "joe")
def test_Element_subclass_constructor(self):
class MyElement(ET.Element):
def __init__(self, tag, attrib={}, **extra):
super(MyElement, self).__init__(tag + '__', attrib, **extra)
mye = MyElement('foo', {'a': 1, 'b': 2}, c=3, d=4)
self.assertEqual(mye.tag, 'foo__')
self.assertEqual(sorted(mye.items()),
[('a', 1), ('b', 2), ('c', 3), ('d', 4)])
def test_Element_subclass_new_method(self):
class MyElement(ET.Element):
def newmethod(self):
return self.tag
mye = MyElement('joe')
self.assertEqual(mye.newmethod(), 'joe')
def test_Element_subclass_find(self):
class MyElement(ET.Element):
pass
e = ET.Element('foo')
e.text = 'text'
sub = MyElement('bar')
sub.text = 'subtext'
e.append(sub)
self.assertEqual(e.findtext('bar'), 'subtext')
self.assertEqual(e.find('bar').tag, 'bar')
found = list(e.findall('bar'))
self.assertEqual(len(found), 1, found)
self.assertEqual(found[0].tag, 'bar')
class ElementFindTest(unittest.TestCase):
def test_find_simple(self):
e = ET.XML(SAMPLE_XML)
self.assertEqual(e.find('tag').tag, 'tag')
self.assertEqual(e.find('section/tag').tag, 'tag')
self.assertEqual(e.find('./tag').tag, 'tag')
e[2] = ET.XML(SAMPLE_SECTION)
self.assertEqual(e.find('section/nexttag').tag, 'nexttag')
self.assertEqual(e.findtext('./tag'), 'text')
self.assertEqual(e.findtext('section/tag'), 'subtext')
# section/nexttag is found but has no text
self.assertEqual(e.findtext('section/nexttag'), '')
self.assertEqual(e.findtext('section/nexttag', 'default'), '')
# tog doesn't exist and 'default' kicks in
self.assertIsNone(e.findtext('tog'))
self.assertEqual(e.findtext('tog', 'default'), 'default')
# Issue #16922
self.assertEqual(ET.XML('').findtext('empty'), '')
def test_find_xpath(self):
LINEAR_XML = '''
'''
e = ET.XML(LINEAR_XML)
# Test for numeric indexing and last()
self.assertEqual(e.find('./tag[1]').attrib['class'], 'a')
self.assertEqual(e.find('./tag[2]').attrib['class'], 'b')
self.assertEqual(e.find('./tag[last()]').attrib['class'], 'd')
self.assertEqual(e.find('./tag[last()-1]').attrib['class'], 'c')
self.assertEqual(e.find('./tag[last()-2]').attrib['class'], 'b')
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[0]')
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[-1]')
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]')
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]')
def test_findall(self):
e = ET.XML(SAMPLE_XML)
e[2] = ET.XML(SAMPLE_SECTION)
self.assertEqual(summarize_list(e.findall('.')), ['body'])
self.assertEqual(summarize_list(e.findall('tag')), ['tag', 'tag'])
self.assertEqual(summarize_list(e.findall('tog')), [])
self.assertEqual(summarize_list(e.findall('tog/foo')), [])
self.assertEqual(summarize_list(e.findall('*')),
['tag', 'tag', 'section'])
self.assertEqual(summarize_list(e.findall('.//tag')),
['tag'] * 4)
self.assertEqual(summarize_list(e.findall('section/tag')), ['tag'])
self.assertEqual(summarize_list(e.findall('section//tag')), ['tag'] * 2)
self.assertEqual(summarize_list(e.findall('section/*')),
['tag', 'nexttag', 'nextsection'])
self.assertEqual(summarize_list(e.findall('section//*')),
['tag', 'nexttag', 'nextsection', 'tag'])
self.assertEqual(summarize_list(e.findall('section/.//*')),
['tag', 'nexttag', 'nextsection', 'tag'])
self.assertEqual(summarize_list(e.findall('*/*')),
['tag', 'nexttag', 'nextsection'])
self.assertEqual(summarize_list(e.findall('*//*')),
['tag', 'nexttag', 'nextsection', 'tag'])
self.assertEqual(summarize_list(e.findall('*/tag')), ['tag'])
self.assertEqual(summarize_list(e.findall('*/./tag')), ['tag'])
self.assertEqual(summarize_list(e.findall('./tag')), ['tag'] * 2)
self.assertEqual(summarize_list(e.findall('././tag')), ['tag'] * 2)
self.assertEqual(summarize_list(e.findall('.//tag[@class]')),
['tag'] * 3)
self.assertEqual(summarize_list(e.findall('.//tag[@class="a"]')),
['tag'])
self.assertEqual(summarize_list(e.findall('.//tag[@class!="a"]')),
['tag'] * 2)
self.assertEqual(summarize_list(e.findall('.//tag[@class="b"]')),
['tag'] * 2)
self.assertEqual(summarize_list(e.findall('.//tag[@class!="b"]')),
['tag'])
self.assertEqual(summarize_list(e.findall('.//tag[@id]')),
['tag'])
self.assertEqual(summarize_list(e.findall('.//section[tag]')),
['section'])
self.assertEqual(summarize_list(e.findall('.//section[element]')), [])
self.assertEqual(summarize_list(e.findall('../tag')), [])
self.assertEqual(summarize_list(e.findall('section/../tag')),
['tag'] * 2)
self.assertEqual(e.findall('section//'), e.findall('section//*'))
self.assertEqual(summarize_list(e.findall(".//section[tag='subtext']")),
['section'])
self.assertEqual(summarize_list(e.findall(".//section[tag ='subtext']")),
['section'])
self.assertEqual(summarize_list(e.findall(".//section[tag= 'subtext']")),
['section'])
self.assertEqual(summarize_list(e.findall(".//section[tag = 'subtext']")),
['section'])
self.assertEqual(summarize_list(e.findall(".//section[ tag = 'subtext' ]")),
['section'])
# Negations of above tests. They match nothing because the sole section
# tag has subtext.
self.assertEqual(summarize_list(e.findall(".//section[tag!='subtext']")),
[])
self.assertEqual(summarize_list(e.findall(".//section[tag !='subtext']")),
[])
self.assertEqual(summarize_list(e.findall(".//section[tag!= 'subtext']")),
[])
self.assertEqual(summarize_list(e.findall(".//section[tag != 'subtext']")),
[])
self.assertEqual(summarize_list(e.findall(".//section[ tag != 'subtext' ]")),
[])
self.assertEqual(summarize_list(e.findall(".//tag[.='subtext']")),
['tag'])
self.assertEqual(summarize_list(e.findall(".//tag[. ='subtext']")),
['tag'])
self.assertEqual(summarize_list(e.findall('.//tag[.= "subtext"]')),
['tag'])
self.assertEqual(summarize_list(e.findall('.//tag[ . = "subtext" ]')),
['tag'])
self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext']")),
['tag'])
self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext ']")),
[])
self.assertEqual(summarize_list(e.findall(".//tag[.= ' subtext']")),
[])
# Negations of above tests.
# Matches everything but the tag containing subtext
self.assertEqual(summarize_list(e.findall(".//tag[.!='subtext']")),
['tag'] * 3)
self.assertEqual(summarize_list(e.findall(".//tag[. !='subtext']")),
['tag'] * 3)
self.assertEqual(summarize_list(e.findall('.//tag[.!= "subtext"]')),
['tag'] * 3)
self.assertEqual(summarize_list(e.findall('.//tag[ . != "subtext" ]')),
['tag'] * 3)
self.assertEqual(summarize_list(e.findall(".//tag[. != 'subtext']")),
['tag'] * 3)
# Matches all tags.
self.assertEqual(summarize_list(e.findall(".//tag[. != 'subtext ']")),
['tag'] * 4)
self.assertEqual(summarize_list(e.findall(".//tag[.!= ' subtext']")),
['tag'] * 4)
# duplicate section => 2x tag matches
e[1] = e[2]
self.assertEqual(summarize_list(e.findall(".//section[tag = 'subtext']")),
['section', 'section'])
self.assertEqual(summarize_list(e.findall(".//tag[. = 'subtext']")),
['tag', 'tag'])
def test_test_find_with_ns(self):
e = ET.XML(SAMPLE_XML_NS)
self.assertEqual(summarize_list(e.findall('tag')), [])
self.assertEqual(
summarize_list(e.findall("{http://effbot.org/ns}tag")),
['{http://effbot.org/ns}tag'] * 2)
self.assertEqual(
summarize_list(e.findall(".//{http://effbot.org/ns}tag")),
['{http://effbot.org/ns}tag'] * 3)
def test_findall_different_nsmaps(self):
root = ET.XML('''
''')
nsmap = {'xx': 'X'}
self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2)
self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 2)
nsmap = {'xx': 'Y'}
self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 1)
self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 2)
nsmap = {'xx': 'X', '': 'Y'}
self.assertEqual(len(root.findall(".//xx:b", namespaces=nsmap)), 2)
self.assertEqual(len(root.findall(".//b", namespaces=nsmap)), 1)
def test_findall_wildcard(self):
root = ET.XML('''
''')
root.append(ET.Comment('test'))
self.assertEqual(summarize_list(root.findall("{*}b")),
['{X}b', 'b', '{Y}b'])
self.assertEqual(summarize_list(root.findall("{*}c")),
['c'])
self.assertEqual(summarize_list(root.findall("{X}*")),
['{X}b'])
self.assertEqual(summarize_list(root.findall("{Y}*")),
['{Y}b'])
self.assertEqual(summarize_list(root.findall("{}*")),
['b', 'c'])
self.assertEqual(summarize_list(root.findall("{}b")), # only for consistency
['b'])
self.assertEqual(summarize_list(root.findall("{}b")),
summarize_list(root.findall("b")))
self.assertEqual(summarize_list(root.findall("{*}*")),
['{X}b', 'b', 'c', '{Y}b'])
# This is an unfortunate difference, but that's how find('*') works.
self.assertEqual(summarize_list(root.findall("{*}*") + [root[-1]]),
summarize_list(root.findall("*")))
self.assertEqual(summarize_list(root.findall(".//{*}b")),
['{X}b', 'b', '{X}b', 'b', '{Y}b'])
self.assertEqual(summarize_list(root.findall(".//{*}c")),
['c', 'c'])
self.assertEqual(summarize_list(root.findall(".//{X}*")),
['{X}b', '{X}b'])
self.assertEqual(summarize_list(root.findall(".//{Y}*")),
['{Y}b'])
self.assertEqual(summarize_list(root.findall(".//{}*")),
['c', 'b', 'c', 'b'])
self.assertEqual(summarize_list(root.findall(".//{}b")), # only for consistency
['b', 'b'])
self.assertEqual(summarize_list(root.findall(".//{}b")),
summarize_list(root.findall(".//b")))
def test_bad_find(self):
e = ET.XML(SAMPLE_XML)
with self.assertRaisesRegex(SyntaxError, 'cannot use absolute path'):
e.findall('/tag')
def test_find_through_ElementTree(self):
e = ET.XML(SAMPLE_XML)
self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag')
self.assertEqual(ET.ElementTree(e).findtext('tag'), 'text')
self.assertEqual(summarize_list(ET.ElementTree(e).findall('tag')),
['tag'] * 2)
# this produces a warning
msg = ("This search is broken in 1.3 and earlier, and will be fixed "
"in a future version. If you rely on the current behaviour, "
"change it to '.+'")
with self.assertWarnsRegex(FutureWarning, msg):
it = ET.ElementTree(e).findall('//tag')
self.assertEqual(summarize_list(it), ['tag'] * 3)
class ElementIterTest(unittest.TestCase):
def _ilist(self, elem, tag=None):
return summarize_list(elem.iter(tag))
def test_basic(self):
doc = ET.XML("this is a paragraph...")
self.assertEqual(self._ilist(doc), ['html', 'body', 'i'])
self.assertEqual(self._ilist(doc.find('body')), ['body', 'i'])
self.assertEqual(next(doc.iter()).tag, 'html')
self.assertEqual(''.join(doc.itertext()), 'this is a paragraph...')
self.assertEqual(''.join(doc.find('body').itertext()),
'this is a paragraph.')
self.assertEqual(next(doc.itertext()), 'this is a ')
# iterparse should return an iterator
sourcefile = serialize(doc, to_string=False)
self.assertEqual(next(ET.iterparse(sourcefile))[0], 'end')
# With an explicit parser too (issue #9708)
sourcefile = serialize(doc, to_string=False)
parser = ET.XMLParser(target=ET.TreeBuilder())
self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], 'end')
tree = ET.ElementTree(None)
self.assertRaises(AttributeError, tree.iter)
# Issue #16913
doc = ET.XML("a&b&c&")
self.assertEqual(''.join(doc.itertext()), 'a&b&c&')
def test_corners(self):
# single root, no subelements
a = ET.Element('a')
self.assertEqual(self._ilist(a), ['a'])
# one child
b = ET.SubElement(a, 'b')
self.assertEqual(self._ilist(a), ['a', 'b'])
# one child and one grandchild
c = ET.SubElement(b, 'c')
self.assertEqual(self._ilist(a), ['a', 'b', 'c'])
# two children, only first with grandchild
d = ET.SubElement(a, 'd')
self.assertEqual(self._ilist(a), ['a', 'b', 'c', 'd'])
# replace first child by second
a[0] = a[1]
del a[1]
self.assertEqual(self._ilist(a), ['a', 'd'])
def test_iter_by_tag(self):
doc = ET.XML('''
bedroom1bedroom2nothing here
bedroom8''')
self.assertEqual(self._ilist(doc, 'room'), ['room'] * 3)
self.assertEqual(self._ilist(doc, 'house'), ['house'] * 2)
# test that iter also accepts 'tag' as a keyword arg
self.assertEqual(
summarize_list(doc.iter(tag='room')),
['room'] * 3)
# make sure both tag=None and tag='*' return all tags
all_tags = ['document', 'house', 'room', 'room',
'shed', 'house', 'room']
self.assertEqual(summarize_list(doc.iter()), all_tags)
self.assertEqual(self._ilist(doc), all_tags)
self.assertEqual(self._ilist(doc, '*'), all_tags)
def test_copy(self):
a = ET.Element('a')
it = a.iter()
with self.assertRaises(TypeError):
copy.copy(it)
def test_pickle(self):
a = ET.Element('a')
it = a.iter()
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.assertRaises((TypeError, pickle.PicklingError)):
pickle.dumps(it, proto)
class TreeBuilderTest(unittest.TestCase):
sample1 = (''
'text
subtext
tail')
sample2 = '''sometext'''
def _check_sample1_element(self, e):
self.assertEqual(e.tag, 'html')
self.assertEqual(e.text, 'text')
self.assertEqual(e.tail, None)
self.assertEqual(e.attrib, {})
children = list(e)
self.assertEqual(len(children), 1)
child = children[0]
self.assertEqual(child.tag, 'div')
self.assertEqual(child.text, 'subtext')
self.assertEqual(child.tail, 'tail')
self.assertEqual(child.attrib, {})
def test_dummy_builder(self):
class BaseDummyBuilder:
def close(self):
return 42
class DummyBuilder(BaseDummyBuilder):
data = start = end = lambda *a: None
parser = ET.XMLParser(target=DummyBuilder())
parser.feed(self.sample1)
self.assertEqual(parser.close(), 42)
parser = ET.XMLParser(target=BaseDummyBuilder())
parser.feed(self.sample1)
self.assertEqual(parser.close(), 42)
parser = ET.XMLParser(target=object())
parser.feed(self.sample1)
self.assertIsNone(parser.close())
def test_treebuilder_comment(self):
b = ET.TreeBuilder()
self.assertEqual(b.comment('ctext').tag, ET.Comment)
self.assertEqual(b.comment('ctext').text, 'ctext')
b = ET.TreeBuilder(comment_factory=ET.Comment)
self.assertEqual(b.comment('ctext').tag, ET.Comment)
self.assertEqual(b.comment('ctext').text, 'ctext')
b = ET.TreeBuilder(comment_factory=len)
self.assertEqual(b.comment('ctext'), len('ctext'))
def test_treebuilder_pi(self):
b = ET.TreeBuilder()
self.assertEqual(b.pi('target', None).tag, ET.PI)
self.assertEqual(b.pi('target', None).text, 'target')
b = ET.TreeBuilder(pi_factory=ET.PI)
self.assertEqual(b.pi('target').tag, ET.PI)
self.assertEqual(b.pi('target').text, "target")
self.assertEqual(b.pi('pitarget', ' text ').tag, ET.PI)
self.assertEqual(b.pi('pitarget', ' text ').text, "pitarget text ")
b = ET.TreeBuilder(pi_factory=lambda target, text: (len(target), text))
self.assertEqual(b.pi('target'), (len('target'), None))
self.assertEqual(b.pi('pitarget', ' text '), (len('pitarget'), ' text '))
def test_late_tail(self):
# Issue #37399: The tail of an ignored comment could overwrite the text before it.
class TreeBuilderSubclass(ET.TreeBuilder):
pass
xml = "texttail"
a = ET.fromstring(xml)
self.assertEqual(a.text, "texttail")
parser = ET.XMLParser(target=TreeBuilderSubclass())
parser.feed(xml)
a = parser.close()
self.assertEqual(a.text, "texttail")
xml = "texttail"
a = ET.fromstring(xml)
self.assertEqual(a.text, "texttail")
xml = "texttail"
parser = ET.XMLParser(target=TreeBuilderSubclass())
parser.feed(xml)
a = parser.close()
self.assertEqual(a.text, "texttail")
def test_late_tail_mix_pi_comments(self):
# Issue #37399: The tail of an ignored comment could overwrite the text before it.
# Test appending tails to comments/pis.
class TreeBuilderSubclass(ET.TreeBuilder):
pass
xml = "text \ntail"
parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True))
parser.feed(xml)
a = parser.close()
self.assertEqual(a[0].text, ' comment ')
self.assertEqual(a[0].tail, '\ntail')
self.assertEqual(a.text, "text ")
parser = ET.XMLParser(target=TreeBuilderSubclass(insert_comments=True))
parser.feed(xml)
a = parser.close()
self.assertEqual(a[0].text, ' comment ')
self.assertEqual(a[0].tail, '\ntail')
self.assertEqual(a.text, "text ")
xml = "text\ntail"
parser = ET.XMLParser(target=ET.TreeBuilder(insert_pis=True))
parser.feed(xml)
a = parser.close()
self.assertEqual(a[0].text, 'pi data')
self.assertEqual(a[0].tail, 'tail')
self.assertEqual(a.text, "text\n")
parser = ET.XMLParser(target=TreeBuilderSubclass(insert_pis=True))
parser.feed(xml)
a = parser.close()
self.assertEqual(a[0].text, 'pi data')
self.assertEqual(a[0].tail, 'tail')
self.assertEqual(a.text, "text\n")
def test_treebuilder_elementfactory_none(self):
parser = ET.XMLParser(target=ET.TreeBuilder(element_factory=None))
parser.feed(self.sample1)
e = parser.close()
self._check_sample1_element(e)
def test_subclass(self):
class MyTreeBuilder(ET.TreeBuilder):
def foobar(self, x):
return x * 2
tb = MyTreeBuilder()
self.assertEqual(tb.foobar(10), 20)
parser = ET.XMLParser(target=tb)
parser.feed(self.sample1)
e = parser.close()
self._check_sample1_element(e)
def test_subclass_comment_pi(self):
class MyTreeBuilder(ET.TreeBuilder):
def foobar(self, x):
return x * 2
tb = MyTreeBuilder(comment_factory=ET.Comment, pi_factory=ET.PI)
self.assertEqual(tb.foobar(10), 20)
parser = ET.XMLParser(target=tb)
parser.feed(self.sample1)
parser.feed('')
e = parser.close()
self._check_sample1_element(e)
def test_element_factory(self):
lst = []
def myfactory(tag, attrib):
nonlocal lst
lst.append(tag)
return ET.Element(tag, attrib)
tb = ET.TreeBuilder(element_factory=myfactory)
parser = ET.XMLParser(target=tb)
parser.feed(self.sample2)
parser.close()
self.assertEqual(lst, ['toplevel'])
def _check_element_factory_class(self, cls):
tb = ET.TreeBuilder(element_factory=cls)
parser = ET.XMLParser(target=tb)
parser.feed(self.sample1)
e = parser.close()
self.assertIsInstance(e, cls)
self._check_sample1_element(e)
def test_element_factory_subclass(self):
class MyElement(ET.Element):
pass
self._check_element_factory_class(MyElement)
def test_element_factory_pure_python_subclass(self):
# Mimic SimpleTAL's behaviour (issue #16089): both versions of
# TreeBuilder should be able to cope with a subclass of the
# pure Python Element class.
base = ET._Element_Py
# Not from a C extension
self.assertEqual(base.__module__, 'xml.etree.ElementTree')
# Force some multiple inheritance with a C class to make things
# more interesting.
class MyElement(base, ValueError):
pass
self._check_element_factory_class(MyElement)
def test_doctype(self):
class DoctypeParser:
_doctype = None
def doctype(self, name, pubid, system):
self._doctype = (name, pubid, system)
def close(self):
return self._doctype
parser = ET.XMLParser(target=DoctypeParser())
parser.feed(self.sample1)
self.assertEqual(parser.close(),
('html', '-//W3C//DTD XHTML 1.0 Transitional//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'))
def test_builder_lookup_errors(self):
class RaisingBuilder:
def __init__(self, raise_in=None, what=ValueError):
self.raise_in = raise_in
self.what = what
def __getattr__(self, name):
if name == self.raise_in:
raise self.what(self.raise_in)
def handle(*args):
pass
return handle
ET.XMLParser(target=RaisingBuilder())
# cET also checks for 'close' and 'doctype', PyET does it only at need
for event in ('start', 'data', 'end', 'comment', 'pi'):
with self.assertRaisesRegex(ValueError, event):
ET.XMLParser(target=RaisingBuilder(event))
ET.XMLParser(target=RaisingBuilder(what=AttributeError))
for event in ('start', 'data', 'end', 'comment', 'pi'):
parser = ET.XMLParser(target=RaisingBuilder(event, what=AttributeError))
parser.feed(self.sample1)
self.assertIsNone(parser.close())
class XMLParserTest(unittest.TestCase):
sample1 = b'22'
sample2 = (b''
b'text')
sample3 = ('\n'
'$\xa3\u20ac\U0001017b')
def _check_sample_element(self, e):
self.assertEqual(e.tag, 'file')
self.assertEqual(e[0].tag, 'line')
self.assertEqual(e[0].text, '22')
def test_constructor_args(self):
parser2 = ET.XMLParser(encoding='utf-8',
target=ET.TreeBuilder())
parser2.feed(self.sample1)
self._check_sample_element(parser2.close())
def test_subclass(self):
class MyParser(ET.XMLParser):
pass
parser = MyParser()
parser.feed(self.sample1)
self._check_sample_element(parser.close())
def test_doctype_warning(self):
with warnings.catch_warnings():
warnings.simplefilter('error', DeprecationWarning)
parser = ET.XMLParser()
parser.feed(self.sample2)
parser.close()
def test_subclass_doctype(self):
_doctype = None
class MyParserWithDoctype(ET.XMLParser):
def doctype(self, *args, **kwargs):
nonlocal _doctype
_doctype = (args, kwargs)
parser = MyParserWithDoctype()
with self.assertWarnsRegex(RuntimeWarning, 'doctype'):
parser.feed(self.sample2)
parser.close()
self.assertIsNone(_doctype)
_doctype = _doctype2 = None
with warnings.catch_warnings():
warnings.simplefilter('error', DeprecationWarning)
warnings.simplefilter('error', RuntimeWarning)
class DoctypeParser:
def doctype(self, name, pubid, system):
nonlocal _doctype2
_doctype2 = (name, pubid, system)
parser = MyParserWithDoctype(target=DoctypeParser())
parser.feed(self.sample2)
parser.close()
self.assertIsNone(_doctype)
self.assertEqual(_doctype2,
('html', '-//W3C//DTD XHTML 1.0 Transitional//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'))
def test_inherited_doctype(self):
'''Ensure that ordinary usage is not deprecated (Issue 19176)'''
with warnings.catch_warnings():
warnings.simplefilter('error', DeprecationWarning)
warnings.simplefilter('error', RuntimeWarning)
class MyParserWithoutDoctype(ET.XMLParser):
pass
parser = MyParserWithoutDoctype()
parser.feed(self.sample2)
parser.close()
def test_parse_string(self):
parser = ET.XMLParser(target=ET.TreeBuilder())
parser.feed(self.sample3)
e = parser.close()
self.assertEqual(e.tag, 'money')
self.assertEqual(e.attrib['value'], '$\xa3\u20ac\U0001017b')
self.assertEqual(e.text, '$\xa3\u20ac\U0001017b')
class NamespaceParseTest(unittest.TestCase):
def test_find_with_namespace(self):
nsmap = {'h': 'hello', 'f': 'foo'}
doc = ET.fromstring(SAMPLE_XML_NS_ELEMS)
self.assertEqual(len(doc.findall('{hello}table', nsmap)), 1)
self.assertEqual(len(doc.findall('.//{hello}td', nsmap)), 2)
self.assertEqual(len(doc.findall('.//{foo}name', nsmap)), 1)
class ElementSlicingTest(unittest.TestCase):
def _elem_tags(self, elemlist):
return [e.tag for e in elemlist]
def _subelem_tags(self, elem):
return self._elem_tags(list(elem))
def _make_elem_with_children(self, numchildren):
"""Create an Element with a tag 'a', with the given amount of children
named 'a0', 'a1' ... and so on.
"""
e = ET.Element('a')
for i in range(numchildren):
ET.SubElement(e, 'a%s' % i)
return e
def test_getslice_single_index(self):
e = self._make_elem_with_children(10)
self.assertEqual(e[1].tag, 'a1')
self.assertEqual(e[-2].tag, 'a8')
self.assertRaises(IndexError, lambda: e[12])
self.assertRaises(IndexError, lambda: e[-12])
def test_getslice_range(self):
e = self._make_elem_with_children(6)
self.assertEqual(self._elem_tags(e[3:]), ['a3', 'a4', 'a5'])
self.assertEqual(self._elem_tags(e[3:6]), ['a3', 'a4', 'a5'])
self.assertEqual(self._elem_tags(e[3:16]), ['a3', 'a4', 'a5'])
self.assertEqual(self._elem_tags(e[3:5]), ['a3', 'a4'])
self.assertEqual(self._elem_tags(e[3:-1]), ['a3', 'a4'])
self.assertEqual(self._elem_tags(e[:2]), ['a0', 'a1'])
def test_getslice_steps(self):
e = self._make_elem_with_children(10)
self.assertEqual(self._elem_tags(e[8:10:1]), ['a8', 'a9'])
self.assertEqual(self._elem_tags(e[::3]), ['a0', 'a3', 'a6', 'a9'])
self.assertEqual(self._elem_tags(e[::8]), ['a0', 'a8'])
self.assertEqual(self._elem_tags(e[1::8]), ['a1', 'a9'])
self.assertEqual(self._elem_tags(e[3::sys.maxsize]), ['a3'])
self.assertEqual(self._elem_tags(e[3::sys.maxsize<<64]), ['a3'])
def test_getslice_negative_steps(self):
e = self._make_elem_with_children(4)
self.assertEqual(self._elem_tags(e[::-1]), ['a3', 'a2', 'a1', 'a0'])
self.assertEqual(self._elem_tags(e[::-2]), ['a3', 'a1'])
self.assertEqual(self._elem_tags(e[3::-sys.maxsize]), ['a3'])
self.assertEqual(self._elem_tags(e[3::-sys.maxsize-1]), ['a3'])
self.assertEqual(self._elem_tags(e[3::-sys.maxsize<<64]), ['a3'])
def test_delslice(self):
e = self._make_elem_with_children(4)
del e[0:2]
self.assertEqual(self._subelem_tags(e), ['a2', 'a3'])
e = self._make_elem_with_children(4)
del e[0:]
self.assertEqual(self._subelem_tags(e), [])
e = self._make_elem_with_children(4)
del e[::-1]
self.assertEqual(self._subelem_tags(e), [])
e = self._make_elem_with_children(4)
del e[::-2]
self.assertEqual(self._subelem_tags(e), ['a0', 'a2'])
e = self._make_elem_with_children(4)
del e[1::2]
self.assertEqual(self._subelem_tags(e), ['a0', 'a2'])
e = self._make_elem_with_children(2)
del e[::2]
self.assertEqual(self._subelem_tags(e), ['a1'])
def test_setslice_single_index(self):
e = self._make_elem_with_children(4)
e[1] = ET.Element('b')
self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a2', 'a3'])
e[-2] = ET.Element('c')
self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'c', 'a3'])
with self.assertRaises(IndexError):
e[5] = ET.Element('d')
with self.assertRaises(IndexError):
e[-5] = ET.Element('d')
self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'c', 'a3'])
def test_setslice_range(self):
e = self._make_elem_with_children(4)
e[1:3] = [ET.Element('b%s' % i) for i in range(2)]
self.assertEqual(self._subelem_tags(e), ['a0', 'b0', 'b1', 'a3'])
e = self._make_elem_with_children(4)
e[1:3] = [ET.Element('b')]
self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a3'])
e = self._make_elem_with_children(4)
e[1:3] = [ET.Element('b%s' % i) for i in range(3)]
self.assertEqual(self._subelem_tags(e), ['a0', 'b0', 'b1', 'b2', 'a3'])
def test_setslice_steps(self):
e = self._make_elem_with_children(6)
e[1:5:2] = [ET.Element('b%s' % i) for i in range(2)]
self.assertEqual(self._subelem_tags(e), ['a0', 'b0', 'a2', 'b1', 'a4', 'a5'])
e = self._make_elem_with_children(6)
with self.assertRaises(ValueError):
e[1:5:2] = [ET.Element('b')]
with self.assertRaises(ValueError):
e[1:5:2] = [ET.Element('b%s' % i) for i in range(3)]
with self.assertRaises(ValueError):
e[1:5:2] = []
self.assertEqual(self._subelem_tags(e), ['a0', 'a1', 'a2', 'a3', 'a4', 'a5'])
e = self._make_elem_with_children(4)
e[1::sys.maxsize] = [ET.Element('b')]
self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a2', 'a3'])
e[1::sys.maxsize<<64] = [ET.Element('c')]
self.assertEqual(self._subelem_tags(e), ['a0', 'c', 'a2', 'a3'])
def test_setslice_negative_steps(self):
e = self._make_elem_with_children(4)
e[2:0:-1] = [ET.Element('b%s' % i) for i in range(2)]
self.assertEqual(self._subelem_tags(e), ['a0', 'b1', 'b0', 'a3'])
e = self._make_elem_with_children(4)
with self.assertRaises(ValueError):
e[2:0:-1] = [ET.Element('b')]
with self.assertRaises(ValueError):
e[2:0:-1] = [ET.Element('b%s' % i) for i in range(3)]
with self.assertRaises(ValueError):
e[2:0:-1] = []
self.assertEqual(self._subelem_tags(e), ['a0', 'a1', 'a2', 'a3'])
e = self._make_elem_with_children(4)
e[1::-sys.maxsize] = [ET.Element('b')]
self.assertEqual(self._subelem_tags(e), ['a0', 'b', 'a2', 'a3'])
e[1::-sys.maxsize-1] = [ET.Element('c')]
self.assertEqual(self._subelem_tags(e), ['a0', 'c', 'a2', 'a3'])
e[1::-sys.maxsize<<64] = [ET.Element('d')]
self.assertEqual(self._subelem_tags(e), ['a0', 'd', 'a2', 'a3'])
class IOTest(unittest.TestCase):
def test_encoding(self):
# Test encoding issues.
elem = ET.Element("tag")
elem.text = "abc"
self.assertEqual(serialize(elem), 'abc')
for enc in ("utf-8", "us-ascii"):
with self.subTest(enc):
self.assertEqual(serialize(elem, encoding=enc),
b'abc')
self.assertEqual(serialize(elem, encoding=enc.upper()),
b'abc')
for enc in ("iso-8859-1", "utf-16", "utf-32"):
with self.subTest(enc):
self.assertEqual(serialize(elem, encoding=enc),
("\n"
"abc" % enc).encode(enc))
upper = enc.upper()
self.assertEqual(serialize(elem, encoding=upper),
("\n"
"abc" % upper).encode(enc))
elem = ET.Element("tag")
elem.text = "<&\"\'>"
self.assertEqual(serialize(elem), '<&"\'>')
self.assertEqual(serialize(elem, encoding="utf-8"),
b'<&"\'>')
self.assertEqual(serialize(elem, encoding="us-ascii"),
b'<&"\'>')
for enc in ("iso-8859-1", "utf-16", "utf-32"):
self.assertEqual(serialize(elem, encoding=enc),
("\n"
"<&\"'>" % enc).encode(enc))
elem = ET.Element("tag")
elem.attrib["key"] = "<&\"\'>"
self.assertEqual(serialize(elem), '')
self.assertEqual(serialize(elem, encoding="utf-8"),
b'')
self.assertEqual(serialize(elem, encoding="us-ascii"),
b'')
for enc in ("iso-8859-1", "utf-16", "utf-32"):
self.assertEqual(serialize(elem, encoding=enc),
("\n"
"" % enc).encode(enc))
elem = ET.Element("tag")
elem.text = '\xe5\xf6\xf6<>'
self.assertEqual(serialize(elem), '\xe5\xf6\xf6<>')
self.assertEqual(serialize(elem, encoding="utf-8"),
b'\xc3\xa5\xc3\xb6\xc3\xb6<>')
self.assertEqual(serialize(elem, encoding="us-ascii"),
b'åöö<>')
for enc in ("iso-8859-1", "utf-16", "utf-32"):
self.assertEqual(serialize(elem, encoding=enc),
("\n"
"åöö<>" % enc).encode(enc))
elem = ET.Element("tag")
elem.attrib["key"] = '\xe5\xf6\xf6<>'
self.assertEqual(serialize(elem), '')
self.assertEqual(serialize(elem, encoding="utf-8"),
b'')
self.assertEqual(serialize(elem, encoding="us-ascii"),
b'')
for enc in ("iso-8859-1", "utf-16", "utf-16le", "utf-16be", "utf-32"):
self.assertEqual(serialize(elem, encoding=enc),
("\n"
"" % enc).encode(enc))
def test_write_to_filename(self):
self.addCleanup(os_helper.unlink, TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
tree.write(TESTFN)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''ø''')
def test_write_to_filename_with_encoding(self):
self.addCleanup(os_helper.unlink, TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
tree.write(TESTFN, encoding='utf-8')
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''\xc3\xb8''')
tree.write(TESTFN, encoding='ISO-8859-1')
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), convlinesep(
b'''\n'''
b'''\xf8'''))
def test_write_to_filename_as_unicode(self):
self.addCleanup(os_helper.unlink, TESTFN)
with open(TESTFN, 'w') as f:
encoding = f.encoding
os_helper.unlink(TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
tree.write(TESTFN, encoding='unicode')
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b"\xc3\xb8")
def test_write_to_text_file(self):
self.addCleanup(os_helper.unlink, TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
with open(TESTFN, 'w', encoding='utf-8') as f:
tree.write(f, encoding='unicode')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''\xc3\xb8''')
with open(TESTFN, 'w', encoding='ascii', errors='xmlcharrefreplace') as f:
tree.write(f, encoding='unicode')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''ø''')
with open(TESTFN, 'w', encoding='ISO-8859-1') as f:
tree.write(f, encoding='unicode')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''\xf8''')
def test_write_to_binary_file(self):
self.addCleanup(os_helper.unlink, TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
with open(TESTFN, 'wb') as f:
tree.write(f)
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''ø''')
def test_write_to_binary_file_with_encoding(self):
self.addCleanup(os_helper.unlink, TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
with open(TESTFN, 'wb') as f:
tree.write(f, encoding='utf-8')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(), b'''\xc3\xb8''')
with open(TESTFN, 'wb') as f:
tree.write(f, encoding='ISO-8859-1')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(),
b'''\n'''
b'''\xf8''')
def test_write_to_binary_file_with_bom(self):
self.addCleanup(os_helper.unlink, TESTFN)
tree = ET.ElementTree(ET.XML('''\xf8'''))
# test BOM writing to buffered file
with open(TESTFN, 'wb') as f:
tree.write(f, encoding='utf-16')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(),
'''\n'''
'''\xf8'''.encode("utf-16"))
# test BOM writing to non-buffered file
with open(TESTFN, 'wb', buffering=0) as f:
tree.write(f, encoding='utf-16')
self.assertFalse(f.closed)
with open(TESTFN, 'rb') as f:
self.assertEqual(f.read(),
'''\n'''
'''\xf8'''.encode("utf-16"))
def test_read_from_stringio(self):
tree = ET.ElementTree()
stream = io.StringIO('''''')
tree.parse(stream)
self.assertEqual(tree.getroot().tag, 'site')
def test_write_to_stringio(self):
tree = ET.ElementTree(ET.XML('''\xf8'''))
stream = io.StringIO()
tree.write(stream, encoding='unicode')
self.assertEqual(stream.getvalue(), '''\xf8''')
def test_read_from_bytesio(self):
tree = ET.ElementTree()
raw = io.BytesIO(b'''''')
tree.parse(raw)
self.assertEqual(tree.getroot().tag, 'site')
def test_write_to_bytesio(self):
tree = ET.ElementTree(ET.XML('''\xf8'''))
raw = io.BytesIO()
tree.write(raw)
self.assertEqual(raw.getvalue(), b'''ø''')
class dummy:
pass
def test_read_from_user_text_reader(self):
stream = io.StringIO('''''')
reader = self.dummy()
reader.read = stream.read
tree = ET.ElementTree()
tree.parse(reader)
self.assertEqual(tree.getroot().tag, 'site')
def test_write_to_user_text_writer(self):
tree = ET.ElementTree(ET.XML('''\xf8'''))
stream = io.StringIO()
writer = self.dummy()
writer.write = stream.write
tree.write(writer, encoding='unicode')
self.assertEqual(stream.getvalue(), '''\xf8''')
def test_read_from_user_binary_reader(self):
raw = io.BytesIO(b'''''')
reader = self.dummy()
reader.read = raw.read
tree = ET.ElementTree()
tree.parse(reader)
self.assertEqual(tree.getroot().tag, 'site')
tree = ET.ElementTree()
def test_write_to_user_binary_writer(self):
tree = ET.ElementTree(ET.XML('''\xf8'''))
raw = io.BytesIO()
writer = self.dummy()
writer.write = raw.write
tree.write(writer)
self.assertEqual(raw.getvalue(), b'''ø''')
def test_write_to_user_binary_writer_with_bom(self):
tree = ET.ElementTree(ET.XML(''''''))
raw = io.BytesIO()
writer = self.dummy()
writer.write = raw.write
writer.seekable = lambda: True
writer.tell = raw.tell
tree.write(writer, encoding="utf-16")
self.assertEqual(raw.getvalue(),
'''\n'''
''''''.encode("utf-16"))
def test_tostringlist_invariant(self):
root = ET.fromstring('foo')
self.assertEqual(
ET.tostring(root, 'unicode'),
''.join(ET.tostringlist(root, 'unicode')))
self.assertEqual(
ET.tostring(root, 'utf-16'),
b''.join(ET.tostringlist(root, 'utf-16')))
def test_short_empty_elements(self):
root = ET.fromstring('abc')
self.assertEqual(
ET.tostring(root, 'unicode'),
'abc')
self.assertEqual(
ET.tostring(root, 'unicode', short_empty_elements=True),
'abc')
self.assertEqual(
ET.tostring(root, 'unicode', short_empty_elements=False),
'abc')
class ParseErrorTest(unittest.TestCase):
def test_subclass(self):
self.assertIsInstance(ET.ParseError(), SyntaxError)
def _get_error(self, s):
try:
ET.fromstring(s)
except ET.ParseError as e:
return e
def test_error_position(self):
self.assertEqual(self._get_error('foo').position, (1, 0))
self.assertEqual(self._get_error('&foo;').position, (1, 5))
self.assertEqual(self._get_error('foobar<').position, (1, 6))
def test_error_code(self):
import xml.parsers.expat.errors as ERRORS
self.assertEqual(self._get_error('foo').code,
ERRORS.codes[ERRORS.XML_ERROR_SYNTAX])
class KeywordArgsTest(unittest.TestCase):
# Test various issues with keyword arguments passed to ET.Element
# constructor and methods
def test_issue14818(self):
x = ET.XML("foo")
self.assertEqual(x.find('a', None),
x.find(path='a', namespaces=None))
self.assertEqual(x.findtext('a', None, None),
x.findtext(path='a', default=None, namespaces=None))
self.assertEqual(x.findall('a', None),
x.findall(path='a', namespaces=None))
self.assertEqual(list(x.iterfind('a', None)),
list(x.iterfind(path='a', namespaces=None)))
self.assertEqual(ET.Element('a').attrib, {})
elements = [
ET.Element('a', dict(href="#", id="foo")),
ET.Element('a', attrib=dict(href="#", id="foo")),
ET.Element('a', dict(href="#"), id="foo"),
ET.Element('a', href="#", id="foo"),
ET.Element('a', dict(href="#", id="foo"), href="#", id="foo"),
]
for e in elements:
self.assertEqual(e.tag, 'a')
self.assertEqual(e.attrib, dict(href="#", id="foo"))
e2 = ET.SubElement(elements[0], 'foobar', attrib={'key1': 'value1'})
self.assertEqual(e2.attrib['key1'], 'value1')
with self.assertRaisesRegex(TypeError, 'must be dict, not str'):
ET.Element('a', "I'm not a dict")
with self.assertRaisesRegex(TypeError, 'must be dict, not str'):
ET.Element('a', attrib="I'm not a dict")
# --------------------------------------------------------------------
class NoAcceleratorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
if ET is not pyET:
raise unittest.SkipTest('only for the Python version')
# Test that the C accelerator was not imported for pyET
def test_correct_import_pyET(self):
# The type of methods defined in Python code is types.FunctionType,
# while the type of methods defined inside _elementtree is
#
self.assertIsInstance(pyET.Element.__init__, types.FunctionType)
self.assertIsInstance(pyET.XMLParser.__init__, types.FunctionType)
# --------------------------------------------------------------------
class BoolTest(unittest.TestCase):
def test_warning(self):
e = ET.fromstring('')
msg = (
r"Testing an element's truth value will raise an exception in "
r"future versions. "
r"Use specific 'len\(elem\)' or 'elem is not None' test instead.")
with self.assertWarnsRegex(DeprecationWarning, msg):
result = bool(e)
# Emulate prior behavior for now
self.assertIs(result, False)
# Element with children
ET.SubElement(e, 'b')
with self.assertWarnsRegex(DeprecationWarning, msg):
new_result = bool(e)
self.assertIs(new_result, True)
# --------------------------------------------------------------------
def c14n_roundtrip(xml, **options):
return pyET.canonicalize(xml, **options)
class C14NTest(unittest.TestCase):
maxDiff = None
#
# simple roundtrip tests (from c14n.py)
def test_simple_roundtrip(self):
# Basics
self.assertEqual(c14n_roundtrip(""), '')
self.assertEqual(c14n_roundtrip(""), # FIXME
'')
self.assertEqual(c14n_roundtrip(""),
'')
self.assertEqual(c14n_roundtrip(""),
'')
self.assertEqual(c14n_roundtrip(""),
'')
# C14N spec
self.assertEqual(c14n_roundtrip("Hello, world!"),
'Hello, world!')
self.assertEqual(c14n_roundtrip("2"),
'2')
self.assertEqual(c14n_roundtrip('"0" && value<"10" ?"valid":"error"]]>'),
'value>"0" && value<"10" ?"valid":"error"')
self.assertEqual(c14n_roundtrip('''valid'''),
'valid')
self.assertEqual(c14n_roundtrip(""),
'')
self.assertEqual(c14n_roundtrip(""),
'')
self.assertEqual(c14n_roundtrip(""),
'')
# fragments from PJ's tests
#self.assertEqual(c14n_roundtrip(""),
#'')
# Namespace issues
xml = ''
self.assertEqual(c14n_roundtrip(xml), xml)
xml = ''
self.assertEqual(c14n_roundtrip(xml), xml)
xml = ''
self.assertEqual(c14n_roundtrip(xml), xml)
def test_c14n_exclusion(self):
xml = textwrap.dedent("""\
abtextbtextdtext
""")
self.assertEqual(
c14n_roundtrip(xml, strip_text=True),
''
'abtext'
'btext'
'dtext'
'')
self.assertEqual(
c14n_roundtrip(xml, strip_text=True, exclude_attrs=['{http://example.com/x}attr']),
''
'abtext'
'btext'
'dtext'
'')
self.assertEqual(
c14n_roundtrip(xml, strip_text=True, exclude_tags=['{http://example.com/x}d']),
''
'abtext'
'btext'
''
'')
self.assertEqual(
c14n_roundtrip(xml, strip_text=True, exclude_attrs=['{http://example.com/x}attr'],
exclude_tags=['{http://example.com/x}d']),
''
'abtext'
'btext'
''
'')
self.assertEqual(
c14n_roundtrip(xml, strip_text=True, exclude_tags=['a', 'b']),
''
'dtext'
'')
self.assertEqual(
c14n_roundtrip(xml, exclude_tags=['a', 'b']),
'\n'
' \n'
' \n'
' \n'
' dtext\n'
' \n'
'')
self.assertEqual(
c14n_roundtrip(xml, strip_text=True, exclude_tags=['{http://example.com/x}d', 'b']),
''
''
''
'')
self.assertEqual(
c14n_roundtrip(xml, exclude_tags=['{http://example.com/x}d', 'b']),
'\n'
' \n'
' \n'
' \n'
' \n'
' \n'
' \n'
' \n'
'')
#
# basic method=c14n tests from the c14n 2.0 specification. uses
# test files under xmltestdata/c14n-20.
# note that this uses generated C14N versions of the standard ET.write
# output, not roundtripped C14N (see above).
def test_xml_c14n2(self):
datadir = findfile("c14n-20", subdir="xmltestdata")
full_path = partial(os.path.join, datadir)
files = [filename[:-4] for filename in sorted(os.listdir(datadir))
if filename.endswith('.xml')]
input_files = [
filename for filename in files
if filename.startswith('in')
]
configs = {
filename: {
# sequential
option.tag.split('}')[-1]: ((option.text or '').strip(), option)
for option in ET.parse(full_path(filename) + ".xml").getroot()
}
for filename in files
if filename.startswith('c14n')
}
tests = {
input_file: [
(filename, configs[filename.rsplit('_', 1)[-1]])
for filename in files
if filename.startswith(f'out_{input_file}_')
and filename.rsplit('_', 1)[-1] in configs
]
for input_file in input_files
}
# Make sure we found all test cases.
self.assertEqual(30, len([
output_file for output_files in tests.values()
for output_file in output_files]))
def get_option(config, option_name, default=None):
return config.get(option_name, (default, ()))[0]
for input_file, output_files in tests.items():
for output_file, config in output_files:
keep_comments = get_option(
config, 'IgnoreComments') == 'true' # no, it's right :)
strip_text = get_option(
config, 'TrimTextNodes') == 'true'
rewrite_prefixes = get_option(
config, 'PrefixRewrite') == 'sequential'
if 'QNameAware' in config:
qattrs = [
f"{{{el.get('NS')}}}{el.get('Name')}"
for el in config['QNameAware'][1].findall(
'{http://www.w3.org/2010/xml-c14n2}QualifiedAttr')
]
qtags = [
f"{{{el.get('NS')}}}{el.get('Name')}"
for el in config['QNameAware'][1].findall(
'{http://www.w3.org/2010/xml-c14n2}Element')
]
else:
qtags = qattrs = None
# Build subtest description from config.
config_descr = ','.join(
f"{name}={value or ','.join(c.tag.split('}')[-1] for c in children)}"
for name, (value, children) in sorted(config.items())
)
with self.subTest(f"{output_file}({config_descr})"):
if input_file == 'inNsRedecl' and not rewrite_prefixes:
self.skipTest(
f"Redeclared namespace handling is not supported in {output_file}")
if input_file == 'inNsSuperfluous' and not rewrite_prefixes:
self.skipTest(
f"Redeclared namespace handling is not supported in {output_file}")
if 'QNameAware' in config and config['QNameAware'][1].find(
'{http://www.w3.org/2010/xml-c14n2}XPathElement') is not None:
self.skipTest(
f"QName rewriting in XPath text is not supported in {output_file}")
f = full_path(input_file + ".xml")
if input_file == 'inC14N5':
# Hack: avoid setting up external entity resolution in the parser.
with open(full_path('world.txt'), 'rb') as entity_file:
with open(f, 'rb') as f:
f = io.BytesIO(f.read().replace(b'&ent2;', entity_file.read()))
text = ET.canonicalize(
from_file=f,
with_comments=keep_comments,
strip_text=strip_text,
rewrite_prefixes=rewrite_prefixes,
qname_aware_tags=qtags, qname_aware_attrs=qattrs)
with open(full_path(output_file + ".xml"), 'r', encoding='utf8') as f:
expected = f.read()
if input_file == 'inC14N3':
# FIXME: cET resolves default attributes but ET does not!
expected = expected.replace(' attr="default"', '')
text = text.replace(' attr="default"', '')
self.assertEqual(expected, text)
# --------------------------------------------------------------------
def setUpModule(module=None):
# When invoked without a module, runs the Python ET tests by loading pyET.
# Otherwise, uses the given module as the ET.
global pyET
pyET = import_fresh_module('xml.etree.ElementTree',
blocked=['_elementtree'])
if module is None:
module = pyET
global ET
ET = module
# don't interfere with subsequent tests
def cleanup():
global ET, pyET
ET = pyET = None
unittest.addModuleCleanup(cleanup)
# Provide default namespace mapping and path cache.
from xml.etree import ElementPath
nsmap = ET.register_namespace._namespace_map
# Copy the default namespace mapping
nsmap_copy = nsmap.copy()
unittest.addModuleCleanup(nsmap.update, nsmap_copy)
unittest.addModuleCleanup(nsmap.clear)
# Copy the path cache (should be empty)
path_cache = ElementPath._cache
unittest.addModuleCleanup(setattr, ElementPath, "_cache", path_cache)
ElementPath._cache = path_cache.copy()
# Align the Comment/PI factories.
if hasattr(ET, '_set_factories'):
old_factories = ET._set_factories(ET.Comment, ET.PI)
unittest.addModuleCleanup(ET._set_factories, *old_factories)
if __name__ == '__main__':
unittest.main()