CSDN博客

img lwjghhh

Introduction to XSTL with Delphi

发表于2004/10/25 10:01:00  1816人阅读

分类: DELPHI

Introduction to XSTL with Delphi
XSLT stands for eXtensible Stylesheet Language (XSL) Template, and is generally used to transform an XML document to another XML document (for example a HTML compliant XML document).
In this article, I will give an introduction to the basic capabilities of XSLT, as well as the reasons why XSLT can be beneficial to Delphi developers. We'll start of with some simple examples of using XSTL to transform XML documents to other XML documents. We then move on to the XSLT support in Delphi in the form of the TXSLPageProducer component (found on the WebSnap tab, but actually not related to WebSnap at all), which can be used to convert any XML data packet or XML document (using XSL Transformation rules), but is not always clear in its use.
I will also show where to get a few XSLT examples that are also a bit "hidden" in Delphi 6 and 7 Enterprise.

Finally, I will show that XSLT on the .NET Framework can be done using the XSL classes from the System.Xml.Xsl namespace, which contains the class XslTransform for this purpose.

What is XSLT?
XSLT stands for XSL Transformations, where XSL stands for eXtensible Stylesheet Language. It is primarily meant to transform XML documents to something else (another XML document, a HTML document or even a plain text document).
Using Delphi (and Delphi for .NET), we can load XML documents, read them, work with them, but it's harder for perform the operations on XML documents that we can do with a few lines of XSTL. Besides, we can use Delphi to build an XSLT processor, so still keep the "main" application in Delphi, while we're using the XSTL capabilities of Win32 or .NET.

How can you "run" XSLT?
There are several ways to execute or apply an XSLT on a XML document. The easiest way is to download the msxsl.exe command-line tool from Microsoft at http://msdn.microsoft.com/xml. msxsl.exe is a command-line XSLT processor, of which I use version 3. The instructions for running MSXSL are as follows:

  Usage: MSXSL source stylesheet [options] [param=value...] [xmlns:prefix=uri...]

  Options:
    -?            Show this message
    -o filename   Write output to named file
    -m startMode  Start the transform in this mode
    -xw           Strip non-significant whitespace from source and stylesheet
    -xe           Do not resolve external definitions during parse phase
    -v            Validate documents during parse phase
    -t            Show load and transformation timings
    -pi           Get stylesheet URL from xml-stylesheet PI in source document
    -u version    Use a specific version of MSXML: '2.6', '3.0', '4.0'
    -             Dash used as source argument loads XML from stdin
    -             Dash used as stylesheet argument loads XSL from stdin
This will work fine as a blackbox for our first examples. However, later in this article we'll also build our own XSLT processors with both Delphi 7 (Enterprise) and Delphi for .NET (using the classes from the System.XML.XSL namespace).

Some examples of XSLT
XSLT itself is an XML document with a special syntax that we have to read to understand what it's doing. They start with the following line (make no typing mistakes):

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
And obviously end with:
 </xsl:stylesheet>
If you apply an otherwise empty XSLT document to an XML document, then no transformation are performed which means that all XML tags will be removed and only the text between the tags remains.

A second XSLT example that ignores the XML document but simply produces some output is the following:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:template match='/'>
   <clinics>
    <clinic name="Delphi 8 for .NET" date="27 May 2004"/>
    <clinic name="Delphi 8 for .NET and ASP.NET" date="24 June 2004"/>
   </clinics>
  </xsl:template>
 </xsl:stylesheet>
