# Streaming XML output

MarkupWriter is a streaming API for generating XML. The `Ft.Xml.MarkupWriter` class is specialized for creating XML documents from scratch. Documents written with MarkupWriter are written to the output (standard output or another file-like object) as you build them, so if you need to process the document in memory, you may need another tool such as a DOM-like tool (e.g. Domlette, Amara, etc).

4Suite partitions XML serializers into two categories: writers and printers.

- A writer is a module that exposes a broad public API for building output incrementally.

- A printer is a module that simply takes a DOM and creates output from it as a whole, within one API invocation.

MarkupWriter is the primary example of this writer category of XML serializers.

The following example uses this class for generating a simple XML Software Autoupdate (XSA) file. XSA is a XML data format for listing and describing software packages.

    from Ft.Xml import MarkupWriter

    # Set the output doc type details (required by XSA)
    SYSID = u"http://www.garshol.priv.no/download/xsa/xsa.dtd"
    PUBID = u"-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML"
    writer = MarkupWriter(indent=u"yes", doctypeSystem=SYSID,
                          doctypePublic=PUBID)
    writer.startDocument()
    writer.startElement(u'xsa')
    writer.startElement(u'vendor')

    # Element with simple text (#PCDATA) content
    writer.simpleElement(u'name', content=u'Centigrade systems')
    writer.simpleElement(u'email', content=u"info@centigrade.bogus")
    writer.endElement(u'vendor')

    # Element with an attribute
    writer.startElement(u'product', attributes={u'id': u"100\u00B0"})
    writer.simpleElement(u'name', content=u"100\u00B0 Server")
    writer.simpleElement(u'version', content=u"1.0")
    writer.simpleElement(u'last-release')
    writer.text(u"20030401")

    # Empty element
    writer.simpleElement(u'changes')
    writer.endElement(u'product')
    writer.endElement(u'xsa')
    writer.endDocument()

This is the output we get from the code above:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsa PUBLIC "-//LM Garshol//DTD XML Software Autoupdate 1.0//EN//XML" "http://www.garshol.priv.no/download/xsa/xsa.dtd">
    <xsa>
      <vendor>
        <name>Centigrade systems</name>
        <email>info@centigrade.bogus</email>
      </vendor>
      <product id="100°">
        <name>100° Server</name>
        <version>1.0</version>
        <last-release>20030401</last-release>
        <changes/>
      </product>
    </xsa>

The above example illustrates some of the basics of using the MarkupWriter class. The following sections describe both the essential and the advanced features of this class. In many cases, there often exists more than one way to output a given document section.

## Starting with MarkupWriter

After importing the MarkupWriter class, you have to create a MarkupWriter object instance and then start the new Document. (See below for output options of MarkupWriter.) Remember that you are working with a streaming API. You must decide what features you want your output to have before you start to write that output.

    >>> from Ft.Xml import MarkupWriter
    >>> writer = MarkupWriter()
    >>> writer.startDocument()

You are now ready to add data to the new document.

Make sure that all of your data (element names, attributes, content, etc) are Python unicode objects.

## How to insert elements

There are two ways to add new elements as children of other document or element nodes.

1. When you want to add a new element that will itself have child elements, you can use the startElement/endElement method combination to signal the beginning and the ending of an element, respectively.

       writer.startElement(u'xsa')
       # other document content can be output here
       writer.endElement(u'xsa')

2. Alternatively, you can use the simpleElement method, which is a shortcut for the startElement/endElement combination and produces an element with no content or with text content (if you specify the content parameter).

       writer.simpleElement(u'xsa')

## How to insert attributes

There are two ways to add attributes to elements:

