XQuery/XQuery and XSLT

Motivation

edit

You want to create a RESTful web service that executes an XSLT transform on an XML document.

Method

edit

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

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

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>
edit

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.

  1. Stylesheet
  2. Data
  3. Search Whisky data

A sequence diagram describes the interaction involved:

Sequence Diagram

Page Scraping Example

edit

see Page scraping and Yahoo Weather

Using XSLT Imports

edit

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
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
<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
<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

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

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"/>

http://demo.exist-db.org/exist/xquery.xml#N10375