我发现自己使用了很多很多密钥,有时我会在其中进行健全性检查,例如:
<xsl:key name="foo-with-bar" match="foo[contains(., 'bar')]">
<xsl:if test="@baz='xyz'">
<xsl:message terminate="yes">
Can't handle <foo> containing "bar" if @baz="xyz"
</xsl:message>
</xsl:if>
<xsl:value-of select="generate-id()"/>
</xsl:key>
(这实际上是一个非常简单的测试 - 实际测试可能非常复杂。)这让我想到,在我实际上不需要密钥而只需要健全性检查的情况下,为什么不使用密钥呢?例如:
<xsl:key name="sanity-check" match="foo[contains(., 'bar')][@baz='xyz']">
<xsl:message terminate="yes">
Can't handle <foo> containing "bar" if @baz="xyz"
</xsl:message>
</xsl:key>
我意识到除非我实际使用密钥,否则撒克逊不会终止,例如
<xsl:template match="/">
<xsl:apply-templates select="key('sanity-check', '')"/>
<xsl:copy-of select="."/>
</xsl:template>
但是,我可以确定 XSLT 处理器实际上会以这种模式终止吗?我想这不是钥匙的设计方式。
背景:吸引力较小的替代品(如果 tl;dr 则忽略)
我意识到 Schematron 可以替代这种方法,但由于这些测试可能不是关于文档有效性,而是关于样式表是否能够处理文档,我发现由样式表本身实现的测试非常有吸引力。
另一种选择可能是使用模板而不是键。我在这里看到两个选项:
- 在常规处理期间,测试和终止模板会取代“正常”模板。但是,在常规处理期间,并非所有需要测试的节点都必须由可以被否决的模板匹配处理。
使用模板运行单独的测试,如下所示:
<xsl:template match="/"> <xsl:apply-templates mode="sanity-check"/> <xsl:apply-templates mode="actual-processing"/> </xsl:template> <xsl:template match="node()|@*" mode="sanity-check"> <xsl:apply-templates select="node()|@*" mode="sanity-check"/> </xsl:template> <xsl:template mode="sanity-check" match="foo[contains(., 'bar')][@baz='xyz']"> <xsl:message terminate="yes"> foo containing bar can't have @baz set to xyz </xsl:message> </xsl:template>
密钥仍然具有优势,因为无需额外努力就可以进行无法以简单模式表示的更复杂的检查。使用模板,这将需要这样的结构:
<xsl:template mode="sanity-check" match="foo">
<xsl:variable name="variable" select="some-complicated-expression"/>
<xsl:if test="some-test-requiring[$variable=@bar]">
<xsl:message terminate="yes">
Fail!
</xsl:message>
</xsl:if>
<xsl:apply-templates mode="sanity-check" select="node()|@*"/>
</xsl:template>
这里有两个缺点:
- 需要更多样板代码。如果我们在那里犯了错误,测试将无法正确执行,但我们不会注意到。我们需要:
- 像模板一样的“身份转换”
<xsl:apply-templates mode="sanity-check" select="node()|@*"/>
在每个包含更复杂测试的模板中。
- 重叠测试的问题,例如一个带有
match="A|B"
,另一个带有match="B|C"
,都需要一些<xsl:choose>
/<xsl:if>
来决定我们是否需要终止。一个元素<B>
只会匹配其中一个模板。使用键,无需担心重叠匹配。