# DOM-like XML processing

Domlette is 4Suite's lightweight DOM implementation. It is optimized for XPath operations, speed, and relatively low memory overhead. The Domlette API is accessible through Ft.Xml.Domlette. This section describes how to parse, manipulate, and then serialize XML documents using this API.

Below, we briefly summarize the various elements of the API that form the basic life span of Domlette objects.

Parsing XML documents

  The Ft.Xml module contains the function Parse that gets the job done quickly. See for details. For a bit more more advanced parsing, you will need a combination of the reader instances in the Ft.Xml.Domlette module and Ft.Xml.CreateInputSource for constructing InputSource instances. In rare cases you might need lower-level APIs in in the Ft.Xml.InputSource module. Read if Ft.Xml.Parse isn't enough.

Modifying and interacting with XML documents

  The Domlette API for interacting with XML documents—accessible as methods of the various Domlette objects—is similar to the DOM Level 2 specification. See for more information.

Serializing XML documents

  The Ft.Xml.Domlette module provides two functions, Print and PrettyPrint, for writing your XML documents. The Print function writes the XML document precisely as given in the model. On the other hand, the PrettyPrint function adds whitespace nodes to your document to try to indent the resulting output nicely. See for details.

## Parsing XML documents

We begin our discussion of the Domlette API by describing how to obtain a model of your XML documents to manipulate further. Because XML documents offer such rich functionality and exist in such varied environments, there can be a surprising amount of work that you must do to simply load your XML documents. We begin by providing a short-cut for easy access. We will then dive into the full suite of document loading utilities.

### Quick access to the Domlette reader API

For basic document manipulations or to get started quickly, the Ft.Xml module offers a quick way to parse XML documents and directly obtain access to the Domlette interface to those documents. Within this module the function of interest is Parse.

This function will get you started quickly because it specifically chooses some default values for some of the more advanced parsing features. If you are passing in a string or stream, and the material in applies to your parsing situation, then you will want to use the full-featured API. In brief, if your XML document references external resources, you should not use this convenience function. See instead.

This function returns a Domlette Document representing the root of the document from the argument.

Parse source

  The Parse function takes a single argument, which is a byte string (not unicode object), file-like object (stream), file path or URI.

    XML = """
    <ham>
    <eggs n='1'/>
    This is the string content with <em>emphasized text</em> text
    </ham>"""

    from Ft.Xml import Parse

    doc = Parse(XML)
    # If the above XML document were located in the file
    # "target.xml", we could have used `Parse("target.xml")`.
    print doc.xpath('string(ham//em[1])')

### The full Domlette reader API

You create Domlette instances by parsing XML documents with the reader system. For general use, the Ft.Xml.Domlette package contains instances of the different reader classes that can be used directly after you import them. These instances include `NonvalidatingReader` and `ValidatingReader`, which provide non-validating parsing and validating parsing services, respectively. The validation in this case refers to DTD validation. For RELAX NG validation, see . All the reader classes (and, hence, their bundled instances) are described in later sections. After you have obtained one of these reader instances, you feed your XML document entity's byte stream to the reader. We summarize the available reader methods below.

parseUri uri

  The parseUri method takes a single argument; this uri argument is the absolute URI of the document entity to parse. The URI will be dereferenced by the default resolver.

parseString st uri

  The parseString method takes two arguments; st is the XML document entity in the form of an encoded Python string (not a Unicode string). See the next section for details on the uri argument.

parseStream stream uri

  The parseStream method takes two arguments; stream is a Python file-like object that can supply the document entity's bytes via read() calls. See the next section for details on the uri argument.

parse inputSource

  The parse method takes a single argument; inputSource is an Ft.Xml.InputSource.InputSource object, described in .

The next two sections cover some of the issues that you should understand before using these functions. Then we start seeing some examples in .

### The importance of base URIs

In the first 3 methods listed in the previous section, the uri argument is the URI of the document entity that you are feeding to the parser. It is a very important—but often overlooked—concept in document processing.

