XSLT brings XML, schemas, and XPath together in a declarative programming language. It is used to query and transform XML (and, with XSLT3, JSON) data, enabling one to express data in new ways, or to create new data based on the content or structure of existing data.
There are, of course, many ways to retrieve data and to convert it from one form or format to another. XSLT has several important advantages over all other programming languages when it comes to XML data transformation:
- It has been designed to work with enterprise XML technologies.
- It makes most data conversion tasks very easy to understand and implement.
- Solutions written using XSLT can be proven to be correct.
When dealing with extremely large data sets or documents, with data content or data structures that must be verifiably correct, or when data must be retained for long periods of time and re-used in new contexts, XML and XSLT are the best tools for the job.
XSLT (Extensible Stylesheet Language Transformation) is a programming language that specializes in the conversion of data structures from one form to another. It is tightly integrated with XML (Extensible Markup Language), XPath (XML Path Language), and XSD (XML Schema Definition), making it by far the best solution for the transformation of XML data objects and documents.
The general gist of XSLT is that after an XML document is parsed, its tags and the content they contain are fed into the XSLT transformation program one-by-one in document order. The transformation program comprises a set of pattern matches, each ready to “capture” incoming tags and content for specific processing. These pattern matches are used in the definition of “templates,” each template performing operations specific to the matched tag.
A template might simply copy the tag to output; it might emit a new tag, effectively renaming the tag that was matched; it might perform calculations based on the attributes and content of the matched tag or other tags, creating new data; or might perform any number of other operations related to restructuring, reordering, or revising the tag and the data structures and content it contains.
The template can also apply an XPath rule to select tags or content for further processing: the processor will receive this set of data and feed it back into the transformation program verbatim, where pattern-matching will again determine which operations are performed. If the template does not request further application of template rules, the template is exited and the processor feeds the next in-order tag into the transformation program.
It is worth noting that if a tag is not matched by an XPath pattern, the transformation may choose to copy only its data content; copy the tag and its child tags verbatim to output; copied the tag itself verbatim but send its child tags and content back into the transformation program for further pattern-matching; skip the tag and its content entirely, removing it all from the output; remove the tag itself from output but send its child tags and content back in for further pattern-matching; or cause a failure that terminates further processing. Due to this flexibility, transformations can avoid over-specifying pattern matches, such that unrecognized tags and content are handled appropriately with no further intervention required by the transformation program.
Another noteworthy feature of XSLT programming is that data is fed into the transformation by the processor, instead of the program needing to fetch the data. That is to say, XSLT processors “push” data into the transformation, a concept that is foreign to most programming languages, but one that immensely reduces the amount of code required to process a document.
At the same time, it is common to use “pull” processing after a pattern match is used to select a template of transformation operations. In this scenario, the template selects specific tags using an XPath expression and sends them into the transformation program immediately, forcing them to be processed before the next in-order tag.
Of course, XSLT and XPath provide a full complement of powerful instructions provides the programmer with the ability to iterate over structures and data, test and branch, create and call functions, sort, convert, and analyse data, merge documents, and so on. The toolset is exhaustive and complete, built upon decades of experience in performing data transformations for complex applications in myriad data domains.
For those programmers with access to an XSLT processor that supports schemas, data type-checking is available. By specifying the type of data expected by the parameters for a function, or returned by a function or template, many programming errors can be detected while writing the programming, eliminating entire classes of run-time errors and dramatically reducing development costs due to defects.
Virtually none of this built-in support for powerful, expressive XPath pattern-matching and XSD type checking is available in common programming languages. Those same common languages rarely support push processing, requiring programmers to explicitly step through the data structure, and consequently creating fragile solutions that break down when presented with unexpected data. These languages are also do not “understand” the XML specification and require extra care to emit compliant XML documents and data structures.
In all cases, XML processing code that is not written using the XSLT programming language is more difficult to understand, more difficult to prove correct, and burdened by significant extra code that is simply not required in XSLT to achieve the same or better results.
Why XSLT?
Because XSLT is a highly-tuned declarative programming language tightly coupled with XPath and its large library of data manipulation functions.
As a result, XSLT eliminates an immense amount of labour when creating transformative and extractive tasks for XML data structures. With the addition of schemas, XSLT also eliminates entire classes of programming errors by catching mistakes in the code while it is being written, resulting in order of magnitude cost savings.
Comparing XSLT and Conventional Programming Languages
Following is a real-world demonstration of the elegance and understandability of XSLT versus Java when processing XML files.
This first example uses a Java API that understands the “Document Object Model,” a language-neutral interface that enables programs to access HTML and XML documents. This API is intended to make it easier to reason about and write programs that manipulate XML data.
Using the Java DOM to Extract the First Recipe
// From http://cs.au.dk/~amoeller/XML/programming/domexample.html
import java.io.*;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.*;
public class FirstRecipeDOM {
public static void main(String[] args) {
try {
DOMParser p = new DOMParser();
p.parse(args[0]);
Document doc = p.getDocument();
Node n = doc.getDocumentElement().getFirstChild();
while (n!=null && !n.getNodeName().equals("recipe"))
n = n.getNextSibling();
PrintStream out = System.out;
out.println("<?xml version=\"1.0\"?>");
out.println("<collection>");
if (n!=null)
print(n, out);
out.println("</collection>");
} catch (Exception e) {e.printStackTrace();}
}
static void print(Node node, PrintStream out) {
int type = node.getNodeType();
switch (type) {
case Node.ELEMENT_NODE:
out.print("<" + node.getNodeName());
NamedNodeMap attrs = node.getAttributes();
int len = attrs.getLength();
for (int i=0; i<len; i++) {
Attr attr = (Attr)attrs.item(i);
out.print(" " + attr.getNodeName() + "=\"" +
escapeXML(attr.getNodeValue()) + "\"");
}
out.print('>');
NodeList children = node.getChildNodes();
len = children.getLength();
for (int i=0; i<len; i++)
print(children.item(i), out);
out.print("</" + node.getNodeName() + ">");
break;
case Node.ENTITY_REFERENCE_NODE:
out.print("&" + node.getNodeName() + ";");
break;
case Node.CDATA_SECTION_NODE:
out.print("<![CDATA[" + node.getNodeValue() + "]]>");
break;
case Node.TEXT_NODE:
out.print(escapeXML(node.getNodeValue()));
break;
case Node.PROCESSING_INSTRUCTION_NODE:
out.print("<?" + node.getNodeName());
String data = node.getNodeValue();
if (data!=null && data.length()>0)
out.print(" " + data);
out.println("?>");
break;
}
}
static String escapeXML(String s) {
StringBuffer str = new StringBuffer();
int len = (s != null) ? s.length() : 0;
for (int i=0; i<len; i++) {
char ch = s.charAt(i);
switch (ch) {
case '<': str.append("<"); break;
case '>': str.append(">"); break;
case '&': str.append("&"); break;
case '"': str.append("""); break;
case '\'': str.append("'"); break;
default: str.append(ch);
}
}
return str.toString();
}
}
The complexity, verbosity, and fragility of the DOM program is common to all programming languages that lack excellent XML integration.
The second example uses the much more advanced JDOM (Java DOM) API. This API provides a deeper level of integration, with compliant XML parsing, support for traversing tree structures, and automatically generating conformant XML file output. Again, this API is intended to make it easier to reason about and write programs that manipulate XML data; to be fair, in comparison to the DOM API it succeeds in its mission:
Using the Java JDOM to Extract the First Recipe
// From http://cs.au.dk/~amoeller/XML/programming/jdomexample.html
import java.io.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
public class FirstRecipeJDOM {
public static void main(String[] args) {
try {
Document d = new SAXBuilder().build(new File(args[0]));
Namespace ns = Namespace.getNamespace("http://recipes.org");
Element e = new Element("collection");
e.addContent(d.getRootElement().getChild("recipe", ns).detach());
Document n = new Document(e);
new XMLOutputter().output(n, System.out);
} catch (Exception e) {e.printStackTrace();}
}
}
But despite the improved support for XML processing and its far more succinct expressions, the JDOM example is still complex and difficult to understand. There is a lot going on in there, with a shocking number of variables and instructions required to even get to the point where a recipe tag can be identified for processing.
Contrast the previous examples with the XSLT solution:
An XSLT Program to Extract the First Recipe
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="shallow-skip" /> <!-- 1 -->
<xsl:template match="*:recipe[1]"> <!-- 2 -->
<xsl:copy-of select="self::*" /> <!-- 3 -->
</xsl:template>
</xsl:stylesheet>
The code is virtually self-explanatory. It is declarative: there is no code required to read the file, create a new document, initialize variables, or otherwise control every step of the process. The processor simply feeds the data source into the transformation, which has two simple rules and one operation:
- Skip any tags it doesn’t recognize (but process the unrecognized tag’s children).
- Match the first recipe tag that comes along.
- Copy it to output.
This code is easier to prove to function correctly, easier to maintain, and easier to extend.
Understanding the Difference between XSLT and Conventional Languages
A key strength of XSLT is that it has its origins in formal systems of mathematical logic, the foundation upon which proofs of correctness are built.
Unfortunately, most programmers are unfamiliar with the programming paradigm that underlies the design of XSLT and, as a result, are frustrated and angry when they can not easily transfer their old skills to the new language. They are used to maintaining a sense of program state, of using variables, of having complete control over the order of operations, and of having to manage every minute detail of computation.
To understand the advantage that XSLT provides to programmers working with XML data, it is necessary to understand that in computing science there are two fundamental approaches to solving computational problems: imperative programming and declarative programming. The differences between these paradigms have consequences for both understanding how a computation solves a problem, and whether one can state that the solution is correct.
Most common programming languages — Java, C/C++, Visual Basic — are imperative languages: languages that describe how the program operates and use commands that compute, record, and change the state of the program. As a result, imperative languages cause side effects: data changed by one part of the program may cause a different (and potentially unrelated) part of the code to behave differently. This can make understanding a program difficult and can render it impossible to authoritatively state that the program is correct.
By contrast, declarative programming languages — SQL, XSLT, Haskell, OCaml — describe what a computation is to accomplish without explicitly stating the required sequence of steps and without causing side effects. As a result, declarative languages are both easier to understand and, most importantly, provably correct.
As a demonstration of the difference between imperative and declarative programming, consider the problem of computing sales by territory, given an appropriate data source ds.
Imperative Computation of Sales vs. Territory
In this imperative version, the program must explicitly initialize variables, configure a loop, choose a computation path, and continuously update both the state of the summation variables and the index variable. The program describes in painful detail how every step of every operation will be performed by the computer. Furthermore, it has the side-effect of creating an index variable that, after the loop has completed, is left “dangling” in the code, where if it were re-used without proper re-initialization, could affect a future computation in a way that is exceedingly difficult to diagnose.
n = 1
income.cda = income.usa = 0
while n <= length(ds) {
if ds[n].territory = 'CDA'
sales.cda = sales.cda + ds[n].sales
elif dn[n].territory = 'USA'
sales.usa = sales.usa + ds[n].sales
n = n + 1
}
print "CDA Sales: " + sales.cda
print "US Sales: " + sales.usa
Declarative Computation of Sales vs. Territory
By comparison, the declarative version simply describes the desired result and leaves it to the programming language to do the work required to perform the computation. The program is much easier to understand and, just as importantly, has no side effects.
print "CDA Sales: " + sum(ds.sales where ds.territory == 'CDA')
print "US Sales: " + sum(ds.sales where ds.territory == 'USA')
A quick review of the imperative Java code and declarative XSLT code in the previous section, Comparing, will contrast and emphasize the greater expressivity and increased comprehensibility of declarative programming over imperative programming.
XML Datasource for “Comparing XSLT and Conventional Programming Languages”
For reference, here is the recipe XML datasource:
Recipe Collection XML Datasource
<collection xmlns="http://recipes.org">
<description>Some recipes, from http://cs.au.dk/~amoeller/XML/xml/recipes.xml.</description>
<recipe>
<title>Beef Parmesan with Garlic Angel Hair Pasta</title>
<ingredient name="beef cube steak" amount="1.5" unit="pound" />
<ingredient name="onion, sliced into thin rings" amount="1" />
<ingredient name="green bell pepper, sliced in rings" amount="1" />
<ingredient name="Italian seasoned bread crumbs" amount="1" unit="cup" />
<ingredient name="grated Parmesan cheese" amount="0.5" unit="cup" />
<ingredient name="olive oil" amount="2" unit="tablespoon" />
<ingredient name="spaghetti sauce" amount="1" unit="jar" />
<ingredient name="shredded mozzarella cheese" amount="0.5" unit="cup" />
<ingredient name="angel hair pasta" amount="12" unit="ounce" />
<ingredient name="minced garlic" amount="2" unit="teaspoon" />
<ingredient name="butter" amount="0.25" unit="cup" />
<preparation>
<step>Preheat oven to 350 degrees F (175 degrees C).</step>
<step>Cut cube steak into serving size pieces. Coat meat with the bread crumbs and parmesan cheese. Heat olive oil in a large frying pan, and saute 1 teaspoon of the garlic for 3 minutes. Quick fry (brown quickly on both sides) meat. Place meat in a casserole baking dish, slightly overlapping edges. Place onion rings and peppers on top of meat, and pour marinara sauce over all.</step>
<step>Bake at 350 degrees F (175 degrees C) for 30 to 45 minutes, depending on the thickness of the meat. Sprinkle mozzarella over meat and leave in the oven till bubbly.</step>
<step>Boil pasta al dente. Drain, and toss in butter and 1 teaspoon garlic. For a stronger garlic taste, season with garlic powder. Top with grated parmesan and parsley for color. Serve meat and sauce atop a mound of pasta!</step>
</preparation>
<comment>Make the meat ahead of time, and refrigerate over night, the acid in the tomato sauce will tenderize the meat even more. If you do this, save the mozzarella till the last minute.</comment>
</recipe>
<recipe>
<title>Ricotta Pie</title>
<ingredient name="filling">
<ingredient name="ricotta cheese" amount="3" unit="pound" />
<ingredient name="eggs" amount="12" />
<ingredient name="white sugar" amount="2" unit="cup" />
<ingredient name="vanilla extract" amount="2" unit="teaspoon" />
<ingredient name="semisweet chocolate chips" amount="0.25" unit="cup" />
<preparation>
<step>Beat the 12 eggs, 2 cups sugar and vanilla extract together. Stir in the ricotta cheese and the chocolate chips. Set aside.</step>
</preparation>
</ingredient>
<ingredient name="dough">
<ingredient name="flour" amount="4" unit="cup" />
<ingredient name="baking powder" amount="5" unit="teaspoon" />
<ingredient name="white sugar" amount="1" unit="cup" />
<ingredient name="shortening" amount="0.5" unit="cup" />
<ingredient name="eggs, lightly beaten" amount="4" />
<ingredient name="vanilla extract" amount="1" unit="teaspoon" />
<preparation>
<step>Combine the flour, baking powder, and 1 cup of the sugar together. Cut in the shortening and mix until the mixture resembles coarse crumbs. Mix in 4 of the eggs and 1 teaspoon of the vanilla. Divide dough into 4 balls and chill (if needed).</step>
</preparation>
</ingredient>
<ingredient name="milk" amount="*" />
<preparation>
<step>Preheat oven to 325 degrees F (165 degrees C). Grease two deep dish pie plates.</step>
<step>Roll out 2 of the balls to fit into the pie pans. Do not make the crust too thick as it will expand during cooking and get too thick. Do not flute the edges of the dough. Roll out the other 2 balls of dough and cut each into 8 narrow strips for the top of the crust. Alternately you can use cookie cutters and place the cutouts on the top of the pies.</step>
<step>Pour the filling evenly into the pie crusts. Top each pie with 8 narrow strips of dough or cookie cut-outs. Brush top of pie with milk for shine. Place foil on the edge of crust.</step>
<step>Bake at 325 degrees F (165 degrees C) for 20 to 30 minutes then remove foil. Continue to bake for another 25 or 30 minutes or until a knife inserted in the center comes out clean.</step>
</preparation>
</recipe>
</collection>