级别: 初级 Jared Jackson, 副研究员, IBM
2002 年 4 月 01 日 随着XML 和 XSL 技术被大家迅速接受和广泛使用,我们已经清楚地看到了这两种技术的联合使用在Web 上表示、操作和提供数据以及在不同应用程序之间共享数据这些方面的优势。但是,大多数熟悉 XML 和 XSL 基础的开发人员还没有充分利用这种能力。本文向开发人员展示了如何使用扩展这种允许您扩展 XSL 能力的技术。
就能力和简单性这两个方面而言,自早期的 SQL 数据库语言以来,XML 和 XSL 的联合以前所未见的方式改革了数据存储和操作。XML 提供了一种清晰而独立的记录数据的方式,这种方式易于共享和理解。同样地,许多人觉得 XSL 也是易于阅读、编写和理解的。显然,对于每个与该技术行业有关的人而言,这个功能强大的一对组合都是必备知识。
与 XSL 转换的基本元素有关的广泛作用域和平坦的学习曲线,有时就象一把双刃剑 ― 既带来了核心技术的广泛使用,又阻止大多数学习 XSL 的开发人员不要研究和使用其更高级和功能强大的功能。
本文适合于已经基本理解 XML 和 XSL 并准备进一步了解这方面知识的开发人员。如果不熟悉这些技术,您可以在
developerWorks和其它网站上找到几篇不错的介绍性文章和教程。本文向您展示了如何使用扩展(一种大多数 XSL 处理器中都有提供的技术)它实际上允许无限扩展 XSL 核心功能的现有能力。本文包含关于如何使用代码编写扩展的一般性描述,之后还有三个具体的且广泛适用的示例。
什么是 XSL 扩展?
首先必须理解:象其它所有编程语言一样,XSL 只是一种需要实现的文法规范。幸运的是,XSL 已经变得很流行,而且有几种实现供选择。扩展并不是文法必需的特性,因此它们的语法不象该语言的其它构造那样得到良好定义。但是,现在 W3C 的 XSLT 建议书(Recommendation)中包含了它们。本文中的示例将遵循该建议书的格式。
简而言之,通过扩展我们可以从 XSL 文档中调用使用其它编程语言编写的方法。通常,扩展方法是用和 XSL 处理器相同的语言编写的。该规则也有例外:譬如,可以使用 Java 来运行用其它语言(如 JavaScript 或 PERL)编写的程序。因此,可以用 JavaScript、PERL 或某种其它语言编写 XSL 中的扩展,并通过基于 Java 的 XSL 处理器利用它们。
XSL 已经如此能干了,是什么使得这些扩展如此重要呢?XSL 在转换的简单性和广泛能力上所取得的优势却常常在进行任何与转换无关的操作的效率和能力上失去了。
例如,假定有一个 XML 文档,列出了您系统的 5,000 个用户。在该 XML 内的一个
Users 节点下给出了每个用户的用户名、真实姓名和电子邮件。稍后,您在该 XML 的一个单独子树中向该 XML 文档添加了一个
Interests 节点,并根据特定兴趣(如特技、自行车、计算机)将用户名分组。最终,您希望将数据转换成 HTML 页面,该页面根据兴趣对用户进行分组并向有相似兴趣的用户提供电子邮件联系方式。XSL 可以通过下列代码方便地完成这一任务:
清单 1. 不使用扩展进行用户兴趣 XSL 转换
<xsl:for-each select="Interests/Interest">
<b><xsl:value-of select="@InterestName"/></b>
<ul>
<xsl:for-each select="User">
<xsl:variable name="userName" select="@userName"/>
<xsl:variable name="userNode" select="/Root/Users/User[@userName =
$userName]"/>
<li>
<xsl:value-of select="$userNode/@realName"/>
<xsl:value-of select="concat(' ',$userName/@email"/>
</li>
</xsl:for-each>
</ul>
</xsl:for-each>
|
遗憾的是,这种转换执行的方式,对于每个兴趣种类中的每个用户,都要对 5,000 名用户的整个列表进行检查。这大大超出了您希望服务器为每个对该 Web 页面的请求所做的工作量。
扩展提供了一种便利方法,该方法可以解决这个问题和其它几种当您在非平凡数据集上使用 XSL 时可能遇到的难题。在上面的示例中,如果使用一个简单的散列图(hashmap)或二进制搜索树就可以轻易地解决问题,但是用 XSL 实现一个这样的数据结构并不是很方便,并且是不必要的。一种具有更合适的数据类型的语言的扩展将更轻松地解决这一问题。(顺便说一下,这种解决方法的代码在下面的第一个示例中给出)。
本文中使用的技术
列出所有 XSL 处理器以及它们实现扩展的方法将是一件令人畏缩的任务。本文使用 Java 版本的 Xalan ― 来自 Apache Project 的一个流行的并可免费使用的 XSL 处理器 ― 来描述编写扩展的细节。所有示例都是针对该平台的。(将 Xerces(另一个 Apache 产品)当作 XML 解析器使用。可通过
参考资料中的链接下载 Xalan 和 Xerces。)其它流行的 XSL 实现大多数也提供了扩展的机制,但是您必需参考其文档以查找方法中的任何差异。
为了简化 XML 和 XSL 的使用,我还提供了用于一些更通用的 XML 操作的 Java 代码。
参考资料中的 zip 文件中提供了该代码,同时还提供了运行所有这些示例必需的代码和数据。但是,这个文件不包含诸如 Xalan 和 Xerces 等外部库。在您通过
参考资料 中的链接获得那些库(版本:Xalan ― Java 2.3.1;Xerces 1.4.4)之后,将它们的 jar 文件放置到从 zip 文件解压缩得到的 lib 目录中。对于希望直接跳到示例的读者,所有 Java 代码都在 src 目录中,XML 数据在 XML 目录中,XSL 转换在 XSL 目录中,批处理文件在 bin 目录中,而已编译的代码在 lib 目录中。
创建扩展
为了从 XSL 调用一个方法,必须首先编写那个方法,并将它的已编译形式放在执行 XSL 转换的应用程序的类路径中。方法可以是您自己设计的、由 Java 标准库提供的或取自其它 Java 库。在某些 XSL 处理器中(如 Xalan),甚至有直接写到处理器中的扩展方法。
当您编写或使用这些方法时,第一件要弄清楚的事就是数据类型从 XSL 到 Java 和从 Java 到 XSL 的映射。下表提供了 Xalan 中这些映射的参考。
表 1、2. 数据类型映射
|
参数映射
| |
XSLT 类型
|
Java 类型
| | Node Set | org.w3c.dom.traversal.NodeIterator | | String | java.lang.String | | Boolean | java.lang.Boolean | | Number | java.lang.Double | | Result Tree Fragment | org.w3c.dom.DocumentFragment |
|
返回类型映射
| |
Java 类型
|
XSLT 类型
| org.w3c.dom.traversal.NodeIterator
org.apache.xml.dtm.DTM
org.apache.xml.dtm.DTMAxisIterator
org.apache.xml.dtm.DTMIterator
org.w3c.dom.Node
| Node Set
| | java.lang.String | String | | java.lang.Boolean | Boolean | | java.lang.Number | Number | | org.w3c.dom.DocumentFragment | Result Tree Fragment |
一旦编写完方法后,将它们合并到 XSL 中是很简单的事。
第一步是在
<xsl:stylesheet> 元素中为您的方法声明一个名称空间。例如,如果希望运行来自
com.myCompany.XSLExtensions 包中名为
foo 类的方法,则 XSL 文件的根将包含下列行:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" xmlns:extension="xalan://com.myCompany.XSLExtensions.foo"/> |
如果稍后您希望调用已声明的类中的方法,则使用
<xsl:stylesheet> 元素中声明的名称空间。继续该示例,为了运行一个获取
String 作为参数并返回一个
String 的称为
bar() 的方法,您可以使用如下类似的代码:
<xsl:variable name="myParam" select="'theParameter'"/>
<xsl:variable name="myResult" select="extension:bar($myParam)"/> |
就那么简单。现在,
myResult 变量包含从您的 Java 类调用
bar 的结果。要更好地掌握该技术,请执行下列三个示例。
示例 1:查找表
本文的开头演示了这样一种方案:在 XML 文档的各个独立的子树中使用标准 XSL 技术查找数据,会花费过多数量的计算时间。解决这个问题的一个简单方法是创建一个通用散列表,该表提供存储和检索字符串的一种机制。因为散列表是直接构建到标准 Java 库中的,所以编写一个使用它们的扩展应该毫不费力。
在
参考资料的 zip 文件中包含的 src/StringHash.java 文件中可以找到散列表 Java 代码。它有两个著名的方法:
-
addString(String tableName, String key, String value)
-
getString(String tableName, String key)
第一个方法允许创建一个与表名有关的散列表,并在其中插入一个映射到键的字符串值。第二个方法提供一种检索存储值的手段。
可以在 XML/user_interests.xml 文件(请参阅
参考资料 中的 zip 文件)中找到一个 XML 数据源。它遵循下列形式:
清单 2. 用户兴趣的 XML 片段
<Users>
<User userName="aragon" realName="Aragon"
email="aragon@middleEarth.fict"/>
<User userName="boromir" realName="Boromir"
email="boromir@middleEarth.fict"/>
...
</Users>
<Interests>
<Interest name="archery">
<User userName="legolas"/>
...
</Interest>
...
</Interests>
|
参考资料中的 zip 文件中给出了两个用于产生 Web 页面结果的 XSL 文件。可以在 XSL/user_interests_xsl_only.xsl 文件中找到第一个文件,该文件遵循
清单 1 中显示的代码。可以在 XSL/user_interests_extensions.xsl 文件中找到第二个文件,它将前面的 XSL 文件修改成清单 3 中显示的代码。为了在 Windows 上轻松地运行 XSL 转换,使用 bin/Example_1*.bat 批处理文件。Unix 和 Mac 开发人员,在检查了这些批处理文件之后,运行这些示例时可能会有点小麻烦。
清单 3. 使用扩展进行用户兴趣 XSL 转换
<xsl:stylesheet xmlns:lookup="xalan://StringHash">
...
<xsl:for-each select="Users/User">
<xsl:value-of select="lookup:addString('realName', string(@userName),
string(@realName))"/>
<xsl:value-of select="lookup:addString('email', string(@userName),
string(@email))"/>
</xsl:for-each>
...
<li>
<xsl:value-of select="lookup:getString('realName',$userName)"/>
<xsl:value-of select="concat(' - ',lookup:getString('email',
$userName))"/>
</li>
|
示例 2:正则表达式
当前的 XSL 标准使用 XPath 技术来执行它所有的模式匹配。尽管 XPath 提供了一种紧凑和优雅的方式来遍历 XML 树,但其模式匹配函数的能力相当有限。(执行布尔匹配的 XPath 中字符串函数的全体是:
starts-with() 、
ends-with() 和
contains() 。您也可以将字符串自动解析成数。)
正则表达式提供了跨文本的字符串的更丰富的模式匹配,但在遍历诸如 XML 树这样的数据结构时,它和 XPath 一样易于使用。有关正则表达式更详细的信息,请参阅
参考资料 。
最佳解决方案是将两种技术结合起来。XSL 转换语言的下一版本目前还处于开发和评审中,它包含了一个将正则表达式添加到该语言的提议。
对于现在就想使用该技术的开发人员,扩展提供了这样做的机制。
本文附带的 zip 文件中包含 src/PatternMatcher.java 文件,可以在该文件中找到作为扩展访问的 Java 方法的源代码。这些方法利用未包含在标准 Java 库中的外部代码,因此这个示例还展示了链接扩展中使用的外部 jar 文件必需的步骤。为了使示例运行,您将需要获取 GNU 提供的正则表达式 jar 文件(请参阅
参考资料 ),并将它放置到解压缩产生的 lib 目录下。可以随意找到另一个正则表达式包并修改代码以适合它。
对于第二个示例,假设您希望从原始源文本生成一个用户列表,该列表上所有用户的姓和名都是已知的。
尽管这是一个相当微不足道的示例,但不难想象在用户组、产品目录或引用数据库上更复杂示例的运行。
一种实现该目标的简单方法是彻底检查用户的真实姓名,并用来匹配那些由一个名字后跟空格再跟另一个名字组成的姓名。用于此目的的正则表达式是
\w* \w 。
XSL 现在包含清单 4 中的行。
清单 4. XSL 中的正则表达式
<xsl:stylesheet xmlns:regexp="xalan://PatternMatcher">
...
<ul>
<xsl:for-each select="Users/User[regexp:containsMatch('\w* \w*',
string(@realName))]">
<li>
<xsl:value-of select="@realName"/>
</li>
</xsl:for-each>
</ul>
|
与示例 1 类似,可以通过 bin/Example_2.bat 文件执行这个示例。可以在 XSL/user_last_names.xsl 上找到使用的 XSL 文件。
扩展在这种技术上的潜力是无限的。
示例 3:国际化
国际化,有时称为
本地化或
自然语言支持,是一种方法,开发人员通过这种方法使具有不同语言和文化的人都可读取他们的产品。如果转换的产品是一组针对广大读者的 Web 页面,则在 XML 转换上下文中国际化就尤其重要。但是国际化主题太广泛了,以至于在这个示例的上下文里不可能进行全面介绍,您可以在下面提到的其它
developerWorks文章中找到关于该主题的良好论述。
这个示例利用了 Java 通过使用资源包处理国际化的内置技术。如果您对这一主题不太熟悉,我鼓励您阅读引用的文章。至于现在,只要说资源包是由一个文件集合组成的,而这些文件包含不同地区或者更确切地说是不同语言环境的翻译就足够了。当用户请求 Web 页面时,Web 服务器能够读取该用户的首选语言环境,并能够使用这些资源包适当地进行响应。基于 XML 的应用程序还可以将结果针对特定语言环境。
这个示例中代码的潜在用途与前一个示例一样广泛和多样。为了演示该技术,通过 bin/Example_3.bat 文件执行的代码从样本 XML 用户数据创建了三个 Web 页面。三个结果页面表现了数据的相同视图,但是以三种不同的语言来呈现。可以在从 zip 文件解压缩出来的 lib 目录中的属性文件中找到所用的翻译。
结束语
即使当考虑 XSL 转换的最基本组件时,它们的能力也是非凡的。当使用XSL扩展来扩展这个核心,以包含现代编程语言的能力时,实际上它就具有了无限的潜力。上面提供的想法和示例只是冰山一角,在理解了本文所提供的知识之后,我将探索众多其余潜力的任务留给了您。
参考资料
关于作者  | 
|  | Jared Jackson 是 IBM 阿尔马登(Almaden)研究中心的研究员。他的工作领域是基于 Web 的技术。 |
对本文的评价
|