The URI gives the document entity a unique identifier that can used to refer to the document as a whole. Also, each Domlette node derived from a particular entity inherits that entity's URI as the node's `baseURI` property, unless an alternative base URI was indicated, such as with xml:base, or if part of the document was loaded as an external entity or XInclude.

The document's URI is also used as the "base URI" for resolving any relative URI references that may appear within the document itself. Relative URI references may occur in a document in places like:

- <!DOCTYPE> or <!ENTITY>, immediately following the keyword SYSTEM

- <xsl:import> and <xsl:include>, in the value of the href attribute

- <xi:include>, in the value of the href attribute

- <exsl:document>, in the value of the href attribute

- the arguments to XSLT's `document()` function

It is a common misconception that relative URI references in a document's content are considered to be relative to the processor's current working directory. They are actually resolved relative to the URI of the document that contains the relative URI reference (more specifically, relative to the URI of the entity in which the reference occurs, keeping in mind that a document may be comprised of multiple entities, i.e., separate files).

In all cases, the document URI that you supply in the reader API must be "absolute", which means that it has a scheme, e.g. "http://spam/eggs.xml", not just "`/spam/eggs.xml`" or "`eggs.xml`".

If you know there are not going to be any relative URI references to resolve during initial parsing or during processing of the Domlette by other tools, then you can safely omit the argument, or, preferably, supply a dummy URI like "urn:dummy" or "http://spam/eggs.xml". If you choose to omit URI arguments from APIs that need them, you may get a Python warning, and a random URI—which is probably not what you want—will be assigned.

If you've understood all this and yet you want to just go ahead and not specify a base URI, you may have to turn off the likely warnings. You can do so with code such as in the following example.

    import  Ft.Xml.Domlette
    import warnings
    def disable_warnings(*args): pass

    warnings.filterwarnings("ignore", category=Warning)
    warnings.showwarning = disable_warnings

    XML = "<spam/>"
    doc  = Ft.Xml.Domlette.NonvalidatingReader.parseString(XML)
    Ft.Xml.Domlette.Print(doc)

You can also in such a case use the convenience function Ft.Xml.Parse (see above).

### Parsing XML that's already a Unicode string

Because 4Suite is trying to provide as thin a wrapper as possible to the underlying parser, and due to complexities in the APIs of these parsers, there is no API in 4Suite for parsing Python's Unicode strings.

If your XML is in the form of a Unicode string, you must encode the string as bytes so that the underlying parser can read it. Once you have an encoded string, you can pass it to the reader's parseString(), or wrap it in an InputSource using Ft.Xml.CreateInputSource, or the fromString() method of an InputSourceFactory. If the string is not UTF-16 or UTF-8 encoded, then you must tell the reader what encoding it actually uses. You can do this either by writing or replacing the XML declaration in the string itself, or (much easier) setting the optional encoding keyword argument in the reader's parseString() method or the InputSourceFactory's fromString() method. For an example, see the Akara article on external encoding declarations.

### `NonvalidatingReader`

Use `NonvalidatingReader` for basic parsing. `NonvalidatingReader` performs its parsing without validating against a DTD.

The following example will parse an XML source taken from the supplied URI, which is treated as a URL by the default resolver.

    from Ft.Xml.Domlette import NonvalidatingReader
    doc = NonvalidatingReader.parseUri(
      "http://www.w3.org/2000/08/w3c-synd/home.rss")

The following example also parses an XML source taken from the supplied URI, which is treated as a URL. In this case, the default resolver tries to read the XML source from the filesystem.

    from Ft.Xml.Domlette import NonvalidatingReader
    doc = NonvalidatingReader.parseUri("file:///tmp/spam.xml")

The following example parses XML from the filesystem. When given a relative file path in the local OS's format, we must first convert that path to a URI that our reader objects can use.

    from Ft.Xml.Domlette import NonvalidatingReader
    from Ft.Lib import Uri
    file_uri = Uri.OsPathToUri('spam.xml')
    doc = NonvalidatingReader.parseUri(file_uri)

