在使用调试器之前,大多数开发人员都使用编程语言中的
print 或
printf 语句的等价语句,试图记录下可以暴露其代码的错误行为的值。XSLT 也不例外。现在,您可以得到 XSLT 调试器,甚至完整的集成开发环境(IDE),但您也许不会经常只为了检查一个值,而要完成调试器的所有操作。尤其对于象我这样使用无格式的旧 XEmacs(在我的例子中,还有 xslide)来编辑 XSLT 转换的人来说,确实是这样。
xsl:message 通常是这种情况的最佳解决方案。只要在正确的模板中的正确位置插入一条消息,用作快速调试工具就行了。主要的缺点是没有
xsl:message 输出的标准规范:它可以采取各种形式:弹出对话框、日志文件或者(对于命令行 XSLT 处理器)标准错误输出的带标记显示。通常,这是个小问题;快速阅读任何工具的文档就可以告诉您它是怎样生成处理器消息的。尽管在显示上可能有所差异,但
xsl:message 的优点是它是一个标准指令,因此可以跨处理器移植。这里,我要介绍一些技巧,它们可以让使用
xsl:message 进行调试变得更有效。
清单 1(labels.xml)是我要在整个讨论中使用的源文档。它是个包含邮递地址标签数据的简单 XML 文档。
清单 1. 邮递标签(labels.xml)
<?xml version="1.0"?> <labels> <label> <name>Thomas Eliot</name> <address> <street>3 Prufrock Lane</street> <city>Hartford</city> <state>CT</state> </address> </label> <label> <name>Ezra Pound</name> <address> <street>45 Usura Place</street> <city>Hailey</city> <state>ID</state> </address> </label> <label> <name>William Williams</name> <address> <street>100 Wheelbarrow Blvd</street> <city>Patterson</city> <state>NJ</state> </address> </label> </labels> |
清单 2(plainmsg.xslt)是一个简单样式表。在这个文件中,我使用
xsl:message 来显示从简单 XPath 表达式中获取的地址的节点集合。
清单 2. 使用无格式的 xsl:message 进行调试的示例(plainmsg.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="labels">
<xsl:call-template name="get-addresses"/>
</xsl:template>
<xsl:template name="get-addresses">
<xsl:variable name="addresses" select="label/address"/>
<xsl:message>
<xsl:copy-of select="$addresses"/>
</xsl:message>
<!-- Actually do something useful here -->
</xsl:template>
<!-- Suppress all other element display -->
<xsl:template match="*"/>
</xsl:transform>
|
该消息只显示每个标签的地址元素及其子代。讨论到的 XPath 表达式
label/address 是通过
addresses 变量访问的。我可以使用这种技术来找出在元素名中输入时常见的 XPath 错误,这样的错误会造成返回意想不到的空节点集合。这种错误还会进一步导致在处理中产生奇怪的结果。通过使用这种调试消息,可以找出更接近根源的问题。要注意的最重要的一点就是我使用了
xsl:copy-of ,而不是
xsl:value-of 来将节点集合放到消息中。使用 4XSLT 得到的输出是:
$ 4xslt labels.xml plainmsg.xslt STYLESHEET MESSAGE: <address> <street>3 Prufrock Lane</street> <city>Hartford</city> <state>CT</state> </address><address> <street>45 Usura Place</street> <city>Hailey</city> <state>ID</state> </address><address> <street>100 Wheelbarrow Blvd</street> <city>Patterson</city> <state>NJ</state> </address> END STYLESHEET MESSAGE |
如果我使用了
xsl:value-of —
<xsl:message>
<xsl:value-of select="$addresses"/>
</xsl:message>
|
― 那么输出就会不太有用:
$ 4xslt labels.xml plainmsg.xslt STYLESHEET MESSAGE: <?xml version='1.0' encoding='UTF-8'?> 3 Prufrock Lane Hartford CT END STYLESHEET MESSAGE |
这是因为
xsl:value-of 将其选择表达式的结果转换成节点集合中第一个节点的
明文字符串表示。
xsl:copy-of 真正将所有节点都完整地复制到消息主体中,作为完整的标记,而这正是通常您想要的。
在更复杂的情况中,如果四处散布着许多模板和一些调试消息,在消息中再显示一些 XPath 上下文也许有用。对于调试最常见的错误,上下文节点本身通常是最有用的信息。清单 3 是说明这一问题的 XSLT 片段:
清单 3. 格式化的 xsl:message 指令,用于显示序列化的上下文节点。
<xsl:message>
<xsl:text>START MESSAGE HEADER
</xsl:text>
<xsl:text>CONTEXT NODE:
</xsl:text>
<xsl:copy-of select="."/>
<xsl:text>
STOP MESSAGE HEADER
</xsl:text>
<xsl:text>START MESSAGE BODY
</xsl:text>
<xsl:copy-of select="$addresses"/>
<xsl:text>
STOP MESSAGE BODY</xsl:text>
</xsl:message>
|
这将消息格式化成头和主体,并在头(
<xsl:copy-of select="."/> )中显示上下文节点。请注意散布的“
”实体:这些是换行实体,是 XSLT 中强制换行的安全方式(只要它们不被当作空白去除,这就是我确保它们在
xsl:text 标记中的原因)。
如果您的 XSLT 处理器支持 EXSLT 标准 XSLT 扩展的
dyn 模块,那么在调试时,您可以做更多工作。
dyn 模块提供了一些工具,这些工具可以在运行时对 XPath 表达式进行动态求值。通过在运行时检查样式表本身,您可以利用这个工具来添加巧妙的调试技巧。这类似于某些语言中所指的
内省。作为一个示例,我要添加一个小实用程序模板,用于显示所有全局变量的值。清单 4 是使用该模板的完整样式表(global-vars.xslt):
清单 4. 演示使用动态求值来显示全局变量的脚本(global-vars.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
>
<xsl:variable name="spam" select="'eggs'"/>
<xsl:variable name="first-label" select="/labels/label[1]"/>
<xsl:param name="monty" select="'python'"/>
<xsl:template match="labels">
<xsl:call-template name="get-addresses"/>
</xsl:template>
<xsl:template name="get-addresses">
<xsl:variable name="addresses" select="label/address"/>
<xsl:message>
<xsl:call-template name="dump-globals"/>
</xsl:message>
</xsl:template>
<!-- Suppress all other element display -->
<xsl:template match="*"/>
<!-- Cut and paste this template into your own scripts -->
<xsl:template name="dump-globals"
xmlns:exsl="http://exslt.org/common"
xmlns:dyn="http://exslt.org/dynamic"
>
<!-- Get the current transform document element -->
<!-- A -->
<xsl:variable name="ctde" select="document('')/xsl:*"/>
<xsl:text>GLOBAL VARIABLES:
</xsl:text>
<!-- B -->
<xsl:for-each
select="$ctde/xsl:variable|$ctde/xsl:param">
<!-- C -->
<xsl:variable name="value" select="dyn:evaluate(concat('$', @name))"/>
<xsl:text>VARIABLE NAME:</xsl:text>
<xsl:value-of select="@name"/><xsl:text>
</xsl:text>
<xsl:text>VARIABLE VALUE:</xsl:text>
<xsl:choose>
<!-- D -->
<xsl:when test="exsl:object-type($value) = 'node-set'">
<xsl:copy-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>END GLOBAL VARIABLES
</xsl:text>
</xsl:template>
</xsl:transform>
|
以下是 4XSLT 将这个转换应用到 labels.xml 文档所生成的输出:
$ 4xslt labels.xml global-vars.xslt STYLESHEET MESSAGE: GLOBAL VARIABLES: VARIABLE NAME:spam VARIABLE VALUE:eggs VARIABLE NAME:first-label VARIABLE VALUE:<label> <name>Thomas Eliot</name> <address> <street>3 Prufrock Lane</street> <city>Hartford</city> <state>CT</state> </address> </label> VARIABLE NAME:monty VARIABLE VALUE:python END GLOBAL VARIABLES END STYLESHEET MESSAGE |
可以看到,结果是要显示所有全局变量的名称和值。执行这一技巧的代码是
dump-globals 模板,我将它设计成全封闭的,其中包含了它需要的扩展名称空间。您可以直接将这个模板复制到自己的 XSLT 代码来使用它,或者可以将它复制到单独的 XSLT 文件以进行导入。只要调用
<xsl:call-template name="dump-globals"/> 就可以使用它。它会将全局变量清单转储到输出。在
清单 4 中,我从
xsl:message 的主体中调用它,这样它就作为消息内容出现。我插入了按字母标记的注释,以便更清楚地说明
dump-globals 模板的工作方式。以下几段说明了每个用字母标明的部分。
A:我使用标准的
document('') 形式将当前样式表当作源文档进行检索。我将这个样式表的文档元素赋值给
ctde 变量,以便拥有对顶级 XSLT 元素的现成访问。请注意,我很谨慎地使用
xsl:* 作为文档元素的名称测试。这是因为
xsl:stylesheet 和
xsl:transform 都是合法文档元素名称,我不能肯定在某个特定脚本中会使用哪个。对于使用 XSLT 允许的
文字结果元素作为根形式的样式表,这个模板不起作用,但在这种转换中不管怎样都不能拥有真正的全局变量,这样的话,您或许就不会使用
dump-globals 模板。
B:
xsl:for-each 在顶级变量和参数元素之间循环。
C:每个变量或参数元素都有一个
name 属性。因为我有变量名,所以我要做的就是对变量引用求值以获取它的值。我通过实时创建 XPath 表达式,获取当前变量名并在前面加上美元符号
$ 来这样做。现在,我需要把结果字符串当作 XPath 表达式,对其求值。这就是
dyn:evaluate 函数发挥作用的时候。它获取字符串,并将它当作 XPath 表达式来执行它,然后返回结果。我就以这种方式从每个全局变量或参数获取值,并将它分配给
$value 以供稍后使用。
D:在显示变量名之后,该代码会显示值。如上所述,通常在以获取信息为目的而显示节点集合时,使用
xsl:copy-of 最有用。但是在显示数字、字符串和布尔值时,
xsl:value-of 才是您想要的。由于我不会事先知道每个变量值是节点集合还是其它类型的对象,我需要一种可以在运行时确定这个问题的方法。幸好,EXSLT 在
common 模块中提供了另一个有用的函数:
exsl:object-type 。这个函数返回一个字符串,该字符串表明从对 XPath 对象的参数求值得到的该对象的类型。
您可以进一步采用这些基本想法。动态 XPath 表达式和
document('') 形式使许多处理技巧(甚至超出调试技巧)成为可能。另外,EXSLT 有许多其它有用的模块和函数,您应该尝试一下。如果您喜欢的 XSLT 处理器不支持 EXSLT,请一定要向供应商寻求支持。EXSLT 支持正在不断发展,Saxon、Xalan、libxslt、jd.xslt 和 4XSLT(4Suite 的一部分)全都支持部分或所有 EXSLT 模块。
不要忘记
xsl:message 支持
terminate 属性,如果该属性设置成
yes ,那么它就会异常终止样式表。这提供了一种在 XSLT 中支持
断言的简单方法 ― 即,您可以将意外情况的检查放到样式表中,如果发生这样的情况,就使用终止消息异常终止。
在本文中,我已经演示了如何使用
xsl:message 进行调试,一些对于调试任务有用的通用技巧和技术,以及一些通过使用 EXSLT 函数扩展转换而可能带来的新调试技巧。我认为所有 XML 开发人员早晚会认识到他们必须转向使用 XSLT 调试器的所有函数,但我希望我已经帮助减少了必需的次数。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 要获取不错的 XSLT 介绍,请阅读 LindaMay Patterson 撰写的“
Investigating XSLT: The XML transformation language”(
developerWorks,2001 年 8 月)。
- 浏览
EXSLT获取有用且广受支持的 XSLT 的扩展函数和元素。
common 模块包括我在本文中使用的
exsl:object-type()函数。 dynamic 模块包含dyn:evaluate()函数。
- 请访问 W3C 的
XSL页面,它包含了许多到 XSLT 相关资源的有用链接,这些资源包括规范本身、教程、文章和实现。
- 如果您不熟悉 XSLT 的
文字结果元素作为样式表形式,请查阅 XSLT 规范中的相关
部分,它提供了简单示例。在查阅该资料时,也许应该复习一下
xsl:message。
- 一定要阅读
XSL Frequently Asked Questions,它讲述了许多对调试有用的注释。
- 请阅读我在
developerWorks上发表的其它 XSLT 相关的技巧:“
Counting with node sets”(2002 年 5 月),“
Generating internal HTML links with XSLT”(2001 年 2 月)和“
XSLT lookup tables”(2001 年 2 月)。
- 我在示例中使用的样式表处理器是 4XSLT,它是我与别人合作开发的
4Suite的一部分。
- 请到
developerWorks XML 专区上寻找更多的 XML 参考资料。
- 请研究一下
IBM WebSphere Studio Application Developer,这是个易于使用的集成开发环境,可以用于构建、测试和部署 J2EE 应用程序,包括从 DTD 和模式生成 XML 文档。
- 了解您怎样可以成为一名
IBM 认证的 XML 及相关技术开发人员。

Uche Ogbuji 是 Fourthought Inc.的顾问兼共同创始人,该公司是专为企业知识管理提供 XML 解决方案的软件供应商和咨询公司。Fourthought 开发了 4Suite,这是一个用于 XML、RDF 和知识管理应用程序的开放源码平台。Ogbuji 先生是一名出生于尼日利亚的计算机工程师兼作家,他生活和工作在美国科罗拉多州博耳德。可以通过 uche.ogbuji@fourthought.com与 Ogbuji 先生联系。