1. First, you can use the attributes parameter of the startElement method. This parameter is a dictionary which maps each attribute name to the value of that attribute. If an attribute's name is in a namespace, then you must specify the name as a Python tuple, with the attribute's QName as the first member of the tuple, and the namespace URI as the second member of the tuple. For an example of this advanced syntax, see .

       writer.startElement(u'product', attributes={u'id': u"100\u00B0"}

2. Alternatively, you can use a distinct attribute method with two parameters: the attribute's name and the attribute's value. As with the dictionary approach above, if the attribute's name is in a namespace, then the whole name should be a Python tuple.

       writer.startElement(u'product')
       writer.attribute(u'id', u"100\u00B0")

## How to insert text nodes

Similarly, there are two ways to add text nodes to elements.

1. First, the simpleElement method takes a content parameter, which can be used to create a single text node child of the node with the specified name.

       writer.simpleElement(u'name', content=u'Centigrade systems')

2. Alternatively, instances of the MarkupWriter class, such as `writer`, have a text method that inserts a single text node as the next child of the element which was last started with the startElement method and which has not yet been closed with the endElement method.

       writer.startElement(u'product')
       writer.text(u'Centigrade systems')
       writer.endElement(u'product')

## How to insert a complete chunk

MarkupWriter also allows you to insert well-formed XML entities as complete chunks in the output. This is a very convenient way to emit boilerplate XML without breaking it down into all the separate element/attribute/content bits. As such the lines:

    writer.simpleElement(u'name', content=u"100\u00B0 Server")
    writer.simpleElement(u'version', content=u"1.0")
    writer.simpleElement(u'last-release', content=u"20030401")

Could instead be written:

    writer.xmlFragment("""
    <name>100° Server</name>
    <version>1.0</version>
    <last-release>20030401</last-release>""")

The parameter of xmlFragment is a string, not a unicode object.

## How to insert processing instructions and comments

The API provides the comment and processingInstruction methods for inserting processing instructions and comments. The comment method takes a unicode string, which is the intended value of the comment. The processingInstruction method takes two unicode strings. The first is the name of the processing instruction, and the second is the value of the processing instruction. For example, the following code:

    writer.comment(u"This is a processing instruction")
    writer.processingInstruction(u'xml-stylesheet', u'type="text/xsl" href="akara.xsl"')

produces the following output:

    <!--This is a processing instruction-->
    <?xml-stylesheet type="text/xsl" href="akara.xsl"?>

## Using namespaces

When you create a new element or an attribute, you can use namespaces. See the next program:

    from Ft.Xml import MarkupWriter

    writer = MarkupWriter(indent=u'yes')
    writer.startDocument()

    RDFNS = u"http://www.w3.org/1999/02/22-rdf-syntax-ns#"

    writer.startElement(u"rdf:RDF", RDFNS)
    writer.startElement(u"rdf:Description", RDFNS,
        attributes={(u'rdf:about', RDFNS): u'http://media.example.com/audio/guide.ra'})
    writer.endElement(u'rdf:Description', RDFNS)
    writer.endElement(u'rdf:RDF', RDFNS)

And this is the output:

    <?xml version="1.0" encoding="UTF-8"?>
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
        <rdf:Description rdf:about="http://media.example.com/audio/guide.ra"/>
    </rdf:RDF>

## Setting up the output

In the above example, you can see how parameters that control the output are passed into the MarkupWriter initializer, including document type info and whether to indent (pretty print).

You can pass any of the usual controls for XSLT output into the initializer this way.

stream

  By default MarkupWriter sends its output to `sys.stdout`, but you can substitute any file-like object by passing in an initializer parameter. This stream parameter should be the first argument to the MarkupWriter constructor. For example:

      output_file = file('output.xml', 'w')
      writer = MarkupWriter(output_file, indent=u"yes")

indent

  The indent named parameter controls whether or not the output will have whitespace inserted to indent tags in the output. The default is "no".

doctypeSystem, doctypePublic

  These two named parameters control the system and public identifiers that will be included in the output.

omitXmlDeclaration=u"yes"

  This named parameter can be used to suppress output of the XML declaration. The default is "no".

encoding

  This named parameter controls the character encoding to use. (The default is UTF-8.) The writer will automatically use character entities where necessary.

standalone

  Set this named parameter to "yes" to set standalone in the XML declaration.

mediaType

  This parameter sets the media type of the output. You will probably never need this.

cdataSectionElements

  This named parameter is a list of element names whose output will be wrapped in a CDATA section. This can provide for friendlier output in some cases.

The XSLT spec also defines a method parameter to choose between XML, HTML or plain text output rules, but for MarkupWriter at the moment you should stick to XML. The result of changing the method is undefined. We'll probably relax this restriction in later releases.

## More examples

### Writing XHTML with MarkupWriter

Uche Ogbuji provides this example, which writes a simple XHTML file, in his blog:

    from Ft.Xml.MarkupWriter import MarkupWriter
    from xml.dom import XHTML_NAMESPACE, XML_NAMESPACE

    XHTML_NS = unicode(XHTML_NAMESPACE)
    XML_NS = unicode(XML_NAMESPACE)
    XHTML11_SYSID = u"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
    XHTML11_PUBID = u"-//W3C//DTD XHTML 1.1//EN"

    writer = MarkupWriter(indent=u"yes", doctypeSystem=XHTML11_SYSID,
                          doctypePublic=XHTML11_PUBID)
    writer.startDocument()
    writer.startElement(u'html', XHTML_NS, attributes={(u'xml:lang', XML_NS): u'en'})
    writer.startElement(u'head', XHTML_NS)
    writer.simpleElement(u'title', XHTML_NS, content=u'Virtual Library')
    writer.endElement(u'head', XHTML_NS)
    writer.startElement(u'body', XHTML_NS)
    writer.startElement(u'p', XHTML_NS)
    writer.text(u'Moved to ')
    writer.simpleElement(u'a', XHTML_NS,
                         attributes={u'href': u'http://vlib.org/'},
                         content=u'vlib.org')
    writer.text(u'.')
    writer.endElement(u'p', XHTML_NS)
    writer.endElement(u'body', XHTML_NS)
    writer.endElement(u'html', XHTML_NS)
    writer.endDocument()

This example results in the following XHTML document:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
      <head>
        <title>Virtual Library</title>
      </head>
      <body>
        <p>Moved to <a href="http://vlib.org/">vlib.org</a>.</p>
      </body>
    </html>

### Writing information of directory listing as a XML document

This recursive example builds an XML document with the information of a directory listing. The example has two functions. The first initializes the writer. The second walks through the filesystem and outputs information about the filesystem as XML. The complete dirlist.py program can be found on Uche Ogbuji's blog.

    def genXML(dir,out):
        print "Processing %s" % dir
        writer = MarkupWriter(out, indent=u"yes")
        writer.startDocument()
        recurse_dir(dir,writer)

    def recurse_dir(path,writer,d):
        d=d+1
        for cdir, subdirs, files in os.walk(path):
            writer.startElement(u'directory', attributes={u'name': unicode(cdir)})
            for f in files:
                writer.simpleElement(u'file', attributes={u'name': unicode(f)})
            for subdir in subdirs: recurse_dir(os.path.join(cdir, subdir), writer,d)
            writer.endElement(u'directory')
            break

### Building a bot

As a more complex example, the Emeka IRC bot uses MarkupWriter to build an RDF document. It writes namespaces. See this chunk of the code:

    DCE_NS = u'http://purl.org/dc/elements/1.1/'
    for nada,category in item['categories']:
        if len(category.split(' ')) > 0:
            for category in category.split(' '):
                writer.startElement(u"dc:subject", DCE_NS)
                writer.text(category)
                writer.endElement(u"dc:subject")
        else:
            writer.startElement(u"dc:subject", DCE_NS)
            writer.text(category)
            writer.endElement(u"dc:subject", DCE_NS)