# XSLT processing

## The super-simple XSLT API

For basic XSLT transform needs, or to get started quickly, the Ft.Xml.Xslt module offers a quick way to apply transforms XML documents and get back the simple string result. Within this module, the function of interest is `Transform`.

Transform fname_or_uri string_stream_fname_uri_isrc [param] [output]

  The `Transform` function takes two arguments, with an optional third. The first is the source XML for the transform. The second is the XSLT document. Both are given as a string, an object like an open file, a local file path on your computer, an absolute URI, or an InputSource object. The optional params is a dictionary of stylesheet parameters, the keys of which may be given as unicode objects if they have no namespace, or as (uri, localname) tuples if they do. The values are the overriden parameter values. If you do not supply the optional output parameter the return value is a string with the result of this transform. If you do supply this parameter it must be a file-like object to which the output will be written, and then the return value is None.

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

    from Ft.Xml.Xslt import Transform
    # URL for the identity transform: reproduces the input XML in the result
    ID_TRANSFORM = 'http://cvs.4suite.org/viewcvs/*checkout*/4Suite/Ft/Data/identity.xslt'

    result = Transform(XML, ID_TRANSFORM)
    print result

    # If the above XML document were located in the file
    # "target.xml", we could have used `Transform("target.xml", ID_TRANSFORM)`.

    #It's more efficient to redirect the processor output to an output stream.  The following does so:
    import sys
    result = Transform(XML, ID_TRANSFORM, output=sys.stdout)
    print result

## Full XSLT processing API

Here is the general procedure for using the Python API for XSLT processing:

1. Create an Ft.Xml.Xslt.Processor.Processor instance.

2. Prepare Ft.Xml.InputSource instances (via their factory) for the source XML and stylesheet.

3. Call the Processor's appendStylesheet method, passing it the stylesheet's InputSource.

4. Call the Processor's run method, passing it the source document's InputSource.

For input to our transform, we will use the namespaced example as in the last section.

    $ cat testNS.xml
    <ham xmlns="http://example.com/ns#">
    <eggs n='1'/>
    This is the string content with
     <em type='bold' f='2'>emphasized Namespaced Text</em>
    text
    </ham>

For our stylesheet, we will again use one of the simplest useful examples, the identity stylesheet.

    $ cat identity.xsl
    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>

    </xsl:stylesheet>

The code below follows the processing outline, having converted the input file and stylesheet to the URI format.

    from Ft.Xml.Xslt import Processor
    # We use the InputSource architecture
    from Ft.Xml import InputSource
    from Ft.Lib.Uri import OsPathToUri  # path to URI conversions

    processor = Processor.Processor()

    # Prepare an InputSource for the source document
    # Convert from local file to uri
    srcAsUri = OsPathToUri('testNS.xml')
    source = InputSource.DefaultFactory.fromUri(srcAsUri)

    # Prepare an InputSource for the stylesheet
    # Convert from local file to uri
    ssAsUri = OsPathToUri('identity.xsl')
    transform = InputSource.DefaultFactory.fromUri(ssAsUri)

    processor.appendStylesheet(transform)
    result = processor.run(source)

    # result is a string with the serialized transform result
    print result

You can call run multiple times on different InputSources. When you're done, the processor's reset method can be used to restore a clean slate (at which point you would have to append stylesheets to the processor again).

The following example uses our `processor` from the previous example to transform a new XML document, this one constructed manually.

    XML = """<foo><bar/></foo>"""
    source = InputSource.DefaultFactory.fromString(XML, 'http://example.org/foo')

    result = processor.run(source)

    # result is a string with the serialized transform result
    print result

This code continues from the previous example to process the second document, using the same `processor` and stylesheet. This is a useful form when there is a requirement for server side processing of multiple input documents with a common stylesheet.

## Example

In the example below, strings are used as the source of the transform (stylesheet) and source documents, and we are careful to pass in a URI to identify each of them. In the source document, the URI is needed for resolving external entity references and XIncludes. In the stylesheet, the URI is needed for resolving `document` function calls, xsl:includes and xsl:imports.

If you do not provide a URI and you attempt to use any of these features, you may get an exception.

    # The identity transform: duplicates the input to output
    TRANSFORM = """
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>

    </xsl:stylesheet>"""

    SOURCE = """<spam id="eggs">I don't like spam</spam>"""

    # The processor class is the core of the XSLT API
    from Ft.Xml.Xslt import Processor
    processor = Processor.Processor()

    # We use the InputSource architecture
    from Ft.Xml import InputSource

    # Prepare an InputSource for the transform
    transform = InputSource.DefaultFactory.fromString(TRANSFORM,
      "http://spam.com/identity.xslt")

    # Prepare an InputSource for the source document
    source = InputSource.DefaultFactory.fromString(SOURCE,
      "http://spam.com/doc.xml")
    processor.appendStylesheet(transform)
    result = processor.run(source)

    # result is a string with the serialized transform result
    print result

