Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Doc/library/pyexpat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ XMLParser Objects
or ``None`` if :meth:`SetBase` hasn't been called.


.. method:: xmlparser.GetSpecifiedAttributeCount()

Return the index just past the attributes given in the start tag.
Attributes defaulted from the DTD follow the specified ones,
so attributes at lower indices in the list
passed to :attr:`StartElementHandler` were given in the start tag.
Each attribute takes two items in that list,
its name and its value.
Only meaningful inside a :attr:`StartElementHandler` call,
and only if :attr:`ordered_attributes` is true.

.. versionadded:: next


.. method:: xmlparser.GetInputContext()

Returns the input data that generated the current event as a string. The data is
Expand Down
9 changes: 6 additions & 3 deletions Doc/library/xml.dom.minidom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,14 @@

* :class:`DOMTimeStamp`

* :class:`EntityReference`

Most of these reflect information in the XML document that is not of general
This reflects information in the XML document that is not of general
utility to most DOM users.

.. versionchanged:: next
:class:`EntityReference` is now implemented.

Check warning on line 262 in Doc/library/xml.dom.minidom.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:class reference target not found: EntityReference [ref.class]
Note that the parser expands entity references,
so they only occur in a document if created explicitly.

.. rubric:: Footnotes

.. [1] The encoding name included in the XML output should conform to
Expand Down
27 changes: 27 additions & 0 deletions Doc/library/xml.dom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,14 @@
tree.


.. method:: Document.createEntityReference(name)

Create and return a new entity reference node.
The node is not inserted into the document when it is created.

.. versionadded:: next


.. method:: Document.createComment(data)

Create and return a comment node containing the data passed as a parameter. As
Expand Down Expand Up @@ -800,6 +808,25 @@
character.


.. _dom-entityreference-objects:

EntityReference Objects
^^^^^^^^^^^^^^^^^^^^^^^

.. class:: EntityReference

Represents an entity reference in the XML document.

Check warning on line 818 in Doc/library/xml.dom.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:attr reference target not found: nodeName [ref.attr]

Check warning on line 818 in Doc/library/xml.dom.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:class reference target not found: Node [ref.class]
It is a subclass of :class:`Node`.
The name of the referenced entity is its :attr:`nodeName`.
Its children are the replacement text of the entity,
and are read-only.

Parsers may expand entity references,
so such a node only occurs in a document if it was created explicitly.

.. versionadded:: next


.. _dom-exceptions:

Exceptions
Expand Down
27 changes: 27 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,22 @@ xml
instead of failing later, when encountering non-ASCII data.
(Contributed by Serhiy Storchaka in :gh:`62259`.)

* :mod:`xml.dom.minidom` now conforms closer to the DOM Level 1 specification.
It checks names passed to the factory methods,
rejects inserting a node created by other document
or making a node a descendant of itself,
reports attributes defaulted in the DTD
and whether an attribute was given in the start tag,
and implements :class:`!EntityReference` nodes
and :meth:`!Document.createEntityReference`.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)

* Add :meth:`!GetSpecifiedAttributeCount` method
to the :mod:`XML parser <xml.parsers.expat>` objects.
It tells how many of the reported attributes were given in the start tag
rather than defaulted from the DTD.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)

zipfile
-------

Expand Down Expand Up @@ -828,6 +844,17 @@ that may require changes to your code.
:exc:`TypeError`.
(Contributed by Serhiy Storchaka in :gh:`152587`.)

* :mod:`xml.dom.minidom` now raises :exc:`~xml.dom.InvalidCharacterErr`
for a name which is not a valid XML name,
:exc:`~xml.dom.WrongDocumentErr`
for inserting a node created by other document,
and :exc:`~xml.dom.HierarchyRequestErr` for inserting a node into itself
or its descendant.
Such operations formerly succeeded
and produced an invalid document or an endless loop.
Attributes defaulted in the DTD are no longer omitted when parsing.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)

