XQuery/XQuery and XSLT
Motivation
[edit | edit source]You want to create a RESTful web service that executes an XSLT transform on an XML document.
Method
[edit | edit source]XQuery is superior to XSLT in many ways. XQuery is designed to be brief and concise programming language that interleaves XML and functional language statements. Therefore, XQuery programs are usually much smaller than XSLT. XQuery processors are also designed to use indexes so that XQueries over large data sets can run quickly. But unfortunately there are still some times when you must use XSLT. One example of this is in-browser transforms. The eXist database comes with an XQuery function that allows you to transform an XML file using XSLT.
Creating an XSLT service
[edit | edit source]eXist includes a function to call an XSLT transform is the following:
transform:transform($input as node()?, $stylesheet as item(), $params as node()?) node()?
where:
$input is the node tree to be transformed $stylesheet is either a URI or a node to be transformed. If it is an URI, it can either point to an external location or to an XSL stored in the db by using the 'xmldb:' scheme. $params are the optional XSLT name/value parameters with the following structure: <parameters> <param name="param-name1" value="param-value1"/> </parameters>
The result is zero or one nodes. The namespace of the transform module is http://exist-db.org/xquery/transform.
The transform:transform() function can be used to provide a service which accepts the url of an XML file, the url of an XSLT script and any other parameters which are passed to the stylesheet.
Currently output is text/html.
declare option exist:serialize "method=html media-type=text/html";
(: look for URL parameters for the XML file and the transform :)
let $xslt:= request:get-parameter("xslt",())
let $xml := request:get-parameter("xml",())
(: now get a list of all the URL parameters that are not either xml= or xslt= :)
let $params :=
<parameters>
{for $p in request:parameter-names()
let $val := request:get-parameter($p,())
where not($p = ("xml","xslt"))
return
<param name="{$p}" value="{$val}"/>
}
</parameters>
return
(: now run the transform :)
transform:transform(doc($xml), doc($xslt), $params)
Checking XSLT Version
[edit | edit source]The following XSLT is useful for checking what version of XSLT you are running.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<p>Version:
<xsl:value-of select="system-property('xsl:version')" />
<br />
Vendor:
<xsl:value-of select="system-property('xsl:vendor')" />
<br />
Vendor URL:
<xsl:value-of select="system-property('xsl:vendor-url')" />
</p>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Form-based search
[edit | edit source]In this example, an XML file on one host is transformed by a XSLT script on another. The XSLT script defines a form to allow the use to select a subset of the entries in the XML file, followed by the search results, if any.
A sequence diagram describes the interaction involved:
Page Scraping Example
[edit | edit source]see Page scraping and Yahoo Weather
Using XSLT Imports
[edit | edit source]XSLT allows you to call a stylesheet that imports a common library of other XSLT templates. But not all of the XSLT import path statements will work within eXist. In the following example we will use a XSLT style sheet that imports another style sheet. The following assumes you have a collection called /db/test/xslt and all the files are placed in that collection.
XQuery XSLT Test Program
[edit | edit source]xquery version "1.0";
declare namespace transform="http://exist-db.org/xquery/transform";
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";
let $input :=
<data>
<element>element 1</element>
<element>element 2</element>
<element>element 3</element>
</data>
return
<html>
<head>
<title>Demonstration of running XSLT within an XQuery</title>
</head>
<body>
<h1>Demonstration of running XSLT within an XQuery</h1>
{ transform:transform($input, doc("/db/test/xslt/style.xsl"), ()) }
</body>
</html>
Top-Level Style.xsl
[edit | edit source]<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:import href="common.xsl"/>
<xsl:template match="/">
<ol>
<xsl:apply-templates/>
</ol>
</xsl:template>
</xsl:stylesheet>
In the second line, the following imports do work as expected:
<xsl:import href="common.xsl"/>
<xsl:import href="common.xsl" xml:base="http://localhost/exist/rest/db/test/xslt"/>
<xsl:import href="common.xsl" xml:base="/exist/rest/db/test/xslt"/>
But you will note that using the following does not work:
<xsl:import href="/exist/rest/db/test/xslt/common.xsl"/>
Imported common.xsl
[edit | edit source]<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="element">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
</xsl:stylesheet>
XForms Example
[edit | edit source]You can also create a simple XForms example that serves as a front end to this script. See the XRX wikibook for an example of this XForms front end.
xquery version "1.0";
declare option exist:serialize "method=xhtml media-type=text/xml indent=no process-xsl-pi=yes";
(:
transform:transform($node-tree as node()?, $stylesheet as item(), $parameters as node()?, as xs:string) node()?
:)
let $transform := 'http://localhost:8080/exist/rest/db/xforms/xsltforms/xsltforms.xsl'
let $form :=
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xf="http://www.w3.org/2002/xforms">
<head>
<title>XForms Template</title>
<xf:model>
<xf:instance xmlns="" id="save-data">
<data>
<name>John Smith</name>
</data>
</xf:instance>
</xf:model>
</head>
<body>
<h1>XForms Test Program</h1>
<xf:input ref="name">
<xf:label>Name: </xf:label>
</xf:input>
</body>
</html>
let $serialization-options := 'method=xml media-type=text/xml omit-xml-declaration=yes indent=no'
let $params :=
<parameters>
<param name="output.omit-xml-declaration" value="yes"/>
<param name="output.indent" value="no"/>
<param name="output.media-type" value="text/html"/>
<param name="output.method" value="xhtml"/>
</parameters>
return
transform:transform($form, $transform, $params, $serialization-options)
Caching Management
[edit | edit source]By default, once a document has been transformed it resides in the cache. This is very good for performance reasons if a file needs to be retransformed but sometimes if the source file changes the transform needs to be rerun.
You can disable caching by changing the configuration file. In the file conf.xml change the @caching value from yes to no.:
<transformer class="org.apache.xalan.processor.TransformerFactoryImpl" caching="no"/>