# SAX

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
    from Ft.Xml import CreateInputSource

    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)
    #'file:ot.xml' or file('ot.xml') or file('ot.xml').read() would work just as well, of course
    parser.parse(CreateInputSource('ot.xml'))
    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, CreateInputSource

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

## SAX filters

In SAX processing, the parser passes to the application a stream of events that represents the XML content. An important aspect of SAX is the user's ability to create SAX filters, which accept a stream of SAX events and pass on a modified stream. For example, you might use a SAX filter to take look for DOcbook `sect1`, `sect2` etc. elements, and rename them to `section` elements before passing them on for further processing (presumably by a SAX handler that only understands how to deal with the latter form). You can chain SAX filters as well, and the idea behind SAX filters is usually reuse across a broad array of applications, focusing each filter they on a single task that can be cleanly separated from upstream and downstream processing. SAX filters can thus be useful building blocks for XML pipelines.

    from xml import sax
    from xml.sax.saxutils import XMLFilterBase
    from Ft.Xml import CreateInputSource, XML_NAMESPACE as XMLNS
    from Ft.Xml.Sax import SaxPrinter

    XML = """<?xml version="1.0" encoding="utf-8"?>
    <menu>
      <item id="A" xml:lang="en">Orange juice</item>
      <item id="A" xml:lang="es">Jugo de naranja</item>
      <item id="B" xml:lang="en">Toast</item>
      <item id="B" xml:lang="es">Pan tostada
        <note xml:lang="en">Wheat bread only, please</note>
      </item>
    </menu>
    """

    #Define constants for the two states we care about
    ALLOW_CONTENT = 1
    SUPPRESS_CONTENT = 2

    class english_only_filter(XMLFilterBase):
        def __init__(self, downstream):
            XMLFilterBase.__init__(self, downstream)
            return

        def startDocument(self):
            #Set the initial state, and set up the stack of states
            self._state_stack = [ALLOW_CONTENT]
            XMLFilterBase.startDocument(self)
            return

        def startElementNS(self, name, qname, attrs):
            #Check if there is any language attribute
            lang = attrs.get((XMLNS, 'lang'))
            if lang:
                #Set the state as appropriate
                if lang[:2] == 'en':
    	        self._state_stack.append(ALLOW_CONTENT)
                else:
    	        self._state_stack.append(SUPPRESS_CONTENT)
            #Always update the stack with the current state
            #Even if it has not changed

            #Only forward the event if the state warrants it
            if self._state_stack[-1] == ALLOW_CONTENT:
                XMLFilterBase.startElementNS(self, name, qname, attrs)
            return

        def endElementNS(self, name, qname):
            self._state_stack.pop()
            #Only forward the event if the state warrants it
            if self._state_stack[-1] == ALLOW_CONTENT:
                XMLFilterBase.endElementNS(self, name, qname)
            return

        def characters(self, content):
            #Only forward the event if the state warrants it
            if self._state_stack[-1] == ALLOW_CONTENT:
                XMLFilterBase.characters(self, content)
            return

    if __name__ == "__main__":
        parser = sax.make_parser(['Ft.Xml.Sax'])
        #SaxPrinter is a special SAX handler that merely writes
        #SAX events back into an XML document
        filtered_parser = english_only_filter(parser)
        handler = SaxPrinter()
        filtered_parser.setContentHandler(handler)
        filtered_parser.parse(CreateInputSource(XML))

Most SAX handlers operate as state machines, meaning they manage some variables based on the stream of events that come in, and change behavior based on these variables. english_only_filter is set up to be in one of two states: one in which content is passed on to the downstream handler, and one in which content is suppressed. This state is marked in the self._state_stack. The state is initially set to `ALLOW_CONTENT`, and changed to `SUPPRESS_CONTENT` if the filter encounters an xml:lang attribute that represents a language other than English (which can be done by checking the first two characters of the value, according to the rules of standard language codes). It has to be a stack because XML language specifications are scoped, so that in the example XML at the top of the listing the string "Pan tostada" is within the scope of the element with the attribute xml:lang="es", and so it is marked as being in Spanish. The entire note element, however, is marked as being in English by an overriding xml:lang="en" attribute.

The SAX handler is set to Ft.Xml.SaxPrinter, which channels the final SAX evenis onto a 4Suite printer which creates a serialized XML document. It's quite easy to chain filters. If you wanted the parser to send events to a filter of class some_other_filter which then passed on events to english_only_filter the relevant line would look as follows:

        filtered_parser = english_only_filter(some_other_filter(parser))

## Streaming canonicalization

The combination of streaming parsing using Saxlette and streaming serialization using Ft.Xml.Lib.CanonicalXmlPrinter allows for very efficient XML canonicalization (c14n).

    import sys
    from xml import sax
    from Ft.Xml import CreateInputSource
    from Ft.Xml.Sax import SaxPrinter
    from Ft.Xml.Lib.XmlPrinter import CanonicalXmlPrinter

    parser = sax.make_parser(['Ft.Xml.Sax'])
    handler = SaxPrinter(CanonicalXmlPrinter(sys.stdout))
    parser.setContentHandler(handler)
    parser.parse(CreateInputSource('   <a><b b="1" a="2"/></a>   '))