# SAX-like XML processing

Saxlette is a fast SAX implementation, all written in C. Its API is similar to those of Python's built-in SAX.

    from xml import sax

    furi = "file:ot.xml"

    class element_counter(sax.ContentHandler):
        def startDocument(self):
            self.ecount = 0

        def startElementNS(self, name, qname, attribs):
            self.ecount += 1

    parser = sax.make_parser(['Ft.Xml.Sax'])
    handler = element_counter()
    parser.setContentHandler(handler)
    parser.parse(furi)
    print "Elements counted:", handler.ecount

If you don't care about PySax compatibility, you can use the more specialized API, which involves the following lines in place of the equivalents above:

    from Ft.Xml import Sax
    ...
    class element_counter:
    ....
    parser = Sax.CreateParser()

The biggest API differences between Saxlette and PySax are that Saxlette only supports SAX 2. For example, `feature_namespaces` is hard-wired to `True` and `feature_namespace_prefixes` to `False` (which is exactly what SAX2 says is required). Saxlette also combines all adgacent text events, which eliminates one of the pain points of PySax.

The argument to the `parse` method is a URI, a SAX input source or a 4Suite input source. In the example above a URI was used. The following example shows similar code using 4Suite's Ft.Xml.InputSource.

    from Ft.Xml import InputSource, Sax
    factory = InputSource.DefaultFactory
    isrc = factory.fromUri("file:ot.xml")
    doc1 = NonvalidatingReader.parse(isrc)

    class element_counter:
        def startDocument(self):
            self.ecount = 0

        def startElementNS(self, name, qname, attribs):
            self.ecount += 1

    parser = Sax.CreateParser()
    handler = element_counter()
    parser.setContentHandler(handler)
    parser.parse(isrc)
    print "Elements counted:", handler.ecount

## Validating a document while parsing it using SAX

To enable validation of your documents while otherwise parsing them normally with SAX, set the `xml.sax.handler.feature_validation` feature to `True` on your parser using a line similar to `parser.setFeature(xml.sax.handler.feature_validation, True)`. The parser will then throw an `xml.sax._exceptions.SAXParseException` exception if it determines that the document is invalid, and it will stop parsing the document. Handlers for document components that have been parsed will be called, however. The following example illustrates these concepts.

    from Ft.Xml import InputSource, Sax
    factory = InputSource.DefaultFactory

    XML = """<!DOCTYPE a [
      <!ELEMENT a (b, b)>
      <!ELEMENT b EMPTY>
    ]>
    <a><b/><b/></a>"""

    isrc = factory.fromString(XML, 'urn:x-example:valid-a')

    class element_counter:
        def startDocument(self):
            self.scount = 0
            self.ecount = 0

        def startElementNS(self, name, qname, attribs):
            self.scount += 1

        def endElementNS(self, name, qname):
            self.ecount += 1

    parser = Sax.CreateParser()
    handler = element_counter()
    parser.setContentHandler(handler)
    # And now, to enable validation...
    import xml
    parser.setFeature(xml.sax.handler.feature_validation, True)
    parser.parse(isrc)
    print "Saw", handler.scount, "start tags"
    print "Saw", handler.ecount, "end tags"

    # And now we show what happens on an invalid document:
    XML = """<!DOCTYPE a [
      <!ELEMENT a (b, b)>
      <!ELEMENT b EMPTY>
    ]>
    <a><b/><b/><b/></a>"""

    isrc = factory.fromString(XML, 'urn:x-example:invalid-a')
    parser.parse(isrc)
    print "Saw", handler.scount, "start tags"
    print "Saw", handler.ecount, "end tags"
    # The above document is invalid; it has one more `b` element than is
    # allowed by the DTD.  The handlers have still been called for those
    # parts of the document that have been parsed.

## Walking a DOM to fire SAX events

Saxlette has the ability to walk a Domlette tree, firing off events to a handler as if from a source document parse. This ability used to be too well, hidden, though, and I made an API addition to make it more readily available. This is the new `Ft.Xml.Domlette.SaxWalker`. The following example should show how easy it is to use:

    from Ft.Xml.Domlette import SaxWalker
    from Ft.Xml import Parse

    XML = "<a><b/><b/></a>"

    class element_counter:
        def startDocument(self):
            self.ecount = 0

        def startElementNS(self, name, qname, attribs):
            self.ecount += 1

    #First get a Domlette document node
    doc = Parse(XML)
    #Then SAX "parse" it
    parser = SaxWalker(doc)
    handler = element_counter()
    parser.setContentHandler(handler)
    #You can set any properties or features, or do whatever
    #you would to a regular SAX2 parser instance here
    parser.parse() #called without any argument
    print "Elements counted:", handler.ecount

## Building a Domlette from SAX events

Saxlette includes a convenience ContentHandler (`Ft.Xml.Sax.DomBuilder`) which listens for SAX events and constructs Domlette Documents.

## Feeding a generator from SAX events

Python's generators are special functions that can produce a series of partial results within the course of running. The calling program can start up a generator, which is suspended when a partial result is yielded, and resumed explicitly by the program when the next result is required. This capability is mirrored in the Expat parser that is the basis of Saxlette. Saxlette has a feature, `FEATURE_GENERATOR` which you can set on a parser object to enable generator semantics. If this feature is set, the `parse()` method returns an iterator. This iterator yields results set by the the SAX handlers. The handlers specify the partial results by setting the property `PROPERTY_YIELD_RESULT` with the value to be yielded. As an example, the following code reports the name of all attributes used in the document.

    class report_attributes:
        def __init__(self, parser):
            self.parser = parser
            return

        def startElementNS(self, name, qname, attribs):
            self.parser.setProperty(Sax.PROPERTY_YIELD_RESULT, attribs)
            return

    from Ft.Xml import Sax
    parser = Sax.CreateParser()
    parser.setFeature(Sax.FEATURE_GENERATOR, True)
    handler = report_attributes(parser)
    parser.setContentHandler(handler)
    attribs_iterator = parser.parse('file:test.xhtml')
    for attribs in attribs_iterator:
         for name in attribs.keys(): print name