65.9K
CodeProject 正在变化。 阅读更多。
Home

XSLT 扩展函数

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.40/5 (5投票s)

2004年1月31日

5分钟阅读

viewsIcon

51183

downloadIcon

948

扩展 XSLT 处理器功能。

引言

XML 和 XSLT 在简化数据块处理方面发挥了巨大作用。我们不再需要为遇到的每种“固定字段”布局编写自定义解析器,这可能会成为维护的噩梦。通过 XSLT,我们可以将 XML 转换为另一种形状,用于演示或供其他进程使用。当标准 XSLT 功能不足时,我们可以通过添加自己的自定义组件来扩展其能力。

引入的功能类型并非没有限制,但必须谨慎使用。在我看来,通过样式表驱动业务逻辑是不明智的。然而,在某些情况下,这样做可能很实用:需要根据数据库进行翻译的数据,或者超过内置数学功能限制的复杂计算。

示例代码

该示例由一个 WTL 测试客户端、一个 XSLT 转换组件和一个用于数据库查找的扩展函数组件组成。我包含了一个书目访问数据库的子集作为数据源、一个样式表和一些输入文件。

扩展函数

扩展函数是一个常规的 COM 组件,必须支持 IDispatch,但有几点需要注意。

  • 在 XML 中,一切都是字符串。
  • 可以使用 number 函数对输入参数进行数字类型转换。XSLT 处理器会将数字输出参数转换为字符串。
  • 可以将 XML 传回给处理器。您必须使用 xsl:value-of 标签的 disable-output-escaping 属性才能生成有效的 XML。否则,您将得到一个无法解析的字符串。

示例扩展函数公开了 3 个公共方法和 1 个属性。每个都有不同的参数列表,以展示在调用 XSLT 处理器外部时的一些细微差别。

interface IBibTitles : IDispatch{
    [id(1), helpstring("method GetBookTitle")] 
HRESULT GetBookTitle([in] BSTR isbn, [out,retval] BSTR* bookTitle);
    [id(2), helpstring("method GetYearPublished")] 
HRESULT GetYearPublished([in] BSTR isbn, [out,retval] LONG* yearPublished);
    [id(3), helpstring("method GetIsbnByYear")] 
HRESULT GetIsbnByYear([in] LONG year, [out,retval] BSTR* result);
    [propget, id(4), helpstring("property ProcessTime")] 
HRESULT ProcessTime([out, retval] BSTR* pVal);
};

每个方法都会查询数据库,返回值直接写入 XML 输出流。如果查询未返回任何行,则返回值是 NULL;或者在返回 LONG 的情况下,其默认值为 -1。此组件引发的任何错误都会从 XSLT 处理器重新抛出,从而导致转换失败。GetIsbnByYear 查询 0 行或多行,并将其作为 XML 字符串返回。这是通过使用 CXmlAccessor 实现的。属性 ProcessTime 返回当前系统时间。

除此之外,该组件没有什么特别之处。它可以是用任何支持 COM 的语言编写的:VC++、Visual Basic 或任何 .NET 语言。如果您使用 .NET 语言,则需要注册程序集的类型库(Regasm.exe)。

XSLTransform 组件

要获得 XSLT 处理器,请将样式表加载到 FreeThreadedDOMDocument40 对象中,创建一个 XSLTemplate40 对象,然后将 XSLTemplatestylesheet 属性设置为 DOMDocument。样式表会编译一次,是只读的,并且可以被其他线程并发使用以执行转换。

我在 FinalConstruct() 中创建了 Free Threaded Document、XSLTemplate 和扩展函数,它们在组件在内存中时可用。在每次调用 Transform 方法时,我都会从 XSLTemplate 查询一个处理器,添加扩展函数,然后进行输入转换。

// get a processor
MSXML2::IXSLProcessorPtr pProc;
pProc = pTemplate->createProcessor();
pProc->addObject(pExtFunc, _T("http://myuniquenamespace/titles"));
pProc->input = pDom.GetInterfacePtr();

pProc->transform();

请注意 addObject 的调用。第二个参数是此扩展的命名空间。它可以是您喜欢的任何名称,但尽量保持其唯一性是个好主意。如果您曾经实现过 IScritpHost,那么这可能看起来很熟悉。我们正在添加一个对象并指定一个名称,以便从脚本中调用它。您可以根据需要向处理器添加任意数量的对象,每个对象都有不同的命名空间。

样式表

在样式表中,我们必须在 stylesheettransform 标签中声明对象命名空间。命名空间前缀可以是您喜欢的任何名称,这里我使用了“db”。命名空间必须与调用 addObject 时使用的命名空间相同。

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:db="http://myuniquenamespace/titles">

xsl:value-of 标签的 select 属性中调用扩展函数。请牢记方法调用的参数类型。以下操作将产生一个类型不匹配错误。

<xsl:value-of select="db:GetBookTitle(ROOT/BK)"/>

这是因为 XPath 表达式求值结果是一个 XML 树片段。我们的扩展组件对 XML 一无所知,只知道字符串和数字。我们可以通过使用 stringnumber 函数来解决这个问题。

<xsl:value-of select="db:GetBookTitle(string(ROOT/BK))"/>

如果 XPath 表达式求值结果是一个具有子节点的节点,则字符串值将是该节点下所有文本的空格分隔字符串。

XML data
<ROOT>
    <BK>0-0230081-2-1</BK>
    <BK>0-0230948-1-8</BK>
    <BK>0-0301413-4-6</BK>
</ROOT>

XPath:
<xsl:value-of select="string(ROOT)"/>
Evaluates to:
0-0230081-2-1 0-0230948-1-8 0-0301413-4-6

调试时请牢记这一点。

如果您的函数公开了属性,则可以通过 get-[Property]set-[Property] 来引用它们。

<xsl:value-of select="db:get-ProcessTime()"/>

摘要

差不多就这样了。唯一剩下的是测试客户端,但那里没什么特别的。

使用 XSLTemplateXSLProcessor 可以通过一次编译样式表并每次转换时重用它来提高性能,这非常类似于存储过程。

XSLT 处理器扩展函数是一个支持 IDispatch 的常规 COM 组件。XML 中的一切都是字符串,因此请特别注意参数类型。

示例代码中包含 3 个输入文件。第一个是 input1.xml,其中包含一些存在于数据库中的 ISBN 号码。Input2.xml 包含一个数据库中不存在的 ISBN 号码和一个将导致样式表调用 GetIsbnByYear 的标签。最后一个输入文件 input3.xml 是无效的 XML。我用它来测试失败条件,并更改了样式表以在加载时强制失败。

随着 .NET 的发展,MSXML4 可能已经过时。无论是否如此,我认为了解幕后发生的事情是个好主意。虽然我不认为扩展处理器总是最佳选择,但在需要时它确实是个不错的选择。

关注点

虽然我没有花很多时间探索 .NET 提供的完整 XML 功能集,但我确信它比 MSXML 4 有所改进。我花了几个小时尝试将 XML 片段传递到扩展函数中,但都没有成功。

© . All rights reserved.