跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

技巧: 用 xsl:message 调试样式表

XSLT 中的回显打印

Elliotte Harold (elharo@metalab.unc.edu), 副教授, Polytechnic University
Elliotte Harold 出生在新奥尔良,现在他还定期回老家喝一碗美味的秋葵汤。不过目前,他与妻子 Beth 定居在纽约临近布鲁克林的 Prospect Heights,与他们住在一起的还有猫咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大学的计算机科学副教授,讲授 Java 和面向对象编程。他的 Cafe au Lait 网站是 Internet 上最受欢迎的独立 Java 站点之一,姊妹站点 Cafe con Leche 是最受欢迎的 XML 站点之一。他的最新著作是 Java I/O, 2nd edition。他目前从事 XML 处理 XOM API、Jaxen XPath 引擎和 Jester 测试覆盖工具的研究。

简介: 这篇技巧讨论了使用 xsl:message 元素帮助理解和调试可扩展样式表语言转换(Extensible Stylesheet Language Transformation,XSLT)样式表的不同方法。

查看本系列更多内容

发布日期: 2006 年 8 月 24 日
级别: 初级
访问情况 : 1788 次浏览
评论: 


回显打印是调试困难问题的最古老的方法之一。不管怎么说,这仍然是最简单、最快捷的方法。如果不能确定为何函数没有按预期工作,用 printf() 或类似的工具在控制台上打印几个变量就可以了解幕后发生了什么。

当然,必须有能够输出调试结果的控制台。XSLT 不一定具有 printf() 这样的东西。不过 XSLT 有一个与 printf() 对应的元素:xsl:messagexsl:message 元素不改变 XSLT 样式表生成的结果树,仅仅输出一些消息让程序员看到。一般输出到控制台,但也可能是对话框或者日志文件。无论输出到哪里,都是一种非常好的调试辅助手段。

xsl:message 元素是可选的。没有要求处理程序一定支持它。不过大部分处理程序都支持它,而且通常把消息输出到控制台。

是否激活了某个模板?

如果输出不同于预期结果,首先要检查是否确实激活了模板。有很多原因可能造成模板被跳过,比如:

  • match属性中的元素名打错了
  • 错误的名称空间,尤其是当试图把默认名称空间中的元素与样式表中没有前缀的名称匹配时
  • 模式不匹配

这仅仅是部分可能的原因 。还可能有其他原因造成应该被激活的模板没有激活。为了检查是否执行了模板,可以在模板的开头加上 xsl:message 元素说明处理程序确实执行到了这里。比方说,假设要将 XHTML 文档转化成没有标记的普通文本。可以将类似 清单 1 所示的消息添加到模板中:


清单 1. 用于调试的匹配 HTML 的模板规则
<xsl:template match="/">
  <xsl:message>Matched root node</xsl:message>
  <xsl:apply-templates select="*"/>
</xsl:template>

<xsl:template match="html" xmlns:html="http://www.w3.org/1999/xhtml">
  <xsl:message>Matched html element</xsl:message>
  <book><xsl:apply-templates select="html"/></book>
</xsl:template>

运行该样式表的时候,如果看到 Matched root node 消息,就知道到了那里。如果没有看到 Matched html element 消息,就知道并没有执行到这里。这样就提供了确定问题所在的线索。很可能是 xsl:apply-templatesselect 属性或者 html templatematch 属性错了(这个例子是后一种情况,第二个模板应该与 html:html 匹配)。无论哪种情况,都会告诉您到哪里查找最可能出问题的地方。

包含 xsl:ifxsl:choose 语句的模板有多个分支。可以在每个分支中加上 xsl:message 元素来确定执行了哪一个(如果有的话)。比如,清单 2 显示了插入到 DocBook XSL 样式表中以确定为何转换无效的代码:


