mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 11:14:38 +08:00
* Align XML parsing and libraries with Python 3.15 Retain the existing XML backend accounting changes and their explicit registry publication limitation. Preserve canonical XML tests and existing RustPython guards, including multiline marker explanations through update_lib. Assisted-by: OpenAI Codex:model version unavailable * Retire proven XML guards and sync canonical consumers Assisted-by: OpenAI Codex:model version unavailable * Rebase vendored xml onto 1.4.0 Replace the vendored crate sources with the published xml 1.4.0 archive and reapply the amplification accounting patch on top of it. The two lexer hunks that conflicted with the new line-ending normalization are merged into the 1.4.0 read loop. The patch no longer normalizes CR in entity values or collapses CRLF in attribute values, because the 1.4.0 lexer and attribute reader already do so. Update the checksums, recorded upstream commit, archive hash and provenance notes, and require version 1.4 for the path dependency. Assisted-by: Claude Code:claude-opus-5-5 * Remove stale SAX external resolver expected failure Linux and macOS canonical CI both demonstrate that the existing unsupported-scheme resolver test passes. Retire only its expected-failure decorator through update_lib, preserving its canonical assertions. This does not implement external DTD parsing. AI assistance: dot diagnosed CI, migrated the marker, and reviewed the source delta and normal-hook validation; the model version is not exposed. Assisted-by: dot: model version not exposed --------- Co-authored-by: Jeong YunWon <jeong@youknowone.org> Co-authored-by: youknowdot <336763669+youknowdot@users.noreply.github.com>
350 lines
12 KiB
Python
Vendored
350 lines
12 KiB
Python
Vendored
import io
|
|
import unittest
|
|
import xml.sax
|
|
|
|
from xml.sax.xmlreader import AttributesImpl
|
|
from xml.sax.handler import feature_external_ges
|
|
from xml.dom import pulldom
|
|
|
|
from test.support import findfile
|
|
|
|
|
|
tstfile = findfile("test.xml", subdir="xmltestdata")
|
|
|
|
# A handy XML snippet, containing attributes, a namespace prefix, and a
|
|
# self-closing tag:
|
|
SMALL_SAMPLE = """<?xml version="1.0"?>
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xdc="http://www.xml.com/books">
|
|
<!-- A comment -->
|
|
<title>Introduction to XSL</title>
|
|
<hr/>
|
|
<p><xdc:author xdc:attrib="prefixed attribute" attrib="other attrib">A. Namespace</xdc:author></p>
|
|
</html>"""
|
|
|
|
|
|
class PullDOMTestCase(unittest.TestCase):
|
|
|
|
def test_parse(self):
|
|
"""Minimal test of DOMEventStream.parse()"""
|
|
|
|
# This just tests that parsing from a stream works. Actual parser
|
|
# semantics are tested using parseString with a more focused XML
|
|
# fragment.
|
|
|
|
# Test with a filename:
|
|
handler = pulldom.parse(tstfile)
|
|
self.addCleanup(handler.stream.close)
|
|
list(handler)
|
|
|
|
# Test with a file object:
|
|
with open(tstfile, "rb") as fin:
|
|
list(pulldom.parse(fin))
|
|
|
|
def test_parse_semantics(self):
|
|
"""Test DOMEventStream parsing semantics."""
|
|
|
|
items = pulldom.parseString(SMALL_SAMPLE)
|
|
evt, node = next(items)
|
|
# Just check the node is a Document:
|
|
self.assertHasAttr(node, "createElement")
|
|
self.assertEqual(pulldom.START_DOCUMENT, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.START_ELEMENT, evt)
|
|
self.assertEqual("html", node.tagName)
|
|
self.assertEqual(2, len(node.attributes))
|
|
self.assertEqual(node.attributes.getNamedItem("xmlns:xdc").value,
|
|
"http://www.xml.com/books")
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.CHARACTERS, evt) # Line break
|
|
evt, node = next(items)
|
|
# XXX - A comment should be reported here!
|
|
# self.assertEqual(pulldom.COMMENT, evt)
|
|
# Line break after swallowed comment:
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual("title", node.tagName)
|
|
title_node = node
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
self.assertEqual("Introduction to XSL", node.data)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
self.assertEqual("title", node.tagName)
|
|
self.assertTrue(title_node is node)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.START_ELEMENT, evt)
|
|
self.assertEqual("hr", node.tagName)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
self.assertEqual("hr", node.tagName)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.START_ELEMENT, evt)
|
|
self.assertEqual("p", node.tagName)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.START_ELEMENT, evt)
|
|
self.assertEqual("xdc:author", node.tagName)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
self.assertEqual("xdc:author", node.tagName)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
# XXX No END_DOCUMENT item is ever obtained:
|
|
#evt, node = next(items)
|
|
#self.assertEqual(pulldom.END_DOCUMENT, evt)
|
|
|
|
def test_expandItem(self):
|
|
"""Ensure expandItem works as expected."""
|
|
items = pulldom.parseString(SMALL_SAMPLE)
|
|
# Loop through the nodes until we get to a "title" start tag:
|
|
for evt, item in items:
|
|
if evt == pulldom.START_ELEMENT and item.tagName == "title":
|
|
items.expandNode(item)
|
|
self.assertEqual(1, len(item.childNodes))
|
|
break
|
|
else:
|
|
self.fail("No \"title\" element detected in SMALL_SAMPLE!")
|
|
# Loop until we get to the next start-element:
|
|
for evt, node in items:
|
|
if evt == pulldom.START_ELEMENT:
|
|
break
|
|
self.assertEqual("hr", node.tagName,
|
|
"expandNode did not leave DOMEventStream in the correct state.")
|
|
# Attempt to expand a standalone element:
|
|
items.expandNode(node)
|
|
self.assertEqual(next(items)[0], pulldom.CHARACTERS)
|
|
evt, node = next(items)
|
|
self.assertEqual(node.tagName, "p")
|
|
items.expandNode(node)
|
|
next(items) # Skip character data
|
|
evt, node = next(items)
|
|
self.assertEqual(node.tagName, "html")
|
|
with self.assertRaises(StopIteration):
|
|
next(items)
|
|
items.clear()
|
|
self.assertIsNone(items.parser)
|
|
self.assertIsNone(items.stream)
|
|
|
|
@unittest.expectedFailure
|
|
def test_comment(self):
|
|
"""PullDOM does not receive "comment" events."""
|
|
items = pulldom.parseString(SMALL_SAMPLE)
|
|
for evt, _ in items:
|
|
if evt == pulldom.COMMENT:
|
|
break
|
|
else:
|
|
self.fail("No comment was encountered")
|
|
|
|
@unittest.expectedFailure
|
|
def test_end_document(self):
|
|
"""PullDOM does not receive "end-document" events."""
|
|
items = pulldom.parseString(SMALL_SAMPLE)
|
|
# Read all of the nodes up to and including </html>:
|
|
for evt, node in items:
|
|
if evt == pulldom.END_ELEMENT and node.tagName == "html":
|
|
break
|
|
try:
|
|
# Assert that the next node is END_DOCUMENT:
|
|
evt, node = next(items)
|
|
self.assertEqual(pulldom.END_DOCUMENT, evt)
|
|
except StopIteration:
|
|
self.fail(
|
|
"Ran out of events, but should have received END_DOCUMENT")
|
|
|
|
def test_external_ges_default(self):
|
|
parser = pulldom.parseString(SMALL_SAMPLE)
|
|
saxparser = parser.parser
|
|
ges = saxparser.getFeature(feature_external_ges)
|
|
self.assertEqual(ges, False)
|
|
|
|
|
|
class ThoroughTestCase(unittest.TestCase):
|
|
"""Test the hard-to-reach parts of pulldom."""
|
|
|
|
def test_thorough_parse(self):
|
|
"""Test some of the hard-to-reach parts of PullDOM."""
|
|
self._test_thorough(pulldom.parse(None, parser=SAXExerciser()))
|
|
|
|
@unittest.expectedFailure
|
|
def test_sax2dom_fail(self):
|
|
"""SAX2DOM can"t handle a PI before the root element."""
|
|
pd = SAX2DOMTestHelper(None, SAXExerciser(), 12)
|
|
self._test_thorough(pd)
|
|
|
|
def test_thorough_sax2dom(self):
|
|
"""Test some of the hard-to-reach parts of SAX2DOM."""
|
|
pd = SAX2DOMTestHelper(None, SAX2DOMExerciser(), 12)
|
|
self._test_thorough(pd, False)
|
|
|
|
def _test_thorough(self, pd, before_root=True):
|
|
"""Test some of the hard-to-reach parts of the parser, using a mock
|
|
parser."""
|
|
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.START_DOCUMENT, evt)
|
|
# Just check the node is a Document:
|
|
self.assertHasAttr(node, "createElement")
|
|
|
|
if before_root:
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.COMMENT, evt)
|
|
self.assertEqual("a comment", node.data)
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.PROCESSING_INSTRUCTION, evt)
|
|
self.assertEqual("target", node.target)
|
|
self.assertEqual("data", node.data)
|
|
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.START_ELEMENT, evt)
|
|
self.assertEqual("html", node.tagName)
|
|
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.COMMENT, evt)
|
|
self.assertEqual("a comment", node.data)
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.PROCESSING_INSTRUCTION, evt)
|
|
self.assertEqual("target", node.target)
|
|
self.assertEqual("data", node.data)
|
|
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.START_ELEMENT, evt)
|
|
self.assertEqual("p", node.tagName)
|
|
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.CHARACTERS, evt)
|
|
self.assertEqual("text", node.data)
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
self.assertEqual("p", node.tagName)
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.END_ELEMENT, evt)
|
|
self.assertEqual("html", node.tagName)
|
|
evt, node = next(pd)
|
|
self.assertEqual(pulldom.END_DOCUMENT, evt)
|
|
|
|
|
|
class SAXExerciser(object):
|
|
"""A fake sax parser that calls some of the harder-to-reach sax methods to
|
|
ensure it emits the correct events"""
|
|
|
|
def setContentHandler(self, handler):
|
|
self._handler = handler
|
|
|
|
def parse(self, _):
|
|
h = self._handler
|
|
h.startDocument()
|
|
|
|
# The next two items ensure that items preceding the first
|
|
# start_element are properly stored and emitted:
|
|
h.comment("a comment")
|
|
h.processingInstruction("target", "data")
|
|
|
|
h.startElement("html", AttributesImpl({}))
|
|
|
|
h.comment("a comment")
|
|
h.processingInstruction("target", "data")
|
|
|
|
h.startElement("p", AttributesImpl({"class": "paraclass"}))
|
|
h.characters("text")
|
|
h.endElement("p")
|
|
h.endElement("html")
|
|
h.endDocument()
|
|
|
|
def stub(self, *args, **kwargs):
|
|
"""Stub method. Does nothing."""
|
|
pass
|
|
setProperty = stub
|
|
setFeature = stub
|
|
|
|
|
|
class SAX2DOMExerciser(SAXExerciser):
|
|
"""The same as SAXExerciser, but without the processing instruction and
|
|
comment before the root element, because S2D can"t handle it"""
|
|
|
|
def parse(self, _):
|
|
h = self._handler
|
|
h.startDocument()
|
|
h.startElement("html", AttributesImpl({}))
|
|
h.comment("a comment")
|
|
h.processingInstruction("target", "data")
|
|
h.startElement("p", AttributesImpl({"class": "paraclass"}))
|
|
h.characters("text")
|
|
h.endElement("p")
|
|
h.endElement("html")
|
|
h.endDocument()
|
|
|
|
|
|
class SAX2DOMTestHelper(pulldom.DOMEventStream):
|
|
"""Allows us to drive SAX2DOM from a DOMEventStream."""
|
|
|
|
def reset(self):
|
|
self.pulldom = pulldom.SAX2DOM()
|
|
# This content handler relies on namespace support
|
|
self.parser.setFeature(xml.sax.handler.feature_namespaces, 1)
|
|
self.parser.setContentHandler(self.pulldom)
|
|
|
|
|
|
class SAX2DOMTestCase(unittest.TestCase):
|
|
|
|
def confirm(self, test, testname="Test"):
|
|
self.assertTrue(test, testname)
|
|
|
|
def test_basic(self):
|
|
"""Ensure SAX2DOM can parse from a stream."""
|
|
with io.StringIO(SMALL_SAMPLE) as fin:
|
|
sd = SAX2DOMTestHelper(fin, xml.sax.make_parser(),
|
|
len(SMALL_SAMPLE))
|
|
for evt, node in sd:
|
|
if evt == pulldom.START_ELEMENT and node.tagName == "html":
|
|
break
|
|
# Because the buffer is the same length as the XML, all the
|
|
# nodes should have been parsed and added:
|
|
self.assertGreater(len(node.childNodes), 0)
|
|
|
|
def testSAX2DOM(self):
|
|
"""Ensure SAX2DOM expands nodes as expected."""
|
|
sax2dom = pulldom.SAX2DOM()
|
|
sax2dom.startDocument()
|
|
sax2dom.startElement("doc", {})
|
|
sax2dom.characters("text")
|
|
sax2dom.startElement("subelm", {})
|
|
sax2dom.characters("text")
|
|
sax2dom.endElement("subelm")
|
|
sax2dom.characters("text")
|
|
sax2dom.endElement("doc")
|
|
sax2dom.endDocument()
|
|
|
|
doc = sax2dom.document
|
|
root = doc.documentElement
|
|
(text1, elm1, text2) = root.childNodes
|
|
text3 = elm1.childNodes[0]
|
|
|
|
self.assertIsNone(text1.previousSibling)
|
|
self.assertIs(text1.nextSibling, elm1)
|
|
self.assertIs(elm1.previousSibling, text1)
|
|
self.assertIs(elm1.nextSibling, text2)
|
|
self.assertIs(text2.previousSibling, elm1)
|
|
self.assertIsNone(text2.nextSibling)
|
|
self.assertIsNone(text3.previousSibling)
|
|
self.assertIsNone(text3.nextSibling)
|
|
|
|
self.assertIs(root.parentNode, doc)
|
|
self.assertIs(text1.parentNode, root)
|
|
self.assertIs(elm1.parentNode, root)
|
|
self.assertIs(text2.parentNode, root)
|
|
self.assertIs(text3.parentNode, elm1)
|
|
doc.unlink()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|