使用XSLT 2时,Schematron会忽略在其上下文中具有属性的规则

时间:2016-10-14 14:32:38

标签: xml xpath xslt-2.0 schematron

我发现Schematron忽略了任何具有context属性的规则,该属性包含对属性的引用。在下面的代码中,我有@att,但任何包含属性的复杂xpath都会遇到问题。 (例如bar/@att也会被忽略。)

我使用的是Schematron分发版here

我有test.sch

<?xml version="1.0" encoding="utf-8"?>
<schema
    xmlns="http://purl.oclc.org/dsdl/schematron"
    queryBinding="xslt2"
    schemaVersion="ISO19757-3">
  <title>Test schema.</title>
  <pattern>
    <rule context="@att">
      <assert test="false()">@att cannot appear anywhere.</assert>
    </rule>
    <rule context="foo">
      <assert test="false()">foo cannot appear anywhere.</assert>
    </rule>
  </pattern>
</schema>

test.xml文件:

<foo att="something"/>

为了可复制性,这个Makefile

SAXON:=saxon
SCHEMATRON_TO_XSL:=/home/ldd/src/schematron/xslt/iso_svrl_for_xslt2.xsl

.PHONY: all
all: test.xsl test.xml
    $(SAXON) -xsl:$< -s:$(word 2,$^)

test.xsl: test.sch
    $(SAXON) -s:$< -o:$@ -xsl:$(SCHEMATRON_TO_XSL) allow-foreign=true generate-fired-rule=false

.PHONY: clean
clean:
    rm test.xsl

如果我发出make clean; make,我会得到一个失败的断言:

<svrl:failed-assert test="false()" location="/foo[1]">
   <svrl:text>foo cannot appear anywhere.</svrl:text>
</svrl:failed-assert>

Schematron忽略了<rule context="@att">中的测试,该测试也应该失败。为什么忽略该规则?

请注意,如果我将test.sch更改为使用queryBinding="xslt1"并编辑Makefile以更改SCHEMATRON_TO_XSL以使用XSLT 1的schematron转换,那么我确实会遇到两个失败我期待的。所以只有在使用XSLT 2时才会出现问题。

1 个答案:

答案 0 :(得分:3)

这是Schematron中的一个错误,似乎至少自2010年以来一直存在于Schematron的代码中,当然也就是自2011年以来。(自从AFAIK发布以来,很难说Schematron没有公共版本控制存储库看看这个答案的结尾,知道为什么我说自2011年以来当然存在。)

解释

如果您检查从Schematron生成的test.xsl文件,您会看到xsl:apply-templates这样的元素:

<xsl:apply-templates select="*|comment()|processing-instruction()" mode="M1"/>

请注意属性节点不属于select

运行saxon转换test.sch文件时执行的代码导入名为iso_schematron_skeleton_for_saxon.xsl的架构。如果您检查该文件,您会发现XSL中出现的xsl:apply-template元素是使用以下代码生成的:

<axsl:apply-templates select="{$context-xpath}" mode="M{count(preceding-sibling::*)}"/>

axsl:不是拼写错误。它是输出节点使用的名称空间前缀。)

在同一个文件中向后看,你会发现:

<xsl:variable name="context-xpath">
  <xsl:if test="$attributes='true' and parent::node() ">@*|</xsl:if>
  <xsl:choose>
    <xsl:when test="$only-child-elements='true'">*</xsl:when>
    <xsl:when test="$visit-text='true'">node()</xsl:when>
    <xsl:otherwise>*|comment()|processing-instruction()</xsl:otherwise>
  </xsl:choose>
</xsl:variable>

<xsl:param name="attributes">
  <xsl:choose>
    <xsl:when test="//iso:rule[contains(@context,'@') or contains(@context,'attribute')]">true</xsl:when>
    <xsl:otherwise>false</xsl:otherwise>
  </xsl:choose>
</xsl:param>

问题在于context-xpath的初始化方式。它是一个顶级变量,初始化一次,只有一次。在初始化时,上下文节点是文档的节点,因此parent::node()始终为false,因此@*|永远不会包含context-xpath

根据我的经验attributes已正确初始化。所以没有必要摆弄它。

请注意,当您运行XSLT 1处理器的Schematron样式表时,会使用不同的代码库,但没有此错误。 (相当于iso_schematron_skeleton_for_saxon.xsl的XSLT 1是iso_schematron_skeleton_for_xslt1.xsl)。

解决方案1:重写规则

可以重写有问题的规则,使它们没有引用属性的context。我不能说我是这个解决方案的粉丝,因为它使规则复杂化并可能产生严重的负面性能影响。并且可能存在重写规则的情况。

解决方案2:编辑iso_schematron_skeleton_for_saxon.xsl

我在编辑文件时不再遇到问题,因此以这种方式初始化context-path

<xsl:variable name="context-xpath">
  <xsl:if test="$attributes='true'">@*|</xsl:if>
  <xsl:choose>
    <xsl:when test="$only-child-elements='true'">*</xsl:when>
    <xsl:when test="$visit-text='true'">node()</xsl:when>
    <xsl:otherwise>*|comment()|processing-instruction()</xsl:otherwise>
  </xsl:choose>
</xsl:variable>

一般来说,我不喜欢对第三方代码进行临时编辑,但我没有看到更好的解决方案。

确证

在完成上述调查后,我很清楚哪些关键字可以帮助我找到有关该主题的任何问题报告,因此我开始搜索context-xpath和类似的关键字。我发现Ken Holman在2011年12月遇到了同样的问题,this email证明了这一点。他得出的结论与我的相似。他还建议强制attributes参数为真。根据我的经验,没有必要。他提到的案例是Schematron代码在多个文件中分割的情况,但Schematron的readme.txt表示处理的第一步应该是运行iso_dsdl_include.xsl以将模式的多个部分组合到一个文件中

AFAIK Ken Holman的电子邮件从未收到回复,问题从未在任何“官方”消息来源中修复。