The XSLT/JavaScript Interface In Gecko
Introduction
With modern browsers supporting XSLT, developers can now use JavaScript to access the power that XSLT provides. JavaScript can enable a web application to load XML data, process it via XSLT into a presentable form and then add it into an existing document. Since the XML data loaded only contains the raw information without any presentation data, it can load quickly even on dialup.
XSLT allows the author to directly manipulate the structure of a document. For example, it permits the rearranging and sorting of elements; it also provides more fine-grained control of the resulting document's structure.
As of Mozilla 1.2, Gecko enables JavaScript to create XSLT processors. This article covers XSLT/JavaScript bindings in Gecko. They are not available in Netscape 7.0x.
JavaScript/XSLT Bindings
JavaScript can run XSLT transformations through the
XSLTProcessor object. Once instantiated, a
XSLTProcessor has an importStylesheet method that
takes as an argument the XSLT stylesheet to be used in the transformation. The
stylesheet has to be passed in as an XML document, which means that the .xsl
file has to be loaded by the page before calling importStylesheet.
This can be done via XMLHttpRequest or
XMLDocument.load.
JavaScript
var
xsltProcessor = new XSLTProcessor();
// Load the xsl file
using synchronous (third param is set to false)
XMLHttpRequest
var myXMLHTTPRequest = new
XMLHttpRequest();
myXMLHTTPRequest.open("GET", "example.xsl",
false);
myXMLHTTPRequest.send(null);
var
xslRef = myXMLHTTPRequest.responseXML;
// Finally import the
.xsl
xsltProcessor.importStylesheet(xslRef);
For the actual transformation, XSLTProcessor requires an XML
document, which is used in conjunction with the imported XSL file to produce the
final result. The XML document can be a separate XML file loaded as shown in
figure 1, or it can be part of the existing page. To process part of a page's
DOM, it is necessary to first create and XML document in memory. Assuming that
the DOM to be processed is contained by an element with the id
example, that DOM can be "cloned" using the in-memory XML
document's importNode method. importNode allows
transferring a DOM fragment between documents, in this case from an HTML
document to an XML document. The first parameter references the DOM node to
clone. By making the second parameter "true", it will clone all descendants as
well (a deep clone). The cloned DOM can then be easily inserted into the XML
document using appendChild, as shown in figure 2.
JavaScript
// create a
new XML document in memory
var xmlRef =
document.implementation.createDocument("", "", null);
// we
want to move a part of the DOM from an HTML document to an XML
document.
// importNode is used to clone the nodes we want to
process via XSLT - true makes it do a deep clone
var myNode =
document.getElementById("example");
var clonedNode =
xmlRef.importNode(myNode, true);
// add the cloned DOM into
the XML document
xmlRef.appendChild(clonedNode);
Once the stylesheet has been imported, XSLTProcessor has to
perform two methods for the actual transformation, namely
transformToDocument() and transformToFragment().
transformToDocument() returns a full XML document while
transformToFragment() returns a document fragment that can be
easily added to an existing document. Both take in the XML document as the first
parameter that will be transformed.
transformToFragment() requires a second parameter, namely the
document object that will own the generated fragment. If the generated fragment
will be inserted into the current HTML document, passing in
document is enough.
JavaScript
var fragment
= xsltProcessor.transformToFragment(xmlRef, document);
Basic Example
The basic example will load an XML file and apply a XSL transformation on it. These are the same files used in the "Generating HTML" example in the XSLT in Netscape Gecko article. The XML file describes an article and the XSL file formats the information for display.
XML Document
(example1.xml):
<?xml
version="1.0"?>
<myNS:Article
xmlns:myNS="http://devedge.netscape.com/2002/de">
<myNS:Title>My Article</myNS:Title>
<myNS:Authors>
<myNS:Author company="Foopy Corp.">Mr. Foo</myNS:Author>
<myNS:Author>Mr. Bar</myNS:Author>
</myNS:Authors>
<myNS:Body>
The <b>rain</b>
in <u>Spain</u>
stays mainly in the plains.
</myNS:Body>
</myNS:Article>
XSL Stylesheet
(example1.xsl):
<?xml
version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myNS="http://devedge.netscape.com/2002/de">
<xsl:output
method="html" />
<xsl:template
match="/">
<html>
<head>
<title>
<xsl:value-of
select="/myNS:Article/myNS:Title"/>
</title>
<style
type="text/css">
.myBox
{margin:10px 155px 0 50px; border: 1px dotted #639ACE; padding:0 5px 0
5px;}
</style>
</head>
<body>
<p
class="myBox">
<span
class="title">
<xsl:value-of
select="/myNS:Article/myNS:Title"/>
</span>
</br>
Authors: <br
/>
<xsl:apply-templates
select="/myNS:Article/myNS:Authors/myNS:Author"/>
</p>
<p
class="myBox">
<xsl:apply-templates
select="//myNS:Body"/>
</p>
</body>
</html>
</xsl:template>
<xsl:template
match="myNS:Author">
--
<xsl:value-of select="."
/>
<xsl:if
test="@company">
:: <b> <xsl:value-of select="@company"
/> </b>
</xsl:if>
<br
/>
</xsl:template>
<xsl:template
match="myNS:Body">
<xsl:copy>
<xsl:apply-templates
select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template
match="@*|node()">
<xsl:copy>
<xsl:apply-templates
select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The example loads using synchronous XMLHTTPRequest both the .xsl
(xslStylesheet) and the .xml (xmlDoc) files into
memory. The .xsl file is then imported
(xsltProcessor.importStylesheet(xslStylesheet)) and the
transformation run (xsltProcessor.transformToFragment(xmlDoc,
document)). This allows fetching of data after the page has been loaded,
without initiating a fresh page load.
JavaScript:
var
xslStylesheet;
var xsltProcessor = new XSLTProcessor();
var
myDOM;
var xmlDoc;
function Init(){
// load the
xslt file, example1.xsl
var myXMLHTTPRequest = new
XMLHttpRequest();
myXMLHTTPRequest.open("GET", "example1.xsl",
false);
myXMLHTTPRequest.send(null);
xslStylesheet
=
myXMLHTTPRequest.responseXML;
xsltProcessor.importStylesheet(xslStylesheet);
//
load the xml file, example1.xml
myXMLHTTPRequest = new
XMLHttpRequest();
myXMLHTTPRequest.open("GET", "example1.xml",
false);
myXMLHTTPRequest.send(null);
xmlDoc =
myXMLHTTPRequest.responseXML;
var fragment =
xsltProcessor.transformToFragment(xmlDoc,
document);
document.getElementById("example").innerHTML =
"";
myDOM =
fragment;
document.getElementById("example").appendChild(fragment);
}
Setting Parameters
While running transformations using precoded .xsl and .xml files is quite useful, configuring the .xsl file from JavaScript may be even more useful. For example, JavaScript and XSLT could be used to sort XML data and then display it. The sorting would have to alternate between ascending and descending sorting.
XSLT provides the xsl:param element, which is a child of the
xsl:stylesheet element. XSLTProcessor() provides three
JavaScript methods to interact with these parameters: setParameter,
getParameter and removeParameter. They all take as the
first argument the namespace URI of the xsl:param (Usually the
param will fall in the default namespace, so passing in "null" will suffice.)
The local name of the xsl:param is the second argument.
setParameter requires a third argument - namely the value to which
the parameter will be set.
XSLT:
<xsl:param name="myOrder"
/>
JavaScript:
var sortVal =
xsltProcessor.getParameter(null, "myOrder");
if (sortVal == "" || sortVal
== "descending")
xsltProcessor.setParameter(null, "myOrder",
"ascending");
else
xsltProcessor.setParameter(null, "myOrder",
"descending");
Advanced Example
The advanced example will sort several divs based on their content. The
example allows to sort the content multiple times, alternating between ascending
and descending sorting. The JavaScript only loads the .xsl file the first time,
and sets the xslloaded variable to true once it has
finished loading the file. Using the getParameter method on the
XSLTProcessor object, the code can figure wether to sort by
ascending or descending. It defaults to ascending if the parameter is empty
(first time the sorting happens, as there is no value for it in the XSLT file).
The sorting value is set using setParameter.
The XSLT file has a parameter called myOrder that JavaScript
sets to change the sorting method. The xsl:sort element's
order attribute can access the value of the parameter using
$myOrder. However, the value needs to be an XPATH expression and
not a string, so {$myOrder} is used. Using {} evaluates the content
as an XPath expression.
Once the transformation is complete, the result is appened to the document, as shown in this example.
XHTML Fragment
(example1.html):
<div
id="example">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
<div>8</div>
<div>9</div>
<div>10</div>
</div>
JavaScript
var
xslRef;
var xslloaded = false;
var xsltProcessor = new
XSLTProcessor();
var myDOM;
var xmlRef =
document.implementation.createDocument("", "", null);
function sort()
{
if (!xslloaded){
p = new
XMLHttpRequest();
p.open("GET", "example2.xsl",
false);
p.send(null);
xslRef
=
p.responseXML;
xsltProcessor.importStylesheet(xslRef)
xslloaded
= true;
}
// create a new XML document in
memory
xmlRef = document.implementation.createDocument("", "",
null);
// we want to move a part of the DOM from an HTML
document to an XML document.
// importNode is used to clone the
nodes we want to process via XSLT - true makes it do a deep
clone
var myNode =
document.getElementById("example");
var clonedNode =
xmlRef.importNode(myNode, true);
// after cloning, we
append
xmlRef.appendChild(clonedNode);
// set
the sorting parameter in the XSL file
var sortVal =
xsltProcessor.getParameter(null, "myOrder");
if (sortVal ==
"" || sortVal ==
"descending")
xsltProcessor.setParameter(null,
"myOrder",
"ascending");
else
xsltProcessor.setParameter(null,
"myOrder", "descending");
// initiate the
transformation
var fragment =
xsltProcessor.transformToFragment(xmlRef, document);
// clear
the contents
document.getElementById("example").innerHTML =
"";
myDOM = fragment;
// add the new content
from the
transformation
document.getElementById("example").appendChild(fragment)
}
XSL
Stylesheet (example2.xsl):
<?xml version="1.0"
encoding="UTF-8"?>
<stylesheet version="1.0"
xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
method="html" indent="yes"/>
<xsl:param name="myOrder"
/>
<xsl:template
match="/">
<xsl:apply-templates
select="/div//div">
<xsl:sort
select="." data-type="number"
order="{$myOrder}"/>
</xsl:apply-templates>
<xsl:template>
<xsl:template
match="div">
<xsl:copy-of
select="."/>
<xsl:template>
</xsl:stylesheet>
Interface List
XSLTProcessor
MethodsstyleSheet is the root-node of a
XSLT stylesheet.
source by applying the stylesheet imported
using the importStylesheet() function. The owner document of the resulting
document fragment is the owner node.
source applying the stylesheet given
importing using the importStylesheet() function.
Resources
- XSLT in Netscape Gecko
- Using the Mozilla JavaScript interface to do XSL Transformations at mozilla.org.
- Mozilla.org's XSLT Project Page, which includes a frequently encountered issues section.
- MSDN documentation on IE/XSLT bindings