清单 2. 增加了调试指令的 DocBook XSL 模板
  <xsl:choose>
    <xsl:when test="caption">
      <xsl:message>CAPTION!</xsl:message>
      <fo:table-and-caption id="{$id}" 
                            xsl:use-attribute-sets="table.properties">
        <xsl:apply-templates select="caption" mode="htmlTable"/>
        <fo:table xsl:use-attribute-sets="table.table.properties">
          <xsl:choose>
            <xsl:when test="$fop.extensions != 0 or
                            $passivetex.extensions != 0">
              <xsl:message>EXTENSIONS!</xsl:message>
              <xsl:attribute name="table-layout">fixed</xsl:attribute>
            </xsl:when>
          </xsl:choose>
          <xsl:attribute name="width">
            <xsl:choose>
              <xsl:when test="@width">
                <xsl:message>WIDTH ATTRIBUTE!</xsl:message>
                <xsl:value-of select="@width"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:message>NO WIDTH ATTRIBUTE!</xsl:message>100%
              </xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
          <xsl:call-template name="make-html-table-columns">
            <xsl:with-param name="count" select="$numcols"/>
          </xsl:call-template>
          <xsl:apply-templates select="thead" mode="htmlTable"/>
          <xsl:apply-templates select="tfoot" mode="htmlTable"/>
          <xsl:choose>
            <xsl:when test="tbody">
              <xsl:message>TBODY!</xsl:message>
              <xsl:apply-templates select="tbody" mode="htmlTable"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:message>NO TBODY!</xsl:message>
              <fo:table-body>
                <xsl:apply-templates select="tr" mode="htmlTable"/>
              </fo:table-body>
            </xsl:otherwise>
          </xsl:choose>
        </fo:table>
      </fo:table-and-caption>
      <xsl:copy-of select="$footnotes"/>
    </xsl:when>
    <xsl:otherwise>
       <xsl:message>NO CAPTION!</xsl:message>
      <fo:block id="{$id}"
                xsl:use-attribute-sets="informaltable.properties">
        <fo:table table-layout="auto"
                  xsl:use-attribute-sets="table.table.properties">
          <xsl:attribute name="width">
            <xsl:choose>
              <xsl:when test="@width">
                <xsl:message>WIDTH ATTRIBUTE!</xsl:message>
                <xsl:value-of select="@width"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:message>NO WIDTH ATTRIBUTE!</xsl:message>
                100%
              </xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
          <xsl:call-template name="make-html-table-columns">
            <xsl:with-param name="count" select="$numcols"/>
          </xsl:call-template>
          <!--<xsl:apply-templates mode="htmlTable"/>-->
          <xsl:choose>
            <xsl:when test="tbody">
              <xsl:message>TBODY!</xsl:message>
              <xsl:apply-templates select="tbody" mode="htmlTable"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:message>NO TBODY!</xsl:message>
              <fo:table-body>
                <xsl:apply-templates select="tr" mode="htmlTable"/>
              </fo:table-body>
            </xsl:otherwise>
          </xsl:choose>
        </fo:table>
      </fo:block>
      <xsl:copy-of select="$footnotes"/>
    </xsl:otherwise>
  </xsl:choose>
  

XSLT 模板可能比较复杂。要注意您的消息。我曾经多次犯过这样的错误,在错误的地方放上了 NO TBODY! 这样的消息声明。结果看到的消息完全是误导。调试困难的问题之前一定要安排好缩进和空白。通常,清理缩进本身就能发现问题所在。


检查节点和节点集

有时候最好计算得到一个结果而不是原样输出文字。这样可以减少偶然误导自己的可能性。所幸的是,xsl:message 元素的内容实际上是完整的 XSLT 模板。这就意味着可以包含的不仅仅是简单的字符串消息。比如 清单 3 可以输出激活模板的上下文节点的完整的祖先树:


清单 3. 列举上下文节点祖先的模板
<xsl:template match="foo">
  <xsl:message>
    <xsl:for-each select="ancestor-or-self::*">
      <xsl:value-of select="name(.)"/> /
    </xsl:for-each>
  </xsl:message>
  ...
</xsl:template>

也可打印当前元素的所有属性,如 清单 4 所示:


清单 4. 列举上下文节点属性的模板
<xsl:template match="foo">
  <xsl:message>
    <xsl:for-each select="attribute::*">
      <xsl:value-of select="name(.)"/>="<xsl:value-of select="."/>"
    </xsl:for-each>
  </xsl:message>
  ...
</xsl:template>

基本上能够生成调试样式表需要的任何信息。但是,如果模板没有因为您所认为的原因而激活,复杂的模板可能失败。有时候最好从一个简单的、仅仅确定上下文节点的表达式开始:

 <xsl:message>
    <xsl:value-of select="name(.)"/>: <xsl:value-of select="."/>
 </xsl:message>