## Using Domlette objects instead of InputSources

If your documents are already in the form of Domlette documents, you don't need to create InputSources for them; you can just use the Processor's appendStylesheetNode and runNode methods instead of appendStylesheet and run, respectively.

It is usually slower to read the stylesheet from a Domlette object than to parse a serialized document.

The Domlette documents used in the following example are obtained by parsing existing XML, but this approach can just as easily be used on Domlette documents that are built programmatically (i.e. using the DOM API).

    # The identity transform: duplicates the input to output
    TRANSFORM = """
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>

    </xsl:stylesheet>"""

    SOURCE = """<spam id="eggs">I don't like spam</spam>"""

    from Ft.Xml.Xslt import Processor
    processor = Processor.Processor()
    from Ft.Xml.Domlette import NonvalidatingReader

    # Create a DOM for the transform
    transform = NonvalidatingReader.parseString(TRANSFORM,
      "http://spam.com/identity.xslt")

    # Create a DOM for the source document
    source = NonvalidatingReader.parseString(SOURCE, "http://spam.com/doc.xml")
    processor.appendStylesheetNode(transform, "http://spam.com/identity.xslt")
    result = processor.runNode(source, "http://spam.com/doc.xml")
    print result

If you have objects from another DOM library, you can first convert them to Domlette objects as shown in .

## Top-level parameters

Passing parameters to a stylesheet

You can pass in stylesheet parameters as a Python dictionary. Use the parameter names for keys. Values use the 4Suite XPath library's standard type mappings, which are described in .

Parameter and variable names in XPath/XSLT are actually expanded-names, which we represent as (namespaceURI, localName) tuples. If your parameter name is in a namespace, you have to use a tuple as the mapping key. Otherwise, you may simply use a unicode string that represents the local-name part only (`Ft.Xml.EMPTY_NAMESPACE` is the default namespace).

Here is an example, which passes in the computed "date" parameter to the stylesheet from the program:

    SRC = """<?xml version="1.0"?><dummy/>"""

    STY = """<?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

      <xsl:param name="date" select="'unknown'"/>

      <xsl:output method="xml" indent="yes" encoding="us-ascii"/>

        <xsl:template match="/">
          <result>
            <xsl:value-of select="$date"/>
          </result>
        </xsl:template>

    </xsl:stylesheet>"""

    from Ft.Xml import InputSource
    from Ft.Xml.Xslt import Processor
    import time
    src_isrc = InputSource.DefaultFactory.fromString(SRC, 'http://foo/dummy.xml')
    sty_isrc = InputSource.DefaultFactory.fromString(STY, 'http://foo/dummy.xsl')

    proc = Processor.Processor()
    proc.appendStylesheet(sty_isrc)
    params = {u'date': unicode(time.asctime())}
    result = proc.run(src_isrc, topLevelParams=params)
    print result

## Using xml-stylesheet processing instructions

4Suite honors the Associating Stylesheets with XML Documents W3C Recommendation and RFC 3023: XML Media Types. Instead of (or in addition to) using the processor's explicit APIs to establish the stylesheet to be used for the transformation, the source document may contain an xml-stylesheet processing instruction (PI) that refers to a stylesheet via a URI reference.

The xml-stylesheet PI must meet the following criteria:

- It must appear in the document prolog.

- It must contain a "type" pseudo-attribute having one of the following values:

  - application/xslt+xml

  - application/xslt

  - text/xml

  - application/xml

- It must contain an "href" pseudo-attribute that is a URI reference for the stylesheet. It will be resolved relative to the base URI of the source document that contains the xml-stylesheet PI.

This example shows a PI being used to refer to the identity stylesheet mentioned earlier

    <?xml-stylesheet type="application/xslt" href="identity.xsl"?>

If you need to add to the supported media types, e.g., to add the nonstandard "text/xsl", then follow the example given in this mailing list message.

If the PI contains "alternate" and "media" pseudo-attributes, the package will do its best to handle them. See this message for details and examples.

## Alternative output destinations

Normally, the processor buffers all output, then returns it as a byte string. If you want to write directly to some other stream (any Python file-like object that has a write method), you can supply the stream as the optional outputStream argument to the Processor's run method. When you supply your own output stream, the run method will return `None`. Here is an example that writes directly to `stdout`:

