XSLT 扩展函数






3.40/5 (5投票s)
2004年1月31日
5分钟阅读

51183

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
对象,然后将 XSLTemplate
的 stylesheet
属性设置为 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
,那么这可能看起来很熟悉。我们正在添加一个对象并指定一个名称,以便从脚本中调用它。您可以根据需要向处理器添加任意数量的对象,每个对象都有不同的命名空间。
样式表
在样式表中,我们必须在 stylesheet
或 transform
标签中声明对象命名空间。命名空间前缀可以是您喜欢的任何名称,这里我使用了“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 一无所知,只知道字符串和数字。我们可以通过使用 string
和 number
函数来解决这个问题。
<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()"/>
摘要
差不多就这样了。唯一剩下的是测试客户端,但那里没什么特别的。
使用 XSLTemplate
和 XSLProcessor
可以通过一次编译样式表并每次转换时重用它来提高性能,这非常类似于存储过程。
XSLT 处理器扩展函数是一个支持 IDispatch
的常规 COM 组件。XML 中的一切都是字符串,因此请特别注意参数类型。
示例代码中包含 3 个输入文件。第一个是 input1.xml,其中包含一些存在于数据库中的 ISBN 号码。Input2.xml 包含一个数据库中不存在的 ISBN 号码和一个将导致样式表调用 GetIsbnByYear
的标签。最后一个输入文件 input3.xml 是无效的 XML。我用它来测试失败条件,并更改了样式表以在加载时强制失败。
随着 .NET 的发展,MSXML4 可能已经过时。无论是否如此,我认为了解幕后发生的事情是个好主意。虽然我不认为扩展处理器总是最佳选择,但在需要时它确实是个不错的选择。
关注点
虽然我没有花很多时间探索 .NET 提供的完整 XML 功能集,但我确信它比 MSXML 4 有所改进。我花了几个小时尝试将 XML 片段传递到扩展函数中,但都没有成功。