xsl调试
回声打印是调试棘手问题的最古老方法之一。 尽管如此,它仍然是最简单,最快的方法之一。 当您不太确定为什么某个功能无法按预期运行时,请使用printf()
或等效功能将一些变量输出到控制台,以查看发生了什么。
当然,这假设您有一个控制台可以在其上打印调试输出。 XSLT不一定有任何这样的东西。 但是XSLT确实具有一个与printf()
: xsl:message
相对应的元素。 xsl:message
元素根本不会更改XSLT样式表产生的结果树。 它仅在程序员可以看到的地方输出一条消息。 通常是控制台,但它可以是对话框或日志文件。 无论输出到哪里,这都是出色的调试工具。
xsl:message
元素是可选的。 不需要处理器来支持它。 但是,大多数这样做,通常是通过在控制台上打印消息来实现的。
模板是否被激活?
当输出不是您期望的值时,要测试的第一件事是模板是否确实处于激活状态。 出于多种原因,很容易意外跳过模板,例如:
-
match
属性中元素名称的错字 - 不正确的名称空间,尤其是试图将默认名称空间中的元素与样式表中的未前缀名称匹配
- 模式不匹配
这只是一个样本。 您认为可能没有激活模板的还有很多其他原因。 要验证是否到达模板,请在模板顶部放置xsl:message
元素,以在处理器确实到达模板时发出信号。 例如,假设您正在尝试转换XHTML文档,并且输出一直以纯文本形式出现而没有标记。 您可以将清单1中的消息添加到模板中:
清单1.用于匹配HTML的Buggy模板规则
<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-templates
的select
属性错误或html
template
的match
属性错误。 (在此示例中,是后者。第二个模板应尝试匹配html:html
。)两种方式都可以减少您必须看的地方,并将精力集中在错误的最可能位置。
包含xsl:if
或xsl: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>
此消息似乎微不足道。 毕竟,您知道匹配哪个节点,对吗? 但这并不是很明显。 这种调试的目的之一是找出您的代码思维模型与实际情况有何不同。 当您遇到错误时,很有可能某些代码根本没有按照您的想象做。 因此,值得验证您“知道”的所有内容都是真实的。 当您发现某些东西被证明是错误的时,便会出现错误。 用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>
您可以测试单个元素的值,以将消息限制在出现错误的情况下。 例如,在清单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>
您是否正在使用自己认为的处理器?
一些最难诊断的问题是由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>
这通常返回一个字符串,例如SAXON from Michael Kay of ICL
Apache Software Foundation (Xalan XSLTC)
或SAXON from Michael Kay of ICL
。 如果您发现您正在使用Xalan,并且以为您在使用Saxon,那可能解释了一些事情。 但是,通常您真正关心的是库的版本。 在Xalan中,这可以通过一个简单的扩展函数来实现,该函数调用静态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
这是将最新版本的Xalan添加到jre / lib / endorsed之后的输出:
$ 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
还要注意的是,显然是在版本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
元素使您可以将处理器在文档中看到的内容与实际看到的内容进行比较。 期望与现实之间的区别在于漏洞所在。
翻译自: https://www.ibm.com/developerworks/xml/library/x-tipxslmsg/index.html
xsl调试