SyntaxStudy
Sign Up
XML SAX Parsing with Python xml.sax
XML Beginner 2 min read

SAX Parsing with Python xml.sax

SAX (Simple API for XML) is an event-driven, streaming parser model. Instead of building an in-memory tree, a SAX parser reads the XML document sequentially and fires callback events as it encounters each component: start of document, end of document, start element, end element, characters, processing instruction, and so on. The application registers a handler object whose methods are called by the parser as events occur. This approach uses a constant amount of memory regardless of document size, making it ideal for very large XML files. Python's xml.sax module implements the SAX2 API. To use it, you subclass xml.sax.ContentHandler and override the methods you need: startElement() / endElement() for element start and end tags, characters() for text content, startDocument() / endDocument() for document boundaries. The parser is created with xml.sax.make_parser() and the handler is registered with parser.setContentHandler(). Parsing begins when you call parser.parse() with a file path or file-like object. The main trade-off of SAX versus DOM is that SAX does not support random access or backward navigation. Your handler must maintain its own state stack to know where in the document it currently is. If you need to collect text that arrives in multiple characters() callbacks (which is allowed by the SAX specification), you must buffer it. Despite these complexities, SAX parsers are significantly faster and more memory-efficient than DOM parsers for large documents, and they are well-suited for extraction pipelines that process documents once in a forward-only manner.
Example
# Python: SAX parsing with xml.sax

import xml.sax
import xml.sax.handler

class BookHandler(xml.sax.ContentHandler):
    """Extract book records from the library XML."""

    def __init__(self):
        super().__init__()
        self.books        = []
        self._current     = {}
        self._current_tag = None
        self._buffer      = []

    def startElement(self, name, attrs):
        self._current_tag = name
        self._buffer      = []
        if name == 'book':
            # attrs is an AttributesImpl object
            self._current = {
                'id':       attrs.get('id', ''),
                'category': attrs.get('category', ''),
            }

    def characters(self, content):
        # May be called multiple times for one text node
        self._buffer.append(content)

    def endElement(self, name):
        text = ''.join(self._buffer).strip()
        if name in ('title', 'author', 'year', 'price'):
            self._current[name] = text
        elif name == 'book':
            self.books.append(dict(self._current))
            self._current = {}
        self._current_tag = None
        self._buffer      = []

    def endDocument(self):
        print(f'Parsing complete. {len(self.books)} books found.')


import io

xml_bytes = b"""<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book id="b001" category="fiction">
        <title>The Great Gatsby</title>
        <author>F. Scott Fitzgerald</author>
        <year>1925</year><price>12.99</price>
    </book>
    <book id="b002" category="non-fiction">
        <title>Clean Code</title>
        <author>Robert C. Martin</author>
        <year>2008</year><price>35.00</price>
    </book>
</library>"""

handler = BookHandler()
xml.sax.parseString(xml_bytes, handler)
for b in handler.books:
    print(b)