这个消息看起来似乎没有什么意义。无论如何总该知道匹配的是什么节点吧?但事实上并非这么显而易见。这类调试的目标之一是发现代码思维模型偏离实际的地方。如果存在 bug,某些代码很可能没有按照您的预期工作。因此,有必要检查一下您 “知道的” 每一点是否为真。如果最终发现某一处并不像您所想像的那样,这就是问题所在。用 Artemus Ward 的话来说,“并非我们清楚的事情带来麻烦。往往是我们自以为知道的东西造成困扰。”


测试条件

有时候问题仅在特定的条件下才出现。可以应用 xsl:if 以便只有当其他某些条件满足时才生成输出。这一点对于必须考虑限制输出结果的大型文档尤其重要。比方说,如果要调试一个只有遇到包含行内图像的段落时才会出现问题的样式表。不需要对整本书中的每个段落都打印消息,只有那些包含行内图像的段落才须如此。比如,清单 5 中的模板只有当 para 元素包含 image 时才会输出消息:


清单 5. 段落中包含图像时输出消息的模板
<xsl:template match="para">
  <xsl:if test="image">
    <xsl:message>
      Para: <xsl:value-of select="."/>"
    </xsl:message>
  </xsl:if>
  ...
</xsl:template>

可以逐个测试元素的值,将消息限制到只有出现 bug 的一种情况。比如 清单 6 中,只有当段落包含特定的单词时才会输出消息:


清单 6. 段落中包含字符串 “BCEL” 时输出消息的模板
<xsl:template match="para">
    <xsl:if test="contains(., 'BCEL')">
      <xsl:message>
        Para: <xsl:value-of select="."/>"
      </xsl:message>
    </xsl:if>
  ...
</xsl:template>


是否使用了预期的处理程序?

为了彻底覆盖 Java 1.4 以及后续版本中的内置处理程序,必须将 Xalan JAR 文件放到 jre/lib/endorsed 中。不能使用 -classpath 以及 -Xbootclasspath/p: 命令行参数。

最难诊断的是 XSLT 处理程序造成的问题。如果使用的不是您预料的处理程序,这类问题尤其难以发现。使用 Java™ 语言的时候这种情况很常见,因为类路径问题意味着使用的常常不是您所希望的版本或品牌的处理程序。比如,Sun 的 JDK 1.4.0 捆绑了问题较多的 Xalan 2.2d10。即使在类路径中添加了更稳定更健壮的 Xalan 2.7,使用的可能仍然是较老的捆绑版本。

确定所用处理程序最简单的办法是用 xsl:message 元素打印出它的名字。xsl:vendor 函数系统属性包含所用处理程序的名称。

    <xsl:message>
      Processor: <xsl:value-of select="system-property('xsl:vendor')"/>
    </xsl:message>
 

通常返回 Apache Software Foundation (Xalan XSLTC)SAXON from Michael Kay of ICL 之类的字符串。如果发现使用的是 Xalan,而您想的本来是 Saxon,这就可以解释一些问题。不过,您通常真正关心的是库的版本。在 Xalan 中可以使用一个简单的扩展函数来得到库的版本,该函数调用 static org.apache.xalan.Version.getVersion() 方法,如 清单 7 所示:


清单 7. 输出 Xalan 版本号的样式表
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:pre="xalan://org.apache.xalan.Version"
>
  
  <xsl:template match="/">
    <xsl:message>
      Processor: <xsl:value-of select="system-property('xsl:vendor')"/>
      <xsl:text> <xsl:text>
      <xsl:value-of select="pre:getVersion()"/>
    </xsl:message>
  </xsl:template>

</xsl:stylesheet>

下面是使用 JDK 1.5.0_06 转换时得到的结果:

$ java org.apache.xalan.xslt.Process -IN test.xml -XSL test.xsl 

      Processor: Apache Software Foundation (Xalan XSLTC) Xalan Java 2.4.1

向 jre/lib/endorsed 添加最新版本的 Xalan 后的结果如下:

$ java org.apache.xalan.xslt.Process -IN test.xml -XSL test.xsl 

file:///Users/elharo/Documents/articles/tips/test.xsl; Line #8; Column #18; 
      Processor: Apache Software Foundation Xalan Java 2.7.0

XSLT 2

