级别: 初级 Uche Ogbuji (uche.ogbuji@fourthought.com), 首席顾问, Fourthought, Inc.
2002 年 11 月 01 日 调试器在编程中非常有用,但它们自身也可能是复杂的软件 ― 难以设置、学习和使用。有时,您只需要将怀疑是所研究的特定问题核心的一些值快速打印输出。在本文中,Uche Ogbuji 演示了如何使用 XSLT 的
xsl:message 和其它内置工具以及 EXSLT 中的公共扩展来执行快速调试。
在使用调试器之前,大多数开发人员都使用编程语言中的
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 调试器的所有函数,但我希望我已经帮助减少了必需的次数。
参考资料
关于作者
对本文的评价
|