Transform output sent to standard out

    SRC = """<?xml version="1.0"?><dummy/>"""

    STY = """<?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

      <xsl:output method="xml" indent="yes" encoding="us-ascii"/>

      <xsl:template match="/">
        <result>hello world</result>
      </xsl:template>

    </xsl:stylesheet>"""

    import sys
    from Ft.Xml import InputSource
    from Ft.Xml.Xslt import Processor

    src_isrc = InputSource.DefaultFactory.fromString(SRC, 'http://foo/dummy.xml')
    sty_isrc = InputSource.DefaultFactory.fromString(STY, 'http://foo/dummy.xsl')

    proc = Processor.Processor()
    proc.appendStylesheet(sty_isrc)
    result = proc.run(src_isrc, outputStream=sys.stdout)

You also have the option of other kinds of output. Just set the `writer` argument of the processor's run method to an instance of an XSLT output writer, which is a handler of SAX-like events coming from the processor as it generates the result tree. 4Suite provides several writer classes for alternative output:

- If you want the XSLT output as SAX events, use an instance of Ft.Xml.Xslt.SaxWriter.SaxWriter. Give its constructor a saxHandler keyword argument that is your own PyXML SAX2 event handler.

- If you want the XSLT output as a Domlette document, use an instance of Ft.Xml.Xslt.RtfWriter.RtfWriter. Give its constructor a second argument: the base URI of the document to create. Obtain the document by calling the writer's getResult method after XSLT processing is finished.

- If you want the XSLT output as any other kind of Python DOM document, use an instance of Ft.Xml.Xslt.DomWriter.DomWriter. Give its constructor an implementation keyword argument that is your desired DOM implementation. Also try to set the ownerDoc to an existing Document node (from the same implementation) from which a base URI for the new document can be obtained.

- If you want the XSLT output in a regular file, open a file for writing then pass this file object to the `proc.run` as the outputStream parameter value, in the same way as the example above which used the `sys.stdout` file object. An example is shown below.

- If you want to make a custom output writer, just make your class extend Ft.Xml.Xslt.NullWriter.NullWriter. If it needs access to the XSLT output parameters, then the constructor should take an instance of Ft.Xml.Xslt.OutputParameters.OutputParameters, which will have the data attributes method, version, encoding, omitXmlDeclaration, standalone, doctypeSystem, doctypePublic, mediaType, cdataSectionElements, and indent, which your writer can act upon, if appropriate. See the `NullWriter` API documentation for further info.

Here is an example of writing to a regular Domlette document:

    SRC = """<?xml version="1.0"?><dummy/>"""

    STY = """<?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

      <xsl:output method="xml" indent="yes" encoding="us-ascii"/>

      <xsl:template match="/">
        <result>hello world</result>
      </xsl:template>

    </xsl:stylesheet>"""

    import sys
    from Ft.Xml import InputSource
    from Ft.Xml.Xslt import Processor
    from Ft.Xml.Xslt.DomWriter import DomWriter
    from Ft.Xml.Domlette import PrettyPrint

    src_isrc = InputSource.DefaultFactory.fromString(SRC, 'http://foo/dummy.xml')
    sty_isrc = InputSource.DefaultFactory.fromString(STY, 'http://foo/dummy.xsl')

    from Ft.Xml.Domlette import implementation as impl
    domlette_writer = DomWriter(implementation=impl)

    proc = Processor.Processor()
    proc.appendStylesheet(sty_isrc)
    proc.run(src_isrc, writer=domlette_writer)

    result_doc = domlette_writer.getResult()
    PrettyPrint(result_doc)

This example writes the transform output to a file. This is a variant of the earlier one. Output is written to `tmp.xml`.

    SRC = """<?xml version="1.0"?><dummy/>"""

    STY = """<?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

      <xsl:output method="xml" indent="yes" encoding="us-ascii"/>

      <xsl:template match="/">
        <result>hello world</result>
      </xsl:template>

    </xsl:stylesheet>"""

    import sys
    from Ft.Xml import InputSource
    from Ft.Xml.Xslt import Processor

    src_isrc = InputSource.DefaultFactory.fromString(SRC, 'http://foo/dummy.xml')
    sty_isrc = InputSource.DefaultFactory.fromString(STY, 'http://foo/dummy.xsl')

    proc = Processor.Processor()
    proc.appendStylesheet(sty_isrc)

    f = open('tmp.xml', mode='w')
    result = proc.run(src_isrc, outputStream=f)
    f.close()

There are many more options available for customizing XSLT development; see the Processor module documentation for details:

    >>> from Ft.Xml.Xslt import Processor
    >>> help(Processor)

## Transform chaining

4Suite provides some hooks for scenarios where the output from one transform becomes the source document for another. This is called transform chaining. The user still has to write the sequence of transform invocations in the Python API (the 4xslt command can perform chaining for the user). This section shows how.

