Another approach for generating HTML starting from an XML document is the use of the Extensible Stylesheet Language (XSL) or, to be more precise, its XSL Transformations (XSLT) subset. The aim of XSLT is to transform an XML document into another document, generally an XML document. One of the most frequent uses of the technology is to turn an XML document into an XHTML document to be sent to a browser from a web server. Another interesting related technology is XSL-FO (XSL Formatting Objects), which can be used to turn an XML document into a PDF or another formatted document.
An XSLT document is a well-formed XML document. The structure of an XSLT file requires a root node like the following:
<xsl:stylesheet version="1.0" xmlns:xsl="...">
The content of the XSLT file is based on one or more templates (or rules or functions), which will be processed by the engine. Their node is xsl:template, usually with a match attribute. In the simplest case, a template operates on nodes with a given name; you invoke the template by passing to it one or more nodes with an XPath expression:
The starting point for this operation is a template that processes the root node, which can be the only template of the XSLT file. Within templates, you can find any of the other commands, such as the extraction of a value from an XML document (xsl:value-of select), looping statements (xsl:for-each), conditional expressions (xsl:if, xsl:choose), sorting requests (xsl:sort), and numbering requests (xsl:number), just to mention a few common XSLT commands.
XSLT uses other XML technologies, notably XPath to identify portions of documents. XPath defines a set of rules to locate one or more nodes within a document. The rules are based on a path-line structure of the node within the XML tree, so that /books/book identifies any book node under the books document root. XPath uses special symbols to identify nodes:
A star (*) stands for any node; for example, book/* indicates any subnode under a book node.
A dot (.) stands for the current node.
The pipe symbol (|) indicates alternatives, as in book|ebook.
A double slash (//) stands for any path, so that //title indicates all the title nodes, whatever their parent nodes, and books//author indicates any author node under a books node regardless of the nodes in between.
The at sign (@) indicates an attribute instead of a node, as in the hypothetical author/ @lastname.
Square brackets can be used to choose only nodes or attributes having a given value. For example, to select all authors with a given first name attribute, you can use author[@name="marco"].
There are many other cases, but this short introduction to the rules of XPath should get you started and help you understand the following examples. An XSLT document is an XML document that works on the structure of a source XML document and generates in output another XML document, such as an XHTML document you can view in a web browser.
Commonly used XSLT processors include MS-XML, Xalan from the Apache XML project (xml.apache.org), and the Java-based Xt from James Clarke. From Delphi, you can also use the XSLT engine, included in TurboPower's XML Partner Pro (www.turbopower.com).
Let's discuss a couple of examples. As a starting point, you should study XSL by itself, and then focus on its activation from within a Delphi application.
For your initial tests, you can connect an XSL file directly to an XML file. As you load the XML file in Internet Explorer, you will see the resulting XHTML transformation. The connection is indicated in the heading of the XML document with a command like this:
<?xml-stylesheet type="text/xsl" href="sample1embedded.xsl"?>
This is what I've done in the sample1embedded.xml file available in the XslEmbed folder. The related XSL embeds various XSL snippets that I don't have space to discuss in detail. For example, it grabs the entire list of authors or filters a specific group of them with this code:
<h2>All Authors</h2> <ul> <xsl:for-each select="books//author"> <li><xsl:value-of select="."/></li> </xsl:for-each> </ul> <h3>E-Authors</h3> <ul> <xsl:for-each select="books/ebook/author"> <li><xsl:value-of select="."/></li> </xsl:for-each> </ul>
More complex code is used to extract nodes only when a specific value is present in a subnode or attribute, regardless of the higher-level nodes. The following XSL snippet also has an if statement and produces an attribute in the resulting node, as a way to build an href hyperlink in the HTML:
<h3>Marco's works (books + ebooks)</h3> <ul> <xsl:for-each select="books/*[author = 'Cantu']"> <li> <xsl:value-of select="title"/> <xsl:if test="url"> (<a><xsl:attribute name="href"><xsl:value-of select="url"/> </xsl:attribute>Jump to document</a>) </xsl:if> </li> </xsl:for-each> </ul>
Within the code of a program, you can execute the TransformNode method of a DOM node, passing to it another DOM hosting the XSL document. Instead of using this low-level approach, however, you can let WebSnap help you to create an XSL-based example. You can create a new WebSnap application (I've built a CGI program called XslCust in this case) and choose an XSLPageProducer component for its main page, to let Delphi help you begin the application code. Delphi also includes a skeleton XSL file for manipulating a ClientDataSet data packet and adds many new views to the editor. The XSL text replaces the HTML file; the XML Tree page shows the data, if any; the XSL Tree page shows the XSL within the Internet Explorer ActiveX; the HTML Result page shows the code produced by the transformation; and the Preview page shows what a user will see in a browser.
In Delphi 7, the editor provides full-blown code completion for XSLT, which makes editing this code in the editor as powerful as it is in some sophisticated and specific XML editors.
To make this example work, you must provide data to the XSLPageProducer component via its XMLData property. This property can be hooked up to an XMLDocument or directly to an XMLBroker component, as I've done in this case. The broker takes its data from a provider connected to a local table attached to the classic Customer.cds component table of the classic DBDEMOS. The effect is that, with the following Delphi-generated XSL, you get (even at design time) the output of shown in Figure 22.12:
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <body> <xsl:apply-templates/> </body> </html> </xsl:template> <xsl:template match="DATAPACKET"> <table border="1"> <xsl:apply-templates select="METADATA/FIELDS"/> <xsl:apply-templates select="ROWDATA/ROW"/> </table> </xsl:template> <xsl:template match="FIELDS"> <tr> <xsl:apply-templates/> </tr> </xsl:template> <xsl:template match="FIELD"> <th> <xsl:value-of select="@attrname"/> </th> </xsl:template> <xsl:template match="ROWDATA/ROW"> <xsl:variable name="fieldDefs" select="//METADATA/FIELDS"/> <xsl:variable name="currentRow" select="current()"/> <tr> <xsl:for-each select="$fieldDefs/FIELD"> <td> <xsl:value-of select="$currentRow/@*[name()=current()/@attrname]"/><br/> </td> </xsl:for-each> </tr> </xsl:template> </xsl:stylesheet>
The standard XSL template has been extended since Delphi 6, because the original versions didn't account for null fields omitted from the XML data packet. I presented several extensions to the original XSL code at the 2002 Borland Conference, and some of my suggestions have been incorporated in the template.
This code generates an HTML table consisting of the expansion of field metadata and row data. The fields are used to generate the table heading, with a <th> cell for each entry in a single row. The row data is used to fill in the other rows of the table. Taking the value of each attribute (select="@*") wouldn't be enough, because an attribute might be missing. For this reason, the list of fields and the current row are saved in two variables; then, for each field, the XSL code extracts the value of a row item having an attribute name (@*[name()=...) corresponding to the name of the current field stored in its attrname attribute (@attrname). This code is far from simple, but it is a compact and portable way to examine different portions of an XML document at the same time.
Using the XSLPageProducer can be handy, but generating multiple pages based on the same data just to handle different possible XSL styles with WebSnap isn't the best approach. I've built a plain CGI application called CdsXslt that can transform a ClientDataSet data packet into different types of HTML, depending on the name of the XSL file passed as a parameter. The advantage is that I can modify the existing XSL files and add new XSL files to the system without having to recompile the program.
To obtain the XSL transformation, the program loads both the XML and the XSL files into two XMLDocument components called xmlDom and XslDom. Then it invokes the transformNode method of the XML document, passing the XSL document as a parameter and filling in a third XMLDocument component called HtmlDom:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var xslfile, xslfolder: string; attr: IDOMAttr; begin // open the client dataset and load its XML in a DOM ClientDataSet1.Open; XmlDom.Xml.Text := ClientDataSet1.XMLData; XmlDom.Active := True; // load the requested xsl file xslfile := Request.QueryFields.Values ['style']; if xslfile = '' then xslfile := 'customer.xsl'; xslfolder := ExtractFilePath (ParamStr (0)) + 'xsl\'; if FileExists (xslfolder + xslfile) then xslDom.LoadFromFile (xslfolder + xslfile) else raise Exception.Create('Missing file: ' + xslfolder + xslfile); XSLDom.Active := True; if xslfile = 'single.xsl' then begin attr := xslDom.DOMDocument.createAttribute('select'); attr.value := '//ROW[@CustNo="' + Request.QueryFields.Values ['id'] + '"]'; xslDom.DOMDocument.getElementsByTagName ('xsl:apply-templates'). item.attributes.setNamedItem(attr); end; // do the transformation HTMLDom.Active := True; xmlDom.DocumentElement.transformNode (xslDom.DocumentElement, HTMLDom); Response.Content := HTMLDom.XML.Text; end;
The code uses the DOM to modify the XSL document for displaying a single record, adding the XPath statement for selecting the record indicated by the id query field. This id is added to the hyperlink by the XSL with the list of records, but I'll skip listing more XSL files. They are available for study in the XSL subfolder of this example's folder.
To run this program, deploy the XSL files in a folder called XSL under the one where the script is located. You can find the demo files in the XSL subfolder of the scripts folder of this chapter. To deploy these files in a different location, change the code above that extracts the XSL folder name from the program name available in the first common line parameter (as the global Application object defined in the Forms unit is not accessible in a CGI application).