The following example parses XML from a string. Note that it does not provide a document/base URI.

    from Ft.Xml.Domlette import NonvalidatingReader
    doc = NonvalidatingReader.parseString("<spam>eggs</spam>")

In the following example, we are parsing XML from a string in a case where the document does need a base URI to be specified.

    from Ft.Xml.Domlette import NonvalidatingReader
    s = """<!DOCTYPE spam [ <!ENTITY eggs "eggs.xml"> ]>
    <spam>&eggs;</spam>"""
    doc = NonvalidatingReader.parseString(s, 'http://foo/test/spam.xml')
    # during parsing, the replacement text for &eggs;
    # will be obtained from http://foo/test/eggs.xml

In all of the above examples, doc is now a Domlette node object. 4Suite currently offers one Domlette implementation, written in C, called cDomlette.

### EntityReader Examples

Sometimes you need to parse a fragment of XML rather than the full document. If operating in non-validating mode is sufficient, Domlette has a reader that can handle this case. When parsing such a fragment, EntityReader returns a Domlette document fragment rather than a document object.

    from Ft.Xml.Domlette import EntityReader
    s = """
    <spam1>eggs</spam1>
    <spam2>more eggs</spam2>
    """
    docfrag = EntityReader.parseString(s, 'http://foo/test/spam.xml')

The content parsed by EntityReader must be an XML External Parsed Entity. This means that it can't be just any XML document. The main limitation is that it must not have a a document type declaration.

### ValidatingReader

If you want to validate a document with a DTD as you parse it, use the ValidatingReader object instead. If ValidatingReader discovers that the document that it is currently parsing is invalid, then it throws a Ft.Xml.ReaderException and does not finish parsing the document. The following example illustrates these concepts.

    # ValidatingReader is a global instance
    from Ft.Xml.Domlette import ValidatingReader

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

    doc = ValidatingReader.parseString(XML, "urn:x-example:valid-a")
    # And of course, as with other readers, you can use `parse`, `parseUri`, and
    # `parseStream` as well.

    # The following document, however, is invalid because an `a` element can only
    # have two `b` children according to its DTD.
    XML = """<!DOCTYPE a [
      <!ELEMENT a (b, b)>
      <!ELEMENT b EMPTY>
    ]>
    <a><b/><b/><b/></a>"""

    # This throws a `Ft.Xml.ReaderException` when it encounters invalid structure,
    # and does not finish parsing the document into `doc`.
    doc = ValidatingReader.parseString(XML, "urn:x-example:invalid-a")

### NoExtDtdReader

When using NonvalidatingReader to parse a document, that document's DTD is still opened and read to obtain information such as entity declarations and default attribute values. You cannot suppress reading of the internal DTD subset, but you can prevent the external subset from being accessed by using NoExtDtdReader. This won't affect the processing of external parameter entities defined in the internal DTD subset. Use this object as you would use NonvalidatingReader.

### Creating your own reader instance

In some cases you might not want to use the global reader instances. For instance in multithreaded use, you might want a reader per thread. Or you might want to change some of the parameters on the readers. If so, you can create your own reader instance:

    from Ft.Xml.Domlette import NonvalidatingReaderBase
    reader = NonvalidatingReaderBase()
    doc = reader.parseUri("http://xmlhack.com/read.php?item=1560")

Instead of NonvalidatingReaderBase, you could instead use NoExtDtdReaderBase or ValidatingReaderBase, depending on your needs. Each of these 3 readers take an optional inputSourceFactory constructor argument, which you can use to supply a custom URI resolver.

### InputSource objects

All of the previous examples involve parsing URIs or strings of data. You can also handle InputSource objects. An InputSource is an object that encapsulates a source of encoded text for parsing, and a URI resolver. The advantage to using an InputSource is that it provides a standard API to the text stream, and—perhaps more importantly—allows you to associate a custom URI resolver with the stream.

