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 however are available in Netscape 7.1.
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