Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
![]() |
![]() |
![]() |
|
WebSnap XSLPageProducer
An XML document contains content as well as definition, but little information on how the XML document should be displayed (for example inside a browser).
We can use an XML EXtensible Stylesheet Language (XSL) template or transformation file in order to transform an XML document to another (usually HTML compliant) XML document, which we'll do in this article using the XSLPageProducer component from the WebSnap tab of Delphi 6 or Kylix 2 Enterprise.
WebSnap
Web Server applications can be build in different ways.
Delphi always had the WebBroker Technology, which was extended with InternetExpress in Delphi 5 and with WebSnap in Delphi 6.
The WebSnap tab of the component palette contains the XSLPageProducer component, although I will show in this section that we can even use this component in a "normal" WebBroker environment - without WebSnap.
ClientDataSets
Let's first build a data module that actually contains some XML data packets.
And while we're at it, let's make it a bit more useful than a single data packet - let's make it a master-detail (with customer records and order records in a nested dataset).
I assume you've started Delphi 6 with a Web Server application.
The empty web module is in fact just a special data module, and we'll use it at first as container for two tables.
Since I want Kylix 2 developers to be able to follow us along for a while, we'll be using two TClientDataSet components from the Data Access tab of the component palette.
Name them cdsCustomer and cdsOrders, and assign their filename properties to resp. customer.cds and orders.cds (which can be found in the C:\Program Files\Common Files\Borland Shared\Data directory).
Both datasets have quite a number of fields, but I don't want to see them all, so I right-click on each of the ClientDataSets and only select the few most important fields (that's CustNo, Company, Addr1, Addr2, City and Country for cdsCustomer and OrderNo, CustNo, and ItemsTotal for cdsOrders).
Master Detail
Anyway, In order to create a master-detail relationship between the cdsCustomer and cdsOrders, you must add a TDataSource component, assign its DataSet property to cdsCustomer, and then assign the MasterSource property of cdsOrders to this DataSource component.
Now click on the ellipsis for the MasterFields property of cdsOrders and use the Field Link Designer to make sure that the CustNo fields of both cdsCustomer and cdsOrders are "linked" to create this master-detail relationship.
The next step involves two more components: a TDataSetProvider component, which gets its DataSet property assigned to cdsCustomer, and a TXMLBroker component (from the InternetExpress tab of the Component Palette), which gets its ProviderName property assigned to the DataSetProvider component.
XSLPageProducer
Let's continue with the data module and drop a XSLPageProducer component from the WebSnap tab of the component palette.
The web module should now look as follows:
procedure TDataModule2.WebDispatcher1WebActionItem2Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin Response.Content := XSLPageProducer1.Content end;Note that we can't simple assign the XSLPageProducer component to the Producer property of the WebActionItem (for some reason, it doesn't seem to be endorsed to use the XSLPageProducer inside a "normal" WebBroker application like we're doing right now).
XML and XSL
Anyway, 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):
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:
<?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.
XSL Transformations
After you've enter placed the XSL template inside the XML property or have pointed the FileName property to datapacket.xsl, it's time to compile and deploy the web server application.
The result can be seen below:
Enhanced XSL Template
So far, we've seen the Customer fields (with shifting columns), but nothing (yet) for the Orders nested dataset fields.
To solve both issues, we need to modify the XSL Transformation template.
First thing, we need to explicitly mention all fieldnames for the cdsCustomer, and second, we need to scan for and process the cdsOrders as well.
The new XSL template is defined as follows:
<?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 cols="5" 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> <td valign="top"><xsl:value-of select="@CustNo"/></td> <td valign="top"><xsl:value-of select="@Company"/></td> <td valign="top"><xsl:value-of select="@Addr1"/></td> <td valign="top"><xsl:value-of select="@Addr2"/></td> <td valign="top"><xsl:value-of select="@City"/></td> <td valign="top"><xsl:value-of select="@Country"/></td> <td> <table cols="5" bgcolor="ffffff"> <xsl:apply-templates select="cdsOrders/ROWcdsOrders"/> </table> </td> </tr> </xsl:template> <xsl:template match="cdsOrders/ROWcdsOrders"> <tr> <xsl:for-each select="@*"> <td bgcolor="ffffcc"> <xsl:value-of/> </td> </xsl:for-each> </tr> </xsl:template> </xsl:stylesheet>The new output shows no more shifting of empty columns (the Addr2 field is empty where it should be empty, and the Country field is always listed in the Country column), as well as a nested table for the cdsOrders entries. Note that you can now customise the XSL Template file even further to make the second table a bit nicer (show only the fields you want, include table headers, etc.), but I'm sure you'll get the idea by now.
For more recent information on this topic, check out my Delphi 2010 XML, SOAP & Web Services courseware manual.