In the following example the next transform in the chain is set from within XSLT.

    # The first transform: just reproduces all para elements within a wrapper
    TRANSFORM = """
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:f="http://xmlns.4suite.org/ext"
      extension-element-prefixes="f"
    >

    <!-- Top level param so that user can pass in the next transform in the
         chain.  By default, use the identity transform -->
    <xsl:param name="next-xslt"/>

    <!-- grab just the first paras for the output -->
    <xsl:template match="/">
      <parawrapper>
        <xsl:apply-templates select="//para"/>
      </parawrapper>
      <!-- Set the next transform in the chain.  You can also set to a
           hard-coded string -->
      <!-- notice that this is within a template, for instantiation -->
      <f:chain-to href="{$next-xslt}"/>
    </xsl:template>

    <xsl:template match="para">
      <xsl:copy-of select="."/>
    </xsl:template>

    </xsl:stylesheet>"""

    DOC = """<doc>a<para>1</para>b<para>2</para>c</doc>"""

    from Ft.Xml.Xslt import Processor
    from Ft.Xml import InputSource

    transform = InputSource.DefaultFactory.fromString(TRANSFORM, "urn:x-bogus:main.xslt")

    IDT = u'http://cvs.4suite.org/viewcvs/*checkout*/4Suite/Ft/Data/identity.xslt'

    processor = Processor.Processor()
    processor.appendStylesheet(transform)
    source = InputSource.DefaultFactory.fromString(DOC, "urn:x-bogus:doc.xml")
    result = processor.run(source, topLevelParams={(None, 'next-xslt'): IDT})
    print result

    # processor.chainTo is the fully-resolved absolute URI of the next transform,
    # or None if there was no f:chain-to element instantiated in the transform that
    # the processor last processed.
    next = processor.chainTo

    processor = Processor.Processor()
    processor.appendStylesheet(InputSource.DefaultFactory.fromUri(next))
    source = InputSource.DefaultFactory.fromString(DOC, "urn:x-bogus:doc.xml")
    result = processor.run(source)
    print result

    next = processor.chainTo                      # Should now be None
    print "chainTo:", processor.chainTo

Note: There is not yet an API for automating the transform chain loop above. Ideas were discussed and an experiment was conducted here. If you have ideas for a good API, please submit them to the mailing list.

## XSLT patterns

XSLT defines a pattern language based on XPath which is used to declare rules for matching patterns in the XML source against which to fire XSLT templates. The pattern implementation that 4Suite's XSLT library uses is also exposed as a library of its own. XSLT patterns are useful when your task is not so much to compute arbitrary information from a given node but, rather, to choose quickly from a collection of nodes the ones that meet some basic rules. This might seem a subtle difference. The following example might help illustrate it.

- XPath task: extract the class attribute from all the child elements of the context node

- XSLT pattern task: given a list of nodes, sort them into piles of those that have a class attribute and those that have a title child

The main API for pattern processing in 4Suite is Ft.Xml.Xslt.PatternList. The following is a code snippet that takes a node and returns a list of patterns it matches.

    from Ft.Xml.Xslt import PatternList
    from Ft.Xml.Domlette import NonvalidatingReader

    # first pattern matches nodes with an href attribute
    # the second matches elements with a title child
    PATTERNS = ["*[@class]", "*[title]"]

    # Second parameter is a dictionary of prefix to namespace mappings
    plist = PatternList(PATTERNS, {})

    DOC = """
    <spam>
      <e1 class="1"/>
      <e2><title>A</title></e2>
      <e3 class="2"><title>B</title></e3>
    </spam>"""

    doc = NonvalidatingReader.parseString(DOC, "file:foo.xml")
    for node in doc.documentElement.childNodes:
        # Don't forget that the white space text nodes before and after
        # e1, e2 and e3 elements are also child nodes of the spam element
        if node.nodeName[0] == "e":
            print plist.lookup(node)

The PatternList initializer takes my list of strings, which it conveniently converts to a list of compiled pattern objects. Such objects have a match method that returns a boolean value, but I don't use these methods directly in this example. The PatternList initializer also takes a dictionary that makes up the namespace mapping. In this example, we use no namespaces, so the dictionary is empty. The lookup method is applied to a selection of the children of the spam element (all the nodes whose name starts with "e", which happens to be all the element nodes). The output of listing 4 follows:

    [*[attribute::class]]
    [*[child::title]]
    [*[attribute::class], *[child::title]]

The output is a list of the representations of the pattern objects that matched each node. Notice how the axis abbreviations have been expanded in the pattern object representation.