xml.dom.minidom is a compact implementation of the Document Object Model in Python’s standard library. Instead of focusing only on elements and children, DOM represents documents, elements, attributes, text nodes, comments, and other node types through an API similar to those found in browsers and other programming languages.
The module is useful when an integration requires DOM concepts, when individual node manipulation matters, or when porting code from another platform. For most ordinary XML work, the official documentation recommends Python ElementTree, which is usually simpler and more memory-efficient.
Parsing a file
from xml.dom import minidom
with minidom.parse("catalog.xml") as document:
root = document.documentElement
print(root.tagName)
parse() accepts a filename or file-like object and returns a Document. The context manager calls unlink() when leaving the block, allowing internal references to be released earlier.
Parsing completes the whole DOM tree before returning. Do not use minidom for unlimited or very large documents without strict input limits.
Parsing a string
xml = "<product id='7'><name>Keyboard</name></product>"
with minidom.parseString(xml) as document:
product = document.documentElement
print(product.getAttribute("id"))
Before calling parseString(), enforce a byte or character limit. A relatively small XML source can still produce a large object graph depending on its structure.
Node types
Each node exposes nodeType. Common constants include DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE, COMMENT_NODE, and PROCESSING_INSTRUCTION_NODE.
from xml.dom import Node
for node in product.childNodes:
if node.nodeType == Node.ELEMENT_NODE:
print("element", node.tagName)
elif node.nodeType == Node.TEXT_NODE:
print("text", repr(node.data))
Whitespace and line breaks between tags can also become text nodes. Never assume that the first child is an element.
Finding elements
products = document.getElementsByTagName("product")
for product in products:
print(product.getAttribute("id"))
getElementsByTagName() searches recursively through descendants. To inspect direct children only, filter childNodes. Repeated recursive searches can become expensive; build application indexes when a document is queried frequently.
Extracting text correctly
An element may contain multiple text nodes mixed with elements. Define exactly whether you want direct text or recursive content.
from xml.dom import Node
def direct_text(element: Node) -> str:
parts = []
for child in element.childNodes:
if child.nodeType == Node.TEXT_NODE:
parts.append(child.data)
return "".join(parts)
For recursive text, walk descendants. Decide how comments, CDATA, and whitespace should be handled rather than applying one implicit rule everywhere.
Attributes
product.setAttribute("active", "yes")
identifier = product.getAttribute("id")
if product.hasAttribute("temporary"):
product.removeAttribute("temporary")
getAttribute() returns an empty string when the attribute is missing. Use hasAttribute() when the distinction between absence and an empty value matters.
Creating a document
from xml.dom.minidom import getDOMImplementation
implementation = getDOMImplementation()
document = implementation.createDocument(None, "catalog", None)
root = document.documentElement
product = document.createElement("product")
product.setAttribute("id", "7")
root.appendChild(product)
name = document.createElement("name")
name.appendChild(document.createTextNode("Keyboard"))
product.appendChild(name)
Create nodes through their owning Document. Do not instantiate internal minidom classes directly.
Adding and removing nodes
price = document.createElement("price")
price.appendChild(document.createTextNode("199.90"))
product.appendChild(price)
product.removeChild(price)
price.unlink()
removeChild() detaches a node, but the object may still hold references. unlink() renders the node and descendants unusable and encourages earlier memory release.
Cloning nodes
copy = product.cloneNode(deep=True)
copy.setAttribute("id", "8")
root.appendChild(copy)
With deep=False, only the node itself is copied. Review identifiers and references before inserting clones so the document does not contain duplicate IDs.
Namespaces
URI = "https://example.com/catalog"
document = implementation.createDocument(URI, "cat:catalog", None)
product = document.createElementNS(URI, "cat:product")
document.documentElement.appendChild(product)
Use namespace-aware methods such as createElementNS(), getElementsByTagNameNS(), and setAttributeNS(). A prefix is only a serialization choice; the namespace URI carries identity.
Serialization with toxml
data = document.toxml(
encoding="utf-8",
standalone=True,
)
with open("catalog.xml", "wb") as file:
file.write(data)
With an explicit encoding, toxml() returns bytes. Without one, it returns a Unicode string. Use standards-compliant names in declarations, such as UTF-8.
Pretty printing
pretty = document.toprettyxml(
indent=" ",
newl="\n",
encoding="utf-8",
)
toprettyxml() improves human readability but can add whitespace that changes meaningful mixed text. Do not use pretty printing for signed documents or byte-for-byte comparison.
writexml
with open("catalog.xml", "w", encoding="utf-8") as file:
document.writexml(
file,
addindent=" ",
newl="\n",
encoding="UTF-8",
)
The writer used by writexml() receives text, not bytes. Match the file mode and writer interface correctly.
Atomic output
Write to a temporary file in the same directory, flush as required, and replace the final path only after successful serialization. See Python tempfile.
Memory and unlink
DOM holds parent-child relationships and the whole tree in memory. Large documents can consume substantially more memory than streaming APIs. Call document.unlink() when finished or use with minidom.parse(...) as document.
After unlinking, do not reuse nodes. The operation is intended for cleanup, not partial reset followed by continued processing.
External XML and security
The documentation points users to Python’s XML vulnerability guidance. Untrusted input can exploit entity expansion, deep nesting, and resource consumption. Limit source size before parsing, keep the runtime updated, and consider hardened XML libraries.
Do not let a user-controlled URL or path flow directly into parse(). Separate resource acquisition, destination validation, and XML parsing to reduce SSRF and local-file risks.
Using a configured SAX parser
parse() can receive an already configured SAX2 parser. This lets you install an entity resolver or features before the DOM builder takes over.
import xml.sax
from xml.dom import minidom
parser = xml.sax.make_parser()
# Configure features and a resolver before parsing.
document = minidom.parse("data.xml", parser=parser)
Minidom changes the document handler and enables namespace support. Test the exact parser configuration against the Python version used in production.
Comments and processing instructions
DOM can preserve comments and processing instructions as nodes. Treat their contents as untrusted data. Transformation code should explicitly decide whether to preserve or remove them.
Minidom versus ElementTree
ElementTree offers a more Pythonic API for most XML files and includes incremental processing. Minidom makes sense when the complete DOM node model, explicit node types, or compatibility with W3C-style DOM code is required.
Validation
Parsing does not validate the business structure. After building the DOM, check the root element, namespaces, required attributes, cardinality, types, ranges, and relationships. Minidom does not automatically validate XSD.
Errors
Malformed XML commonly raises an Expat or SAX parsing exception. Catch errors around the parsing call and preserve the cause without exposing the full document.
from xml.parsers.expat import ExpatError
try:
document = minidom.parseString(received_xml)
except ExpatError as error:
raise ValueError(
f"invalid XML at line {error.lineno}, column {error.offset}"
) from error
Recommended tests
Test whitespace text nodes, comments, CDATA, missing and empty attributes, namespaces, deep cloning, removal, pretty printing, encoding, large documents, malformed XML, excessive depth, cleanup with unlink, and domain validation.
Common mistakes
Frequent mistakes include using the first childNode without checking its type, confusing missing attributes with empty strings, retaining large DOM trees, forgetting cleanup, pretty-printing mixed content, creating nodes without their Document, ignoring namespaces, parsing unlimited XML, and treating parsing as validation.
Conclusion
xml.dom.minidom provides a compact and familiar DOM implementation. Use it when a complete node model is important. For straightforward XML or large files, ElementTree or SAX is often a better choice.
Read the official minidom documentation and the DOM Level 1 specification. For external XML, combine strict limits, an updated parser, domain validation, and explicit cleanup.