Normally, you can just get an InputSource by calling the convenience function Ft.Xml.CreateInputSource with a single argument, which is a string (not Unicode object), file-like object (stream), file path or URI. You can then pass the InputSource object to the reader's parse() method, as in the following example.

    from Ft.Xml import InputSource, CreateInputSource
    from Ft.Xml.Domlette import NonvalidatingReader

    #
    # Use CreateInputSource to parse a URL:
    #
    isrc = CreateInputSource("http://xmlhack.com/read.php?item=1560")
    doc1 = NonvalidatingReader.parse(isrc)
    #
    # Or a string:
    #
    isrc = CreateInputSource("<spam>eggs</spam>", "http://spam.com/base")
    doc2 = NonvalidatingReader.parse(isrc)
    #
    # InputSource is a file-like object, so you can treat it as such:
    #
    isrc = CreateInputSource("http://xmlhack.com/read.php?item=1560")
    raw_text = isrc.read()
    #
    # The uri/system ID you used for it is maintained
    #
    print isrc.uri
    #
    # You can also create other InputSources from URIs relative to this one
    #
    isrc2 = isrc.resolve("read.php?item=1703")

If you need lower-level control you can use an InputSourceFactory instance, calling the appropriate method: fromUri(uri), fromString(st), or fromStream(stream), much like the reader API described earlier. The following listing is functionally equivalent to the above one.

    from Ft.Xml import InputSource
    from Ft.Xml.Domlette import NonvalidatingReader

    factory = InputSource.DefaultFactory
    isrc = factory.fromUri("http://xmlhack.com/read.php?item=1560")
    doc1 = NonvalidatingReader.parse(isrc)
    #
    # The factory is reusable. Here we also parse a string:
    #
    isrc = factory.fromString("<spam>eggs</spam>", "http://spam.com/base")
    doc2 = NonvalidatingReader.parse(isrc)
    #
    # InputSource is a file-like object, so you can treat it as such:
    #
    isrc = factory.fromUri("http://xmlhack.com/read.php?item=1560")
    raw_text = isrc.read()
    #
    # The uri/system ID you used for it is maintained
    #
    print isrc.uri
    #
    # You can also create other InputSources from URIs relative to this one
    #
    isrc2 = isrc.resolve("read.php?item=1703")

### Converting from other DOM libraries

You can convert another Python DOM object (e.g. 4DOM or minidom) to a Domlette object using the function ConvertDocument:

    from Ft.Xml.Domlette import ConvertDocument
    converted_document = ConvertDocument(oldDocument, documentURI=u'http://www.example.org/')

The DocumentURI parameter provides a base URI for the converted nodes. If not specified, attributes documentURI and then baseURI are checked in the source DOM, as defined in DOM Level 3. If no URI is found in this way, a warning is issued and a UUID URI is generated for the new Domlette object.

## Domlette API summary

Interacting with Domlette documents

You will use a large part of the Domlette API to interact with the model of your XML documents. The implementation of this part of the API is found in the Ft.Xml.cDomlette module. This part of the API allows you to navigate around a document and modify the content of that document. It is very similar to the DOM Level 2 specification and follows some of the DOM Level 3 specification; feel free to refer to those specifications and the 4Suite API documentation for details about the intended behavior of this API. You can find brief descriptions of the methods and attributes provided by this API listed below. This API is also nearly the same as the API for xml.dom, which is bundled with Python. The node type constants are inherited directly from `xml.dom.Node`.

Many objects that you will work with in the Domlette API are descendents of the Domlette Node class. Documents, document fragments (of class DocumentFragment), Elements, attributes (class Attr), text (class Text), processing instructions (class ProcessingInstruction), and comments (class Comment) are all nodes; any node operations are defined on objects of these types, as well. Some operations do not make sense on some objects, however. For example, it does not make sense to add children to an attribute node.

