IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

实时调试 XSLT

使调试变得简单的技巧

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Uche Ogbuji (uche.ogbuji@fourthought.com), 首席顾问, Fourthought, Inc.

2002 年 11 月 01 日

调试器在编程中非常有用,但它们自身也可能是复杂的软件 ― 难以设置、学习和使用。有时,您只需要将怀疑是所研究的特定问题核心的一些值快速打印输出。在本文中,Uche Ogbuji 演示了如何使用 XSLT 的 xsl:message 和其它内置工具以及 EXSLT 中的公共扩展来执行快速调试。

在使用调试器之前,大多数开发人员都使用编程语言中的 printprintf 语句的等价语句,试图记录下可以暴露其代码的错误行为的值。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:stylesheetxsl:transform 都是合法文档元素名称,我不能肯定在某个特定脚本中会使用哪个。对于使用 XSLT 允许的 文字结果元素作为根形式的样式表,这个模板不起作用,但在这种转换中不管怎样都不能拥有真正的全局变量,这样的话,您或许就不会使用 dump-globals 模板。

Bxsl: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 调试器的所有函数,但我希望我已经帮助减少了必需的次数。



参考资料



关于作者

Uche Ogbuji 的照片

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




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款