<?xml version="1.0" encoding="UTF-8"?>
<section id="saxlette">
  <title>SAX-like XML processing</title>

  <para>Saxlette is a fast SAX implementation, all written in C. Its API is
  similar to those of <ulink
  url="http://docs.python.org/lib/content-handler-objects.html">Python's
  built-in SAX</ulink>.</para>

  <programlisting>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</programlisting>

  <para>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:</para>

  <programlisting>from <systemitem class="library">Ft.Xml</systemitem> import <classname>Sax</classname>
...
class element_counter:
....
parser = Sax.CreateParser()</programlisting>

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

  <para>The argument to the <function>parse</function> 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 <systemitem
  class="library">Ft.Xml.InputSource</systemitem>.</para>

  <programlisting>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</programlisting>

  <section>
    <title>Validating a document while parsing it using SAX</title>

    <para>To enable validation of your documents while otherwise parsing them
    normally with SAX, set the
    <constant>xml.sax.handler.feature_validation</constant> feature to
    <literal>True</literal> on your parser using a line similar to
    <code>parser.setFeature(xml.sax.handler.feature_validation, True)</code>.
    The parser will then throw an
    <classname>xml.sax._exceptions.SAXParseException</classname> 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.</para>

    <programlisting>from Ft.Xml import InputSource, Sax
factory = InputSource.DefaultFactory

XML = """&lt;!DOCTYPE a [
  &lt;!ELEMENT a (b, b)&gt;
  &lt;!ELEMENT b EMPTY&gt;
]&gt;
&lt;a&gt;&lt;b/&gt;&lt;b/&gt;&lt;/a&gt;"""

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 = """&lt;!DOCTYPE a [
  &lt;!ELEMENT a (b, b)&gt;
  &lt;!ELEMENT b EMPTY&gt;
]&gt;
&lt;a&gt;&lt;b/&gt;&lt;b/&gt;&lt;b/&gt;&lt;/a&gt;"""

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.</programlisting>
  </section>

  <section id="saxlette_domwalker">
    <title>Walking a DOM to fire SAX events</title>

    <para>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
    <classname>Ft.Xml.Domlette.SaxWalker</classname>. The following example
    should show how easy it is to use:</para>

    <programlisting>from Ft.Xml.Domlette import SaxWalker
from Ft.Xml import Parse

XML = "&lt;a&gt;&lt;b/&gt;&lt;b/&gt;&lt;/a&gt;"

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</programlisting>
  </section>

  <section id="saxlette_dombuilder">
    <title>Building a Domlette from SAX events</title>

    <para>Saxlette includes a convenience ContentHandler
    (<classname>Ft.Xml.Sax.DomBuilder</classname>) which listens for SAX
    events and constructs Domlette Documents.</para>
  </section>

  <section id="saxlette_generator">
    <title>Feeding a generator from SAX events</title>

    <para>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, <literal>FEATURE_GENERATOR</literal>
    which you can set on a parser object to enable generator semantics. If
    this feature is set, the <literal>parse()</literal> method returns an
    iterator. This iterator yields results set by the the SAX handlers. The
    handlers specify the partial results by setting the property
    <literal>PROPERTY_YIELD_RESULT</literal> with the value to be yielded. As
    an example, the following code reports the name of all attributes used in
    the document.</para>

    <programlisting>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</programlisting>
  </section>
</section>