In the DOM model of XML documents, there is a Document node which represents the starting point for the other pieces of the document. This node is not the root element of the document; rather, the Document node contains the root element as its only element child. The Document node may have other children, though, such as processing instructions and comments.

You can easily access properties of a node directly. The following properties are available on any node. These properties generally store information about the structure of the document in the near "vicinity" of the target node.

Properties available on every Node object

attributes

  This is a python dictionary containing the attributes defined on the target node. The key for the dictionary is a tuple containing the namespace and local name of the attribute. The value associated with this attribute name tuple is the attribute (of class Attr) itself.

      node = Parse("<foo a='1'/>")
      print node.childNodes[0].attributes

      {(None, u'a'): <Attr at 0x40870ecc: name u'a', value u'1'>}

baseURI

  This is the base URI in scope for the target node as a Python unicode string.

childNodes

  This is the Python list of all the node children of the target node. Note that in DOM terminology, the attributes of a node are not children of that node.

      node = Parse("<foo a='1'/>")
      print node.childNodes

      [<Element at 0x4086052c: name u'foo', 1 attributes, 0 children>]

firstChild

  This is the first child node of the target node. This is equivalent to `childNodes[0]`, and is a useful property for quickly walking the document tree.

      node = Parse("<foo a='1'/>")
      print node.firstChild

      <Element at 0x40860a6c: name u'foo', 1 attributes, 0 children>

lastChild

  This is the last child node of the target node. This is equivalent to `childNodes[-1]`.

      node = Parse("<foo a='1'/><!--Hi!-->")
      print node.lastChild

      <Comment at 0x4087caf4: u'Hi!'>

localName

  This is the local name of the target node as a Python unicode string.

namespaceURI

  This is the namespace URI of the target node as a Python unicode string.

nextSibling

  This is the node immediately following the target node, or `None` if the target node is the last child of its parent (or if the target node is an attribute, as attributes are unordered).

nodeValue

  This is the value of the target node as a Python unicode string, if the target node has a string value. If not, this is `None`. To illustrate some of the possibilities, attributes and text nodes have values, while elements and documents do not.

ownerDocument

  This is the Document node in which the target node is contained.

parentNode

  This is the parent of the target node. If the target node is a Document node, then this will be `None`; Document nodes do not have parents.

prefix

  This is the namespace prefix of the current node, or `None` if the current node does not (or cannot) have a namespace prefix.

previousSibling

  This is the node immediately preceding the target node, or `None` if the target node is the first child of its parent (or if the target node is an attribute, as attributes are unordered).

rootNode

  This is a synonym for ownerDocument.

xmlBase

  This is a synonym for baseURI.

In addition to accessing the structure relative to a node, there are also a set of operations that we can perform on these structures, including a variety of operations for modifying the document. Some of these methods allow you to add new nodes in various places; note that in the DOM, only Document nodes can create new nodes. See for details. The following methods are available on any node.

Methods available to every Node object

appendChild node

  This method adds node as the last child of the current instance. This is useful for manually building a document in breadth-first document order.

insertBefore newChild refChild

  This method adds the node newChild to the current instance immediately before child node refChild.

replaceChild newChild oldChild

  This method replaces the child node oldChild with the newChild node.

removeChild oldChild

  This method removes the oldChild node as a child of the instance node.

cloneNode deep

  This method returns a new copy of the current instance. If (and only if) deep is true, then we copy deeply: the node's attributes and children are also copied deeply.

isSameNode otherNode

  This method determines whether the instance node and otherNode are the same node based upon object identity.

normalize

  This method merges any adjacent text nodes in the attributes or descendents of the current instance.

hasChildNodes

  This method returns true if and only if the instance node has any child nodes.

xpath expr explicitNss

  This method evaluates the XPath expression expr with the current instance as the expression context and returns an appropriately-valued result. The explicitNss parameter is optional; it is a Python dictionary mapping namespace prefixes to namespaces for use in the expression. See for details.