* On Windows, seeking a pipe now fails instead of silently appearing to
succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`,
and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence,
Expand Down
111 changes: 111 additions & 0 deletions Lib/test/test_minidom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1784,5 +1784,116 @@ def test_cdata_parsing(self):
dom2 = parseString(dom1.toprettyxml())
self.checkWholeText(dom2.getElementsByTagName('node')[0].firstChild, '</data>')

def testInvalidCharacterErr(self):
doc = parseString("<doc/>")
impl = getDOMImplementation()
for name in ("", "bad name", "1st", "-x", ".x", "a<b", "a&b", "a\tb"):
with self.subTest(name=name):
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.createElement, name)
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.createElementNS, None, name)
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.createAttribute, name)
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.createAttributeNS, None, name)
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.createProcessingInstruction, name, "")
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.createEntityReference, name)
self.assertRaises(xml.dom.InvalidCharacterErr,
impl.createDocumentType, name, None, None)
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.documentElement.setAttribute, name, "v")
self.assertRaises(xml.dom.InvalidCharacterErr,
doc.documentElement.setAttributeNS,
None, name, "v")
for name in ("a", "_x", ":x", "a.b-c", "ns:tag", "a1",
"\N{GREEK CAPITAL LETTER OMEGA}", "\N{LINEAR B SYLLABLE B008 A}x"):
with self.subTest(name=name):
self.assertEqual(doc.createElement(name).tagName, name)
self.assertEqual(doc.createAttribute(name).name, name)
doc.unlink()

def testWrongDocumentErr(self):
doc = parseString("<doc><child/></doc>")
other = parseString("<other/>")
elem = doc.documentElement
alien = other.createElement("alien")
self.assertRaises(xml.dom.WrongDocumentErr, elem.appendChild, alien)
self.assertRaises(xml.dom.WrongDocumentErr, elem.insertBefore,
alien, elem.firstChild)
self.assertRaises(xml.dom.WrongDocumentErr, elem.replaceChild,
alien, elem.firstChild)
self.assertRaises(xml.dom.WrongDocumentErr, doc.appendChild, alien)
# the rejected node is left alone
self.assertIs(alien.ownerDocument, other)
self.assertIsNone(alien.parentNode)
# importNode() is the supported way to do this
elem.appendChild(doc.importNode(alien, True))
self.assertEqual(elem.lastChild.tagName, "alien")
doc.unlink()
other.unlink()

def testAncestorLoops(self):
doc = parseString("<doc><child><grandchild/></child></doc>")
elem = doc.documentElement
child = elem.firstChild
grandchild = child.firstChild
for node in elem, child, grandchild:
self.assertRaises(xml.dom.HierarchyRequestErr,
node.appendChild, node)
self.assertRaises(xml.dom.HierarchyRequestErr, child.appendChild, elem)
self.assertRaises(xml.dom.HierarchyRequestErr,
grandchild.appendChild, elem)
self.assertRaises(xml.dom.HierarchyRequestErr,
grandchild.insertBefore, child, None)
self.assertRaises(xml.dom.HierarchyRequestErr,
grandchild.replaceChild, elem, None)
# the tree is unchanged
self.assertIs(child.parentNode, elem)
self.assertIs(grandchild.parentNode, child)
doc.unlink()

def testAttrSpecified(self):
doc = parseString("<!DOCTYPE doc ["
" <!ELEMENT doc EMPTY>"
" <!ATTLIST doc a CDATA 'default' b CDATA #IMPLIED>"
"]><doc b='given'/>")
elem = doc.documentElement
# attributes defaulted from the DTD are reported too
self.assertEqual(sorted(elem.attributes.keys()), ["a", "b"])
self.assertEqual(elem.getAttribute("a"), "default")
self.assertFalse(elem.getAttributeNode("a").specified)
self.assertEqual(elem.getAttribute("b"), "given")
self.assertTrue(elem.getAttributeNode("b").specified)
doc.unlink()

def testEntityReference(self):
doc = parseString("<doc/>")
ref = doc.createEntityReference("ent")
self.assertEqual(ref.nodeType, Node.ENTITY_REFERENCE_NODE)
self.assertEqual(ref.nodeName, "ent")
self.assertIsNone(ref.nodeValue)
self.assertIs(ref.ownerDocument, doc)
doc.documentElement.appendChild(ref)
self.assertEqual(doc.documentElement.toxml(), "<doc>&ent;</doc>")
# entity reference nodes are read-only
text = doc.createTextNode("x")
self.assertRaises(xml.dom.NoModificationAllowedErr,
ref.appendChild, text)
self.assertRaises(xml.dom.NoModificationAllowedErr,
ref.insertBefore, text, None)
self.assertRaises(xml.dom.NoModificationAllowedErr,
ref.removeChild, text)
self.assertRaises(xml.dom.NoModificationAllowedErr,
ref.replaceChild, text, None)
self.assertEqual(ref.cloneNode(True).nodeName, "ent")
other = parseString("<other/>")
self.assertEqual(other.importNode(ref, True).nodeName, "ent")
doc.unlink()
other.unlink()


if __name__ == "__main__":
unittest.main()
5 changes: 4 additions & 1 deletion Lib/xml/dom/expatbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ def getParser(self):
self._intern_setdefault = self._parser.intern.setdefault
self._parser.buffer_text = True
self._parser.ordered_attributes = True
self._parser.specified_attributes = True
self.install(self._parser)
return self._parser

Expand Down Expand Up @@ -352,11 +351,13 @@ def start_element_handler(self, name, attributes):
self.curNode = node

if attributes:
specified = self.getParser().GetSpecifiedAttributeCount()
for i in range(0, len(attributes), 2):
a = minidom.Attr(attributes[i], EMPTY_NAMESPACE,
None, EMPTY_PREFIX)
value = attributes[i+1]
a.value = value
a.specified = i < specified
a.ownerDocument = self.document
_set_attribute_node(node, a)

Expand Down Expand Up @@ -760,6 +761,7 @@ def start_element_handler(self, name, attributes):
node._ensure_attributes()
_attrs = node._attrs
_attrsNS = node._attrsNS
specified = self.getParser().GetSpecifiedAttributeCount()
for i in range(0, len(attributes), 2):
aname = attributes[i]
value = attributes[i+1]
Expand All @@ -775,6 +777,7 @@ def start_element_handler(self, name, attributes):
_attrsNS[(EMPTY_NAMESPACE, aname)] = a
a.ownerDocument = self.document
a.value = value
a.specified = i < specified
a.ownerElement = node

if __debug__:
Expand Down
Loading
Loading