This one uses an xsl:output statement that specifies that the output is of type XML (version 1.0, UTF-8) and that indentation should be done.
The xsl:template element matches the root of the XML document, where the transformation starts, and replaces the root with the literal output that I've specified. No other transformation are done.
Applying this XSLT transformation on any XML document (doesn't matter which one) produces the following XML output file:
 <?xml version="1.0" encoding="UTF-8"?>
 <clinics>
 <clinic name="Delphi 8 for .NET" date="27 May 2004" />
 <clinic name="Delphi 8 for .NET and ASP.NET" date="24 June 2004" />
 </clinics>
Now, suppose we want to change the attribute or a node from an XML document. We must then use another match expression, where a node is specified by the fully qualified name (starting from the root), and an attribute has an @ character before the name.

The first example is to copy an XML document to itself - including the XML tags (each node to itself), which is defined as follows (note that I've ommited the <xsl:output ...> statement to make sure the resulting XML document is the same format as the input XML document):

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="node() | @*">
   <xsl:copy>
    <xsl:apply-templates select="@* |node()"/>
   </xsl:copy>
  </xsl:template>
 </xsl:stylesheet>
The xsl:template element now matches the XPath expression node() which means each node in the XML document. The xsl:apply-templates is used to replace the @* with the XPath node() again which results in a literal copy of each input node to itself as output note.
An example that extends this functionality and turns each attribute into a child node (element) is defined as follows:
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

 <xsl:template match="node() | @*">
  <xsl:copy>
   <xsl:apply-templates select="node() | @*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="@*">
  <xsl:element name="{local-name(.)}">
   <xsl:value-of select="."/>
  </xsl:element>
 </xsl:template>

 </xsl:stylesheet>
The transformation is performed in two steps: first of all, the node() are replaced by themselves, and for attributes (@*) we continue to the next step (the second template match). In this second step, the attributes attributes (@*) are replaced by an element of the same name (local-name(.)) with as value, the value of the attribute itself.

Of course, we can also covert a document the other way around: turn all sub-elements of a specific node into attributes instead. As an example, I've once written an application that creates a disk.xml file (containing the directories and files on my local disk): an XML file with sub-elements date and size for <file> nodes. Instead of using the elements, I would like to change this big XML file and turn the child-elements into attributes. This can be done with the following XSTL:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

 <xsl:template match="node() | @*">
  <xsl:copy>
   <xsl:apply-templates select="node() | @*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="file">
  <xsl:copy>
   <xsl:copy-of select="@*"/>
   <xsl:for-each select="*">
    <xsl:attribute name="{local-name(.)}">
     <xsl:value-of select="."/>
    </xsl:attribute>
   </xsl:for-each>
  </xsl:copy>
 </xsl:template>

 </xsl:stylesheet>
This consists of a number of steps. First of all, we start with the transformation of all nodes and attributes to themselves. Them the elements "file" are handled separately. We first copy the attributes of the file nodes themselves, and then for all sub-elements, turn them into attributes themselves. A problem could occur if this results into attributes with the same name (i.e. an element that also occured as attribute), but I leave it as excercise for the reader to decide how to rename the attributes in that case.

Default Templates
XSLT needs matching templates in order to perform the transformation. However, sometimes, no matching template can be found. For those casee, XSTL defines a number of default templates that will be used if no matching template can be found. The first default template is defined as follows:

 <xsl:template match="*|/">
   <xsl:apply-template/>
 </xsl:template>
This template matches all elements and the root, and continues the transformation process by re-applying the templates for the child elements (but not the attributes) of the current node. To ensure that all nodes are transformed.
Another default template is defined as follows:
 <xsl:template match="text()|@*">
   <xsl:value-of select="."/>
 </xsl:template>
This template matches the text nodes and all attributes from the elements and sends the values of these elements to the output. This is usually not what you would want, so you should try to transform text notes and attributes values yourself.

Our own XSLT Processor
There are several ways in which we can build our own XSLT Processor. I will start with Delphi 7 Enterprise, then move to Delphi Professional and show how we can perform the XSLT transformations by ourselves.
In Delphi 7 Enterprise, we can find a rather strange component called the TXslPageProducer - as part of WebSnap (although it's not really tightly integrated with WebSnap to be honest). This component works like a regular PageProducer in that we can call the "Content" property to fire the method (the GetContent function) that will perform the XSLT transformation. The only problem is that the properties that we can use to "feed" this transformation are not always clear...

XML and XSL
The XSLPageProducer component can be used to turn an XML document (or XML string) into another XML string, using an EXtensible Stylesheet Language (XSL) template. Note that the Delphi 6 on-line help still calls it the TXMLPageProducer component instead of the TXSLPageProducer component. The XSLPageProducer component has a number of properties that can easily be confused with each other (like the FileName, XML and XMLData properties, as I will make clear in a moment).
First of all, the only way to specify the input XML content is by using the XMLData property. You cannot use the XML property, since this is used to specify the XSL template (just like the FileName is used to point to an external XSL template file). I wonder why the XML property isn't called XSL, but I guess that's the same reason why the on-line help refers to it as the TXMLPageProducer instead the TXSLPageProducer component - it may have been renamed late in the development process of Delphi 6 (and Borland may have forgot the XML property - or would break existing code and examples by changing it).

XMLData property
Anyway, the XMLData property can point to any component that implements the IXMLDocument interface (like the TXMLDocument component) or the IGetXMLStream interface (like the TXMLBroker component). Since we're already using the data module with a TXMLBroker component, we can use that one. Just click on the XMLData property of the XSLPageProducer component and select XMLBroker1 to connect it to the XMLBroker component (which in turn connects to the XMLTransformProvider component).

FileName property
The FileName property points to an external XSL template file, which should contain XSL Transformations (XSLT) using XPath and XPointer (a bit more about that in a moment). It may be handy to use the FileName property, but you can also use the XML property to make sure the XSL template is embedded within the XSLPageProducer component (and hence the web server application itself). Unfortunately, when you click on the ellipsis to start the property editor for the FileName property, you get a File Open dialog that by default starts to look for XML files. You have to open the "Files of type" combobox in order to specify that you're looking for XSL files instead.

XML property
The XML property is yet another "strange one". You may think that this one can be used as alternative for the XMLData property, but that's not the case. The XML property is actually an alternative for the FileName property, and should in that function contain the XSL template, and not the XML data (so the "XML" property is a bit misleading). Warning: if you click on the XML property and enter some XSL Transformations, be aware that you'll clear the FileName property! It appears that the FileName and XML property are mutual exclusive. If you set a value to one, you clear the other. And this can be especially painful if you enter a new FileName property and accidentally clear the XML property (containing a potentially long list of XSL Transformations). In our example, I'll fill the XML property with the following set of XSL Transformations that can be generated by Delphi itself:

  <?xml version="1.0"?>
  <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
    <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">
      <tr>
      <xsl:for-each select="@*">
        <td>
        <xsl:value-of/>
        </td>
      </xsl:for-each>
      </tr>
    </xsl:template>

  </xsl:stylesheet>
This XSL Transformation template above is especially designed to handle data packets that are coming from the XMLBroker component, and can originally be found in the XSLProducer WebSnap demo directory of Delphi 6 itself. Tip: if for some reason you can't locate this example, then you can use Delphi 6 to produce it for you as example template. Do File | New | Other, go to the WebSnap tab of the Object Repository and double-click on the WebSnap Page Module icon. In the dialog that follows, select XSLPageProducer as Producer Type. Now, make sure the XSL "New File" option is checked, and select the type of template from the combobox (standard, blank or data packet). For our example, select "data packet". Ignore all other options on the dialog, because we are only interested in the generated XSL template file. Click on OK to generate a new WebSnap Page Module. Click on the new Uni1.xsl tab, copy the contents and paste it inside the XML property of the XSLPageProducer. Alternately, you may save the contents in file datapacket.xsl and assign the FileName property to datapacket.xsl (note that the property editor starts to look for .xml files first, you need to make sure to put the .xsl file inside it). Make sure that you don't save the Unit1.pas or .dfm itself, since we now need to remove the useless WebSnap Page Module from the WebBroker project (do View | Project Manager, and remove Unit1 from the WebBroker project). And finally, in case you're interested, the standard XSL template produces the following three lines (ready for you to enter your own custom XSL):
  <?xml version="1.0"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl">
  </xsl:stylesheet>
and the blank XSL template is indeed just an empty file.
The best thing, however, is that we can use the XSLPageProducer in a "normal" WebBroker environment, without the need for WebSnap Page Modules. The XSL Template language using XPath and XPointer can be used to specify the transformation from XML to HTML-compliant XML that can be shown in the browser.

XSLT Processor
In order to build our own XSLT Processor, we need an XMLDocument component (filled with an XML Document), as well as a place to store the XSLT templates, and a place to store the resulting transformation. Figure 1 shows a Delphi form at design-time with three memo fields: MemoXML, MemoXSLT and MemoXSL (for the result) as well as a XSMLDocument and XSLPageProducer component to perform the work.

The few steps of code that are needed to perform the XSLT transformation are as follows:

 try
   XMLDocument1.Active := False; // just in case
   XSLPageProducer1.FileName := FileNameXSLT;
   XMLDocument1.FileName := FileNameXML;
   XSLPageProducer1.XMLData := XMLDocument1;
   XMLDocument1.Active := True;
   MemoXSL.Text := XSLPageProducer1.Content; // Transform!!
   XMLDocument1.Active := False;
 except
   on E: Exception do
     MemoXSL.Text := E.Message
 end;

Alternative Approach
If you don't have Delphi 6 or 7 Enterprise, then you don't have WebSnap with the XSLPageProducer component. Fortunately, there's still a way to perform the same XSLT Transformation using the transformNode method of the IXMLDOMDocument interface (from the MSXML unit):

 procedure TForm1.XSLT1Click(Sender: TObject);

   function LoadXmlDoc(const FileName: WideString): IXMLDOMDocument;
   begin
     Result := CoDomDocument.Create();
     Result.async := False;
     Result.load(FileName);
 //  OleCheck(Result.parseError.errorCode);
   end;

 var
   doc, xsl: IXMLDOMDocument;
 begin
   try
    doc := LoadXmlDoc(XML);
    xsl := LoadXmlDoc(XSLT);
    MemoXSL.Text := doc.transformNode(xsl)
   except
     on E: Exception do
       MemoXSL.Text := E.Message
   end
 end;
As final example, remember the input.xml file (including attributes) that was transformed to output.xml (attributes changed into elements) by the Attr2Child.xslt transformation, which is shown in action in the figure below:

XSLT on .NET
We can also perform XSLT transformations (to work on XML documents) on the .NET Framework using the System.Xml.Xsl and System.Xml.Path namespaces from the System.Xml assembly.

 program XSLT;
 {$APPTYPE CONSOLE}
 uses
   System.IO,
   System.Xml,
   System.Xml.Xsl,
   System.Xml.XPath;
 var
   XslTrans: XslTransform;
   XmlDoc: XmlDocument;
   XmlOut: XmlReader;
   XmlStream: FileStream;
 begin
   try
     XslTrans := XslTransform.Create;
     XslTrans.Load('attr2child.xslt');
     XmlDoc := XmlDocument.Create;
     XmlDoc.Load('input.xml');
{    XmlOut := XslTrans.Transform(XmlDoc, nil);
     XmlOut.Free;
}    XmlStream := System.IO.FileStream.Create('output.xml', FileMode.Create);
     XslTrans.Transform(XmlDoc, nil, XmlStream);
     XmlStream.Flush;
   except
     on E: Exception do
       writeln(E.ClassName, ': ', E.Message)
   end;
   XmlStream.Free;
   XmlDoc.Free;
   XslTrans.Free;
 end.
This simple example shows that we can use the Transform method of the XslTransform class to transform a XmlDocument and produce a XmlReader (or directly write the output to an XML FileStream). This code should be enough basis to get you started to write more extensive XSTL processing applications for .NET.

Summary
In this paper, I have give an introduction to the basic capabilities of XSLT, although everyone should conclude for his or herself if XSLT can be beneficial. I don't use it very often, but when I do, I'm glad it's around (and support by Delphi).
I've also shown how we can use the msxsl command-line XSLT processor, and how we can build at least three different XSLT processors using Delphi 7 and the Delphi for .NET preview command-line compiler or Delphi 8 for .NET.

阅读全文
0 0

相关文章推荐

img
取 消
img