In addition to their behavior as nodes, Document nodes are uniquely responsible for a number of tasks. For example, only Document nodes can create other nodes. The following methods are availble only to Document nodes.

Methods available to Document objects

createElementNS namespaceURI qualifiedName

  This method creates and returns a new Element with the given namespace URI and qualified name.

createAttributeNS namespaceURI qualifiedName

  This method creates and returns a new attribute (Attr object) with the given namespace URI and qualified name.

createTextNode data

  This method creates and returns a new Text node with the string value of data.

createProcessingInstruction target data

  This method creates and returns a new processing instruction (ProcessingInstruction object) with the given target name and contents taken from data.

createComment data

  This method creates and returns a new Comment with the string value of data.

createDocumentFragment

  This method creates and returns a new, empty document fragment (DocumentFragment object).

importNode importedNode deep

  Nodes can only belong to one document at a time. This method creates a copy of the node importedNode that belongs to the instance (but which does not yet have a parent). If (and only if) deep is true, then we copy deeply: the node's attributes and children are also copied deeply and imported.

Document nodes also have a number of properties that are not found on other nodes. These properties are summarized in the following list.

Properties available on Document objects

doctype

  This is a DocumentType object that encapsulates info about the document's "type", as described in its DOCTYPE tag. In Domlette, which doesn't use such objects, the value of the doctype property will always be `None`.

documentElement

  This is the root element of the document.

documentURI

  This is the URI that identifies the document.

implementation

  This is the DOMImplementation that created the document.

publicId

  This Domlette-specific property is the public ID of the DTD of this document.

rootNode

  This refers to the current instance.

systemId

  This Domlette-specific property is the system ID of the DTD of this document.

unparsedEntities

  This is the list of unparsed entities in the current document.

Attributes (Attr objects) do not have any special methods, but they do have a few additional properties. These properties are summarized in the following list.

Properties available on Attr objects

name

  This is the qualified name of the current instance.

nodeName

  This is a synonym for the name property.

ownerElement

  This is a synonym for the parentNode property.

specified

  You will probably never need this property. It is always `1`. DOM says it should be `0` if it is present through defaulting, rather than explicitly specified in the document. This is only possible if the DOM implementation preserves certain details from DTD processing, which 4Suite never does. Therefore the value is always `0`.

value

  This is a synonym for the nodeValue property.

Since attributes can only be attached to elements, Element objects have a set of special methods for managing which attributes are attached to them. We describe these methods below.

Methods available to Element objects

hasAttributeNS namespaceURI localName

  This method returns true if the current instance has an attribute with the given namespace URI and local name, and false otherwise.

getAttributeNS namespaceURI localName

  This method returns the attribute value of the attribute with the given namespace URI and local name, if one exists. If not, this returns `None`.

getAttributeNodeNS namespaceURI localName

  This method returns the Attr object of the attribute with the given namespace URI and local name, if one exists. If not, this returns `None`.

removeAttributeNS namespaceURI localName

  This method removes the attribute with the given namespace URI and local name from the current instance element.

removeAttributeNode node

  This method removes the attribute node from the current instance element.

setAttributeNS namespaceURI qualifiedName value

  This method adds an attribute or replaces an attribute with the specified namespace URI and qualified name and sets the content of that attribute to value.

setAttributeNodeNS node

  This method adds or replaces an attribute using the Attr object node.

Elements also have several properties above and beyond what they get from being Nodes. See the list below for details.

Properties available on Element objects

nodeName

  This is the qualified name of the current instance.

tagName

  This is a synonym for nodeName.

Both Text and Comment nodes are also more general CharacterData nodes in the DOM. CharacterData nodes have several additional properties and methods for managing the string data that they contain. The individual Text and Comment nodes, however, do not add any functionality to their general CharacterData parent class. You can find descriptions of the properties and methods offered by CharacterData objects below.

Properties available on CharacterData objects

