Python xmlreader: Control SAX Parsers

Published on: August 23, 2026
Reading time: 4 minutes
Close-up of a saxophone and sheet music stand, perfect for jazz lovers and musicians.

The xml.sax.xmlreader module defines the interfaces used by SAX parsers in Python. It describes how a parser receives input sources, sends events to handlers, configures features and properties, reports line and column positions, and supports incremental parsing.

Most applications create a parser with xml.sax.make_parser(), but understanding XMLReader, InputSource, Locator, and attribute objects provides better control over security, encoding, streams, and parser extensions.

The role of XMLReader

XMLReader is the base interface implemented by SAX parser drivers. A driver exposes a create_parser() function, and make_parser() uses it to create a reader. The reader processes XML and invokes registered handlers.

from xml.sax import make_parser
from xml.sax.handler import ContentHandler

class MyHandler(ContentHandler):
    def startElement(self, name, attrs):
        print("start:", name)

parser = make_parser()
parser.setContentHandler(MyHandler())
parser.parse("data.xml")

When parse() returns, the document has been fully processed. Use the incremental interface when chunks arrive over time or blocking behavior is not acceptable.

Configurable handlers

A reader can receive a ContentHandler, DTDHandler, EntityResolver, and ErrorHandler. Without a content handler, content events are discarded. Without an error handler, errors normally become exceptions and warnings may be printed.

The guide to Python xml.sax explains the main callbacks. Python saxutils provides generators and filters for the same event flow.

Control origins with InputSource

InputSource stores a public identifier, system identifier, encoding, byte stream, and character stream. It lets an application provide a source that is already open instead of allowing the reader to open a path or URL automatically.

from xml.sax import make_parser
from xml.sax.xmlreader import InputSource

source = InputSource()
source.setSystemId("local-import")
source.setEncoding("utf-8")
source.setByteStream(open("data.xml", "rb"))

parser = make_parser()
parser.setContentHandler(MyHandler())
parser.parse(source)

When a character stream exists, the parser ignores the byte stream and the encoding value on the input source. A byte stream takes precedence over opening the system identifier.

Byte streams and character streams

A byte stream allows the parser to inspect the XML encoding declaration unless an encoding is explicitly supplied. A character stream is already decoded, so the application becomes responsible for correct decoding.

Do not decode arbitrary XML bytes with errors="ignore" before parsing. Removed characters may change document meaning. The guide to Python codecs covers encodings and error policies.

Prevent automatic URL access

A string passed to parse() may represent a filename, path-like object, or system identifier. With external data, open the source yourself, enforce size and protocol restrictions, and provide a stream. This prevents a user-controlled reference from becoming an unrestricted network or file access.

Combine this with an EntityResolver that rejects external entities. XML input should not freely choose local files, remote hosts, or protocols.

Parser features

getFeature() and setFeature() read or change boolean SAX options. Common features include namespace processing, namespace prefixes, validation, and external entities. A parser may not recognize or support every feature.

from xml.sax import make_parser
from xml.sax.handler import feature_namespaces, feature_external_ges

parser = make_parser()
parser.setFeature(feature_namespaces, True)

try:
    parser.setFeature(feature_external_ges, False)
except Exception as exc:
    raise RuntimeError("Required XML protection is unavailable") from exc

Do not silently ignore a failed security configuration. If an essential protection cannot be guaranteed, reject the operation.

Parser properties

Properties carry more complex objects, including lexical handlers. getProperty() and setProperty() may raise SAXNotRecognizedException or SAXNotSupportedException. Configure properties before parsing begins.

Incremental parsing

IncrementalParser accepts chunks through feed(). After the final chunk, close() checks well-formedness conditions that can only be verified at the end and sends remaining events. Call reset() after close() before reusing the parser.

from xml.sax import make_parser

parser = make_parser()
parser.setContentHandler(MyHandler())

with open("large.xml", "rb") as file:
    while chunk := file.read(64 * 1024):
        parser.feed(chunk)

parser.close()

Do not mix parse() and feed() during the same operation. Do not call reset() while a document is being processed.

Limits still matter

Reading in chunks does not limit total size. Count input bytes, events, depth, accumulated text, and runtime. Stop before policy limits are exceeded.

MAX_BYTES = 20 * 1024 * 1024
received = 0

while chunk := origin.read(64 * 1024):
    received += len(chunk)
    if received > MAX_BYTES:
        raise ValueError("XML exceeds the size limit")
    parser.feed(chunk)

parser.close()

Locator for line and column

A Locator associates an event with the current document position. The parser provides it through setDocumentLocator(). Values are valid only during handler callbacks.

from xml.sax.handler import ContentHandler

class PositionedHandler(ContentHandler):
    def setDocumentLocator(self, locator):
        self.locator = locator

    def startElement(self, name, attrs):
        line = self.locator.getLineNumber()
        column = self.locator.getColumnNumber()
        print(name, line, column)

Copy location values during the event. Keeping the locator and consulting it later will report a different position.

AttributesImpl

AttributesImpl represents attributes passed to startElement(). It supports part of the mapping protocol plus methods such as getLength(), getNames(), getType(), and getValue().

A parser may reuse the object. Copy attributes when they must survive the callback.

def startElement(self, name, attrs):
    copied = dict(attrs.items())
    self.records.append((name, copied))

Namespace-aware attributes

With namespaces enabled, startElementNS() receives element names as (namespaceURI, localname) tuples. AttributesNSImpl works with the same form and can convert between qualified names and namespace pairs.

Do not rely only on textual prefixes. Prefixes may change while the namespace URI remains the same.

Error handling

An error handler receives warnings, recoverable errors, and fatal errors. Data pipelines should usually stop on structural errors and return a controlled diagnostic. Avoid logging the complete XML document or credentials.

A blocking entity resolver

from io import StringIO
from xml.sax.handler import EntityResolver
from xml.sax.xmlreader import InputSource

class BlockExternal(EntityResolver):
    def resolveEntity(self, publicId, systemId):
        source = InputSource()
        source.setCharacterStream(StringIO(""))
        return source

parser.setEntityResolver(BlockExternal())

Depending on the application, raising an exception may be safer than returning an empty source. The important rule is to avoid automatic external access.

Test strings, Path values, byte streams, character streams, declared and overridden encodings, namespaces, copied attributes, incremental input, truncated documents, external entities, unsupported features, byte limits, and locator positions.

Common mistakes

Frequent mistakes include trusting external system IDs, decoding character streams incorrectly, forgetting close(), reusing a parser without reset(), storing attribute objects without copying, enabling external entities, and assuming all parsers support the same features.

Conclusion

xml.sax.xmlreader defines the infrastructure behind SAX parsers: readers, input sources, handlers, features, properties, locators, and attribute interfaces. Understanding it gives applications stronger control over input, encoding, streaming, and security.

Open external sources with limits, keep external entities disabled, and configure the reader before parsing. Consult the official xmlreader documentation and Python’s XML security guidance.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Developer configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Python code showing warnings controlled with catch_warnings
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: Capture Python Warnings in Tests

    Learn Python catch_warnings to capture, test, and control warnings with focused filters and safe temporary scopes.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Python code representing persistent pickle references
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: Serialize External References

    Learn Python pickle persistent_id for stable external references, validation, security, performance, and long-term compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Binary code representing Python buffers and memory views
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: Count Values Without Buffer Copies

    Learn Python memoryview.count to count bytes and values in buffers without copies, with formats, limits, and practical safety.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code and type annotations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: Avoid Circular Imports in Annotations

    Learn Python annotationlib to inspect deferred annotations, avoid circular imports, and build safer runtime tooling.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026