XSLTForms/The transform function

Description

edit

The transform() function is an XSLTForms extension to the standard XForms function library. Public discussions suggest that it's based on proposals for a similar function to be included in XForms 2.0, but work seems to have stalled in 2011. There seems to be no transform() function in the current draft of the XPath Expressions Module, and not currently active specification of a separate module for the transform() function.

Syntax / Signature

edit

The transform() function applies an external XSLT stylesheet to a node set. In current versions of XSLTForms (since 2014 or so), it takes a series of three or more arguments:

  • The first argument is a node to be transformed (e.g. instance('foo')/bar/baz or just child-element or .).
  • The second argument is either
    • the URI of an XSLT stylesheet, as a string (e.g. 'my-transform.xsl'), or
    • a string representation of an XSLT stylesheet.
  • The third argument is true if the second argument is an XSLT stylesheet in string form, false if the second argument is the URI of an XSLT stylesheet.
  • The fourth, sixth, eighth, ... arguments, if present, are names of XSLT stylesheet parameters.
  • The fifth, seventh, ninth, ... arguments, if present, are values for those parameters.

Note: Earlier versions of the function took only two arguments: the node to be transformed, and the URI of an XSLT stylesheet.

The transform() function returns a string. This will seem implausible to many XSLT users, so it needs to be stressed: the transform() function returns a string.

  • If the XSLT output method is text, the returned value is the string (wrapped, in almost all browsers under almost all circumstances, in some browser-dependent XML element).
  • If the XSLT output method is xml, the returned value is the serialized form of the XML document produced. Under most circumstances, looking at it will show the angle brackets and ampersands escaped as < and &. If the returned value is injected into the XHTML document, however, the results are formatted like normal HTML.

(And the html output method?)

Examples and uses

edit

Formatting data in read-only form

edit

One use for transform() is to format XML instance data into appropriately styled HTML for convenient read/only display by the browser. For example:

  <div style="margin: 1em 0;">
    <xf:output value="transform(my-tricky-element, 'display-tricky.xsl')"
               mediatype="application/xhtml+xml" />
  </div>

Showing XML source

edit

Another use for transform() is to pretty-print XML instance data for convenient read/only display of the XML source by the browser. For example:

  <h2>XML representation of the data</h2>

  <pre style="border: 2px #552211 solid; 
              padding: 0.5em; 
              background-color: #f7eed4;">
    <xf:output value="transform(instance('doc'),'../lib/prettyprint.xsl')" 
               mediatype="text/plain" />
  </pre>

This example assumes a pretty-printing stylesheet which produces output with newlines and suitable indentation. This is not necessary in principle (one could use serialize(instance('user-data')) instead), but useful in practice, since otherwise some browsers display the entire XML document on one very long line of output.

Updating an instance

edit

Since transform() returns a string, the following attempt to generate a new document instance for further processing by XForms will fail: instead of replacing instance foo with the output of the transformation, it will replace the content of foo with the serialized string form of the transformation result.

  <xf:trigger>
    <xf:label>Generate diagram</xf:label>
    <xf:action ev:event="DOMActivate">
      <xf:setvalue ref="instance('svg')" 
                   value="transform(instance('input'),'make-svg.xsl')"/>
      <xf:toggle case="picture"/>
    </xf:action>
  </xf:trigger>

If the svginstance contained a normal SVG document to start with, then after the trigger shown is fired, it will consist of an SVG element containing an XML-escaped SVG document as a single text-node:

  <svg xmlns="http://www.w3.org/2000/svg" 
       width="100%" 
       height="100%">&lt;svg xmlns="http://www.w3.org/2000/svg" 
           xmlns:svg="http://www.w3.org/2000/svg" 
           width="900" 
           height="1000"&gt;
  &lt;desc&gt;SVG representation of the data&lt;/desc&gt;
  ...
  &lt;/svg&gt;</svg>

One workaround for this problem is to use the experimental action xf:setnode with inner or outer attributes, to replace either the content of the node or the node itself:

  <xf:trigger>
    <xf:label>Inner</xf:label>
    <xf:setnode ref="." ev:event="DOMActivate"
               inner="'&lt;item&gt;b&lt;/item&gt;'"/>
  </xf:trigger>
  <xf:trigger>
    <xf:label>Outer</xf:label>
    <xf:setnode ref="item" ev:event="DOMActivate"
               outer="'&lt;item&gt;c&lt;/item&gt;'"/>
  </xf:trigger>

Another workaround is to submit the string to a script on the server which echoes it back as XML. (See the example in the discussion of the setnode action.)

Known problems and issues

edit

In Safari, and in older versions of Chrome (through version 30 and possibly later), the transformation may fail if the stylesheet whose URI is given in the second argument to transform() contains an xsl:import instruction. The symptom is an error alert saying TypeError: null is not an object (evaluating 'resultDocument.documentElement'). (It's likely that this problem will also arise if the stylesheet contains an xsl:include instruction, but that has not been verified.) This problem does not arise in Firefox, Opera, or newer versions of Chrome (from version 49 and possibly earlier).

Workaround: remove the xsl:import, so that the stylesheet is a free-standing object without dependencies on other stylesheets.


Further information

edit

Some further information can be gleaned from a a set of transform() test cases which illustrate a variety of different ways to call the function and show what it produces in each case.