data

  This is the string content of the current instance.

length

  This is the length of the string content of the current instance.

nodeValue

  This is a synonym for data.

Methods available to CharacterData objects

insertData offset data

  This method inserts the string data into the content of the current instance at the index specified by offset.

appendData data

  This method appends the string data to the end of the value of the current instance.

replaceData offset count data

  This method replaces count number of characters found at index offset in the current instance with the string data.

substringData offset count

  This method retrieves and returns the part of the string value of the current instance that begins at index offset and extends count characters.

deleteData offset count

  This method deletes the part of the string value of the current instance that begins at index offset and extends count characters.

A few DOM actions are not "owned" by any individual document. In effect, they are general-purpose operations. They can be found in DOMImplementation objects. One such precreated instance can be conveniently found at and used from Ft.Xml.Domlette.implementation. The general methods that such a DOMImplementation object offers are listed below.

DOMImplementation methods:

createDocument namespaceURI qualifiedName doctype

  This standard DOM method creates and returns a Document object associated with the given DocumentTyype object, and having a single element child with the given QName and namespace. Since Domlette does not use DocumentTyype objects, the doctype argument must be given as `None`.

createRootNode documentURI

  This Domlette-specific method creates a Document object with the specified document (base) URI. No document element is created. This method is generally preferred over createDocument(); see the following section, 'Building a DOM from scratch'.

hasFeature feature version

  This method tests whether the DOM implementation implements a specific feature.

### What about getElementsByTagName()?

The getElementsByTagName() method isn't supported, because there are better options. In particular, you can just use XPath:

    doc.xpath(u"//tagname")

For more possibilities, see getElementsByTagName Alternatives.

## Serializing Domlette nodes

Domlette comes with a couple of very fast printer functions which also go to great pains to correctly handle character encoding issues: Print and PrettyPrint. Here are some serialization examples using the Domlette printers, given a node '`node`' (it doesn't have to be a document node).

    from Ft.Xml.Domlette import Print, PrettyPrint

    # basic serialization to sys.stdout
    Print(node)

    # ... with extra whitespace (indenting)
    PrettyPrint(node)

    # ... using a single tab, rather than 2 spaces, to indent at each level
    PrettyPrint(node, indent='\t')

    # serializing to a utf-8 encoded file
    f = open('output.xml','w')
    Print(node, stream=f)
    f.close()

    # ... to an iso-8859-1 encoded file
    f = open('output.xml','w')
    Print(node, stream=f, encoding='iso-8859-1')
    f.close()

    # ... to an ascii encoded string
    import cStringIO
    buf = cStringIO.StringIO()
    Print(node, stream=buf, encoding='us-ascii')
    buf.close()
    s = buf.getvalue()

    # Normally, output syntax (XML or HTML) is chosen based on the DOM type,
    # which is automatically detected. A Domlette or XML DOM can be output in
    # HTML syntax if the asHtml=1 argument is given.
    PrettyPrint(node, asHtml=1)

See also: Serializing XML from DOM or Domlette documents

## Building a DOM from scratch

As an alternative to parsing a preexisting XML document, you can also build a document model, with certain limitations, from the ground up. W3C and Python DOM facilities for doing this are intended mainly for creating a temporary document whose nodes will be imported into an existing document, and while Domlette does offer a more convenient document creation method, it has many of the same limitations. However, for most documents, its capabilities should be sufficient.

The Ft.Xml.Domlette module contains a DOMImplementation instance named implementation which provides a set of methods for initializing new Documents. The implementation.createRootNode method takes a base URI argument and provides a natural approach for creating an XPath model root node. This is similar to the DOM idea of a document node and even closer to a DOM document fragment (multiple element children are allowed). The implementation.createDocument method, on the other hand, is designed to come close to the DOM interface, although its doctype argument must be `None`.

    doc = implementation.createRootNode('file:///article.xml')

is the equivalent of

    from Ft.Xml import EMPTY_NAMESPACE
    doc = implementation.createDocument(EMPTY_NAMESPACE, None, None)

with the added advantage of doc.baseURI being set to 'file:///article.xml', which is not possible to set via standard DOM interfaces (the baseURI attribute is read-only).

Similarly,

    from Ft.Xml import EMPTY_NAMESPACE
    doc = implementation.createRootNode('file:///article.xml')
    docelement = doc.createElementNS(EMPTY_NAMESPACE, 'article')
    doc.appendChild(docelement)

is the equivalent of

    from Ft.Xml import EMPTY_NAMESPACE
    doc = implementation.createDocument(EMPTY_NAMESPACE, 'article', None)

plus doc.baseURI being set to 'file:///article.xml'.

If you want as much fidelity to the DOM API as Domlette offers, use `implementation.createDocument`. If you just want to create a document or other such root-level node, and never mind the strange parameters, use implementation.createRootNode.

## XPath query

You can easily perform XPath queries by use the xpath method for cDomlette nodes as follows:

    from Ft.Xml.Domlette import NonvalidatingReader
    doc = NonvalidatingReader.parseString("<spam>eggs<a/><a/></spam>")
    print doc.xpath(u'//a')
    print doc.xpath(u'string(/spam)')

Notice: this is nothing like W3C DOM's XPath query module. The emphasis, as usual with Domlette, is on speed, simplicity and pythonic-ness.

The API, in brief:

`node.xpath(expr[, explicitNss])`

- node - will be used as core of the context for evaluating the XPath

- expr - XPath expression in string or compiled form

- explicitNss - (optional) any additional or overriding namespace mappings in the form of a dictionary that maps prefixes to namespace URIs. The base namespace mappings are taken from in-scope declarations on the given node. This explicit dictionary is superimposed on the base mappings.

For additional details, see .

## More on base URIs

For some users, always specifying a base URI feels like an inconvenience. Perhaps they always generate XML sources from text or streams without naturally associated URIs, and they have to figure out schemes to come up with base URIs for the parse. But there is good reason for this pickiness. Just ask one of the users who got bitten by carelessness with base URIs in practice. It's better to always put some amount of thought into base URIs when processing XML, and 4Suite encourages this.

Note that 4Suite only enforces the requirement for base URIs in cases where they are needed to make sense of a requested operation. Your document must have a valid base URI if you use external entities, XInclude, xsl:import, xsl:include, the XSLT document() function, the EXSLT exsl:document element, or any other operations that require access to an external resource. If your main use for URI resolution is XSLT import and includes, you can avoid having to give valid base URIs by using XSLT include paths.

A valid base URI starts with a scheme, such as `http:`. A simple name, such as "spam" is a valid relative URI reference, but not a valid base URI. Without a base URI, a relative reference is no more useful than an apartment number given without the address of the entire apartment building. Merging a base URI with a relative reference is a string operation that is undertaken in a standard manner, and is generally only useful when the base URI is hierarchical; that is, it is a URL using one of the common schemes that have slashes as path separators (e.g., http:, ftp:, gopher:, and most file: URLs). The built-in 4Suite URI resolver Ft.Lib.Uri.BASIC_RESOLVER knows how to perform such resolution.

## Why does Domlette diverge from the DOM specification?

Domlette is not a complete or fully conformant DOM implementation, but it does provide an interface very close to W3C DOM Level 2 and the corresponding Python mapping as laid out in the xml.dom API docs.

The areas of divergence are inconsequential for most users, and generally reflect decisions made in the interest of eliminating redundancy, inefficiency, and, to some degree, un-Pythonic design. Also, one of the important design principles for Domlette is that where DOM and XPath disagree, XPath wins; aside from making things more efficient to implement, this behavior is generally what people want in an XML document model.

It is also worth noting that in the interest of usability, all DOM implementations exhibit some degree of variation from the specs. Coding a completely implementation-agnostic DOM application is difficult and usually unnecessary.