XSLT 2 定义了标准的 xsl:product-namexsl:product-version 系统属性,它们返回产品的名称和版本号。我所知道的 XSLT 1 处理程序都还不能识别这两个函数,如果使用 XSLT 2,这是更有效的检查所用处理程序的方法。

值得一提的是,2.4.1 和 2.7.0 版本之间的差别有时候很明显,从后一个版本开始 Xalan 输出 URL 和每个消息的行列号。

当然只有在使用 Xalan 的时候才能用这种方法分辨。如果您认为自己在使用 Xalan 而实际上用的是其他处理程序,很可能出现的问题会警告您这一点。但是其他大部分处理程序都由类似的功能。最新版本的 Saxon 实际上在 xsl:vendor 字符串中包含了版本号,这一点非常方便。


停止转换

在很少的情况下,可能会发现转换一团糟难以继续下去。也许因为输入与预料的相差甚远,希望放弃或停止处理。这种情况下,使用 terminate="yes" 属性的 xsl:message 元素可以停止转换。比如 清单 8 中,如果输入中包含 XHTML 名称空间之外的任何元素则该模板放弃处理:


清单 8. 如果文档包含非 XHTML 元素则停止处理的模板
<xsl:template match="/">
  <xsl:for-each select="descendant::*">
    <xsl:if test="namespace-uri() != 'http://www.w3.org/1999/xhtml'">
      <xsl:message terminate="yes">
        Non-XHTML element encountered: <xsl:value-of select="name()"/> 
      </xsl:message>
    </xsl:if>
  </xsl:for-each>
  <xsl:apply-templates select="*"/>
</xsl:template>

理想情况下,结果树应该在生成任何输出之前建立,以便在遇到这样的消息处理程序时可以什么也不输出。不同的处理程序对此的处理相差很大。有一些处理程序,包括 xsltproc,在终止之前仍然生成一些输出。如果在样式表中包含该消息,要保证任何依赖于输出的处理能够发现输出提前停止了。


结束语

函数性语言(如 XSLT)与过程性语言(如 C)相比,调试的时候更加微妙。有时候最简单的办法就是重新拿起传统的回显打印这个工具,在使用的时候将任何可能有关的内容输出到控制台。xsl:message 元素允许样式表在控制台或程序员能够看到的其他地方输出消息。消息可以是简单的信号、复杂的节点信息组合或者条件数据。xsl:message 元素可以让您比较处理程序在文档中应该看到和实际看到的内容。期望和现实之间的差别就是问题所在。


参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • XML 1.1 Bible(Elliotte Rusty Harold; Wiley, 2003)第 15 章:详细讨论了 XSLT。

  • Principles of XML design”(developerWorks,2004 年 3 月到 2005 年 4 月):阅读 Uche Ogbuji 的系列文章,避免 XML 设计落入一般内容处理的窠臼。

  • IBM XML 1.1 认证:了解如何才能成为一名 IBM 认证的 XML 1.1 及相关技术的开发人员。

  • XML:developerWorks XML 专区提供了各种技术文章和技巧、教程、标准和 IBM 红皮书。

  • developerWorks 技术事件和网络广播:随时关注技术的最新进展。

获得产品和技术

  • Xalan:XSLT 处理程序的当前版本远远超出了 JDK 特别是 JDK 1.4 捆绑的版本。

  • IBM 试用软件:用这些试用软件开发您的下一个项目,可直接从 developerWorks 下载。

讨论

关于作者

Elliotte Harold 出生在新奥尔良,现在他还定期回老家喝一碗美味的秋葵汤。不过目前,他与妻子 Beth 定居在纽约临近布鲁克林的 Prospect Heights,与他们住在一起的还有猫咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大学的计算机科学副教授,讲授 Java 和面向对象编程。他的 Cafe au Lait 网站是 Internet 上最受欢迎的独立 Java 站点之一,姊妹站点 Cafe con Leche 是最受欢迎的 XML 站点之一。他的最新著作是 Java I/O, 2nd edition。他目前从事 XML 处理 XOM API、Jaxen XPath 引擎和 Jester 测试覆盖工具的研究。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=156002
ArticleTitle=技巧: 用 xsl:message 调试样式表
publish-date=08242006
author1-email=elharo@metalab.unc.edu
author1-email-cc=dwxed@us.ibm.com

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。