如何使用 XSLT 转换 XML?

How to transform an XML using an XSLT?

我有一个 XML 文件和一个 XSLT,我想使用 XSLT 将 XML 转换为 HTML 字符串,该字符串可以加载到 TWebBrowser.

对于我的测试,我使用 these 个示例文件。

XML 文件:

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

<food>
<name>Belgian Waffles</name>
<price>.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>.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>.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>.50</price>
<description>Thick slices made from our homemade sourdough bread</description>
<calories>600</calories>
</food>

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

</breakfast_menu>

XSLT 文件:

<?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>

尝试 1:

我找到了 this 解决方案并尝试了以下功能:

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;

但它产生了意外的输出:

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


尝试 2:

我在 this 页面的 "Using the MSXML Parser/ Transform Engine" 部分找到了以下函数(注意:这是一个 Delphi 5 示例,但我使用的是 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;

我已经导入了 Microsoft XML 类型库...

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

...并在 uses 子句中添加了 MSXML2_TLB 但随后出现了一些其他错误:

E2003 Undeclared identifier: 'CoFreeThreadedDOMDocument30'

E2003 Undeclared identifier: 'CoXSLTemplate30'

我已将 CoFreeThreadedDOMDocument30 切换为 CoFreeThreadedDOMDocument60,将 CoXSLTemplate30 切换为 CoXSLTemplate60,现在编译没有错误。

在运行时,在这一行:

Template.stylesheet := XSLDoc;

它引发了以下异常(意大利语):

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

英文应该是这样的:

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

我用其他示例文件进行了测试,错误始终相同,但我不明白可能是哪个问题。

我的方法是否正确,或者是否有更好的方法来满足我的需求?

我有以下有效的代码。它假定一个包含 XSLT 的文件,但应该很容易更改。

很久以前写的,赶时间。所以可能还有一些改进的余地。希望对你有帮助。

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;

有一些问题;

您的 XSL 没有样式表、输出或根模板。它需要是这样的:

<?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>

在你的delphi代码中,因为你正在加载字符串,所以你应该使用.loadXML,而不是.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;

执行 .load 或 .loadXML 后,您查看解析错误以检查发生了什么(如果您遇到问题)

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

这是我在 Delphi 2007Delphi 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;

结果 HTML 代码:

<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>.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>.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>.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>.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>.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>

为了将结果字符串显示到 TWebBrowser 中,我使用了以下代码:

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;