0
votes

I have an XML file and an XSLT and I want to use the XSLT to trasform the XML into an HTML string which could be loaded into a TWebBrowser.

For my tests, I'm using these example files.

XML File:

<?xml version="1.0" encoding="UTF-8"?>
<breakfast_menu>

<food>
<name>Belgian Waffles</name>
<price>$5.95</price>
<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
<calories>650</calories>
</food>

<food>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<description>Light Belgian waffles covered with strawberries and whipped cream</description>
<calories>900</calories>
</food>

<food>
<name>Berry-Berry Belgian Waffles</name>
<price>$8.95</price>
<description>Light Belgian waffles covered with an assortment of fresh berries and whipped cream</description>
<calories>900</calories>
</food>

<food>
<name>French Toast</name>
<price>$4.50</price>
<description>Thick slices made from our homemade sourdough bread</description>
<calories>600</calories>
</food>

<food>
<name>Homestyle Breakfast</name>
<price>$6.95</price>
<description>Two eggs, bacon or sausage, toast, and our ever-popular hash browns</description>
<calories>950</calories>
</food>

</breakfast_menu>

XSLT File:

<?xml version="1.0" encoding="UTF-8"?>
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:for-each select="breakfast_menu/food">
  <div style="background-color:teal;color:white;padding:4px">
    <span style="font-weight:bold"><xsl:value-of select="name"/> - </span>
    <xsl:value-of select="price"/>
    </div>
  <div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
    <p>
    <xsl:value-of select="description"/>
    <span style="font-style:italic"> (<xsl:value-of select="calories"/> calories per serving)</span>
    </p>
  </div>
</xsl:for-each>
</body>
</html>

Attempt 1:

I've found this solution and tried the function:

Uses
  XMLDoc, XMLIntf;

function Transform(XMLContent : string; XSLContent : string) : WideString;
var
  XML : IXMLDocument;
  XSL : IXMLDocument;
begin

  XML := LoadXMLData(XMLContent);
  XSL := LoadXMLData(XSLContent);

  XML.DocumentElement.TransformNode(XSL.DocumentElement, Result)

end;

But it produces an unexpected output:

Belgian Waffles$5.95Two of our famous Belgian Waffles with plenty of real maple syrup650Strawberry Belgian Waffles$7.95Light Belgian waffles covered with strawberries and whipped cream900Berry-Berry Belgian Waffles$8.95Light Belgian waffles covered with an assortment of fresh berries and whipped cream900French Toast$4.50Thick slices made from our homemade sourdough bread600Homestyle Breakfast$6.95Two eggs, bacon or sausage, toast, and our ever-popular hash browns950


Attempt 2:

I've found the following function in the "Using the MSXML Parser/ Transform Engine" section of this page (Note: it's a Delphi 5 example but I'm using Delphi 2007).

function DoTransform(const xml, xsl : string ): string;
var
  XMLDoc : IXMLDOMDocument;
  XSLDoc : IXMLDOMDocument;
  Template : IXSLTemplate;
  Processor : IXSLProcessor;
begin
  Result := '';
  try
    XMLDoc := CoFreeThreadedDOMDocument30.Create;
    XSLDoc := CoFreeThreadedDOMDocument30.Create;
    XMLDoc.load(xml);
    XSLDoc.load(xsl);
    Template := CoXSLTemplate30.Create;
    Template.stylesheet := XSLDoc;
    Processor := Template.createProcessor;
    Processor.input := XMLDoc;
    Processor.transform;
    result :=  Processor.output;
  finally
    XMLDoc := nil;
    XSLDoc := nil;
  end;
end;

I've imported the Microsoft XML type library...

Component -> Import Component -> Import a Type Library -> Next -> Microsoft XML, v6.0

...and added MSXML2_TLB to the uses clause but then some other errors occours:

E2003 Undeclared identifier: 'CoFreeThreadedDOMDocument30'

E2003 Undeclared identifier: 'CoXSLTemplate30'

I've switched CoFreeThreadedDOMDocument30 to CoFreeThreadedDOMDocument60 and CoXSLTemplate30 to CoXSLTemplate60 and now it compiles without errors.

At runtime, at this line:

Template.stylesheet := XSLDoc;

It raises the following exception (In italian):

Il foglio di stile non include un elemento documento. Il foglio di stile รจ vuoto oppure potrebbe essere un documento XML in formato non corretto.

In english it should be something like this:

The stylesheet doesn't include a document element. The stylesheet is empty or it could be a bad formatted XML document.

I've tested with other example files and the error is always the same and I don't understand which could be the problem.

Am I on the right way or are there better ways to do what I need?

3

3 Answers

0
votes

I have the following code that works. It is assuming a file containing the XSLT but that should be easy to change.

It was written a long time ago and I was in a hurry. So there might be room for some improvements. Hope it helps.

uses
  Windows, ComObj, XMLDoc, msxmldom, msxml;

function DOMToMSDom(const Doc: IDOMDocument): IXMLDOMDocument3;
begin
  Result := ((Doc as IXMLDOMNodeRef).GetXMLDOMNode as IXMLDOMDocument3);
end;

function TransformXMLDocWithXSLTFile(const Doc: XMLIntf.IXMLDocument; const StyleSheetLocation: string; var TransformedData, Error: string): Boolean;
var
  MsxmlDoc: IXMLDOMDocument3;
  xslStyle : IXMLDOMDocument;
begin
  Result := False;
  if not FileExists(StyleSheetLocation) then 
  begin
    Error := 'Specified XSLT stylesheet file does not exist: ' + StyleSheetLocation;
    Exit;
  end;
  try
    MsxmlDoc := DOMToMSDom(Doc.DOMDocument);
    xslStyle := CoDOMDocument60.Create;
    xslStyle.load(StyleSheetLocation);
    IXMLDOMDocument3(xslStyle).setProperty('AllowXsltScript', True);
    TransformedData := MsxmlDoc.transformNode(xslStyle);
    Result := True;
  except
    on E: Exception do 
    begin
      Error := E.Message;
    end;
  end;
end;
0
votes

There are a few problems;

Your XSL doesn't have a stylesheet, output or root template. It needs to be like this:

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

    <xsl:output method="html" encoding="UTF-8" />

    <xsl:template match="/">
        <html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            <body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
                <xsl:for-each select="breakfast_menu/food">
                    <div style="background-color:teal;color:white;padding:4px">
                        <span style="font-weight:bold">
                            <xsl:value-of select="name"/> - </span>
                        <xsl:value-of select="price"/>
                    </div>
                    <div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
                        <p>
                            <xsl:value-of select="description"/>
                            <span style="font-style:italic"> (<xsl:value-of select="calories"/> calories per serving)</span>
                        </p>
                    </div>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

In your delphi code, because you are loading strings, so you should use .loadXML, not .load

    XMLDoc := CoFreeThreadedDOMDocument60.Create;
    XSLDoc := CoFreeThreadedDOMDocument60.Create;
    XMLDoc.loadXML(xml);
    XSLDoc.loadXML(xsl);
    
    Template := CoXSLTemplate60.Create;
    Template.stylesheet := XSLDoc;
    Processor := Template.createProcessor;
    Processor.input := XMLDoc;
    Processor.transform;
    s :=  Processor.output;

After doing .load or .loadXML you look at the parseerror to check whats going on (if you are having problems)

xsldoc.parseError.errorCode;  //will be 0 if all is well
xsldoc.parseError.reason; 
0
votes

This is my solution tested on Delphi 2007 and Delphi XE7.

uses
  msxml; 

function Transform(const AXMLContent : string; const AXSLContent : string) : string;
var
  XML : IXMLDOMDocument;
  XSL : IXMLDOMDocument;
begin
  XML := CoDOMDocument.Create;
  XML.loadXML(AXMLContent);

  XSL := CoDOMDocument.Create;
  XSL.loadXML(AXSLContent);

  Result := XML.TransformNode(XSL);
end;

picture of the resulting styled xml

Resulting HTML code:

<html>
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<div style="background-color:teal;color:white;padding:4px"><span style="font-weight:bold">Belgian Waffles - </span>$5.95</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>Two of our famous Belgian Waffles with plenty of real maple syrup<span style="font-style:italic"> (650 calories per serving)</span></p>
</div>
<div style="background-color:teal;color:white;padding:4px"><span style="font-weight:bold">Strawberry Belgian Waffles - </span>$7.95</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>Light Belgian waffles covered with strawberries and whipped cream<span style="font-style:italic"> (900 calories per serving)</span></p>
</div>
<div style="background-color:teal;color:white;padding:4px"><span style="font-weight:bold">Berry-Berry Belgian Waffles - </span>$8.95</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>Light Belgian waffles covered with an assortment of fresh berries and whipped cream<span style="font-style:italic"> (900 calories per serving)</span></p>
</div>
<div style="background-color:teal;color:white;padding:4px"><span style="font-weight:bold">French Toast - </span>$4.50</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>Thick slices made from our homemade sourdough bread<span style="font-style:italic"> (600 calories per serving)</span></p>
</div>
<div style="background-color:teal;color:white;padding:4px"><span style="font-weight:bold">Homestyle Breakfast - </span>$6.95</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>Two eggs, bacon or sausage, toast, and our ever-popular hash browns<span style="font-style:italic"> (950 calories per serving)</span></p>
</div>
</body>
</html>

For displaying the resulting string into a TWebBrowser I've used the following code:

procedure LoadHTMLCode(AWebBrowser : TWebBrowser; const AHTMLCode: string);
var
  Doc: Variant;
begin
  if not Assigned(AWebBrowser.Document) then
    AWebBrowser.Navigate('about:blank');

  Doc := AWebBrowser.Document;
  Doc.Clear;
  Doc.Write(AHTMLCode);
  Doc.Close;
end;

...

var
  XMLContent : string;
  XLSContent : string;
  HTMLCode : string;
begin
  //loading XML content
  XMLContent := ...;

  //loading XLS content
  XLSContent := ...;
 
  //transforming
  HTMLCode := Transform(XMLContent, XLSContent);

  //displaying
  LoadHTMLCode(WebBrowser1, HTMLCode);  
end;