对于连续的字符串/文本节点,这个递归函数有效:
<xsl:template name="quote">
<xsl:param name="text" select="." />
<xsl:param name="old" select="'""'" />
<xsl:param name="new" select="'»«'" />
<xsl:param name="state" select="0" />
<xsl:variable name="o" select="substring($old, $state + 1, 1)" />
<xsl:variable name="n" select="substring($new, $state + 1, 1)" />
<xsl:choose>
<xsl:when test="not($o and contains($text, $o))">
<xsl:value-of select="$text" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($text, $o)" />
<xsl:value-of select="$n" />
<xsl:call-template name="quote">
<xsl:with-param name="text" select="substring-after($text, $o)" />
<xsl:with-param name="old" select="$old" />
<xsl:with-param name="new" select="$new" />
<xsl:with-param name="state" select="($state + 1) mod 2" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
如果您提供 $old 和 $new 参数,它们应该是长度为 2 的字符串,分别包含开始和结束引号的字符。
对所有参数使用默认值的示例:
<xsl:template match="text()">
<xsl:call-template name="quote" />
</xsl:template>
如果有问题的文本节点是嵌套结构 (<p>Here "are quotes <b>too</b>"</p>) 的一部分,事情会稍微复杂一些。
为了在不连续的文本节点上实现非对称引用(不同的开始和结束引号),我们需要做一些假设:
- 我们将使用引号计数,即如果一个引号前面有偶数个引号(0、2、4 ...),我们假设它是一个开始引号,否则我们假设一个结束引号.
- 我们将假设所有相关引用都发生在同级级别(没有“不正确的嵌套”,例如
text "text <x>"</x>,其中结束引号位于不同的嵌套级别)。
- 输入中的引号必须正确,否则我们的计数将被取消。
- 为了获得最大的兼容性,我将假设使用 vanilla XSLT 1.0 处理器。
首先我们需要一个可以计算输入文本中字符数的函数。我们将使用它作为报价计数方法的基础。这很容易;作为一个小复杂功能,我们将其设计为能够计算多个不同的字符:
<xsl:template name="count-chars">
<xsl:param name="input" select="." />
<xsl:param name="chars" select="$input" />
<xsl:value-of select="
string-length($input) - string-length(translate($input, $chars, ''))
" />
</xsl:template>
当使用 $input = "input A input B" 和 $chars = "AB" 调用时,它将返回 2。不带任何参数调用时,它只会返回输入的总字符串长度(默认为当前节点)。
接下来,我们需要一个能够跨一组节点计算字符数的模板。这基本上是通过迭代输入节点集并在每个节点上调用count-chars 来工作的。同样,这是递归的,以便能够计算总计:
<xsl:template name="count-chars-mutiple">
<xsl:param name="nodes" />
<xsl:param name="chars" />
<xsl:choose>
<xsl:when test="not($chars and count($nodes))">
<xsl:value-of select="0" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="c">
<xsl:call-template name="count-chars">
<xsl:with-param name="input" select="$nodes[1]" />
<xsl:with-param name="chars" select="$chars" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="d">
<xsl:call-template name="count-chars-mutiple">
<xsl:with-param name="nodes" select="$nodes[position() > 1]" />
<xsl:with-param name="chars" select="$chars" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$c + $d" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
这很简单。
当使用 $nodes = ["input A input B", "input A input C"] 和 $chars = "AB" 调用时,它将返回 3。
设置了支持函数后,我们现在可以从我的文章开头修改函数,以便能够从引用计数中获取其上下文。
为此,我们将计算所有先前兄弟文本节点中的引号,并根据该计数加上手头文本节点中的引号数来决定使用哪个引号。
例如:
<p>Here "<i>are</i> quotes <b>too</b>", and "here"</p>
-----^ -------- ~~~~~~~~~~~~~
1 2 3 4
当我们在文本最后一个文本节点 (~) 处时,会考虑带下划线的文本节点,其中第一个包含一个引号 (1),因此我们知道引号 (2) 是结束引号。 (3) 和 (4) 的处理方式与我原来的函数一样(即通过递归):
<xsl:template name="quote">
<xsl:param name="text" select="." />
<xsl:param name="old" select="'""'" />
<xsl:param name="new" select="'»«'" />
<xsl:param name="context">
<xsl:call-template name="count-chars-mutiple">
<xsl:with-param name="nodes" select="preceding-sibling::text()" />
<xsl:with-param name="chars" select="$old" />
</xsl:call-template>
</xsl:param>
<xsl:variable name="state" select="($context mod 2) + 1" />
<xsl:variable name="o" select="substring($old, $state, 1)" />
<xsl:variable name="n" select="substring($new, $state, 1)" />
<xsl:choose>
<xsl:when test="not($o and contains($text, $o))">
<xsl:value-of select="$text" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($text, $o)" />
<xsl:value-of select="$n" />
<xsl:call-template name="quote">
<xsl:with-param name="text" select="substring-after($text, $o)" />
<xsl:with-param name="old" select="$old" />
<xsl:with-param name="new" select="$new" />
<xsl:with-param name="context" select="$context + 1" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
$state 最终为 1 或 2,因此我们可以从 $new 参数中选择开始或结束引号。 $context 默认为相应的前面的引用计数,并为下一个递归步骤简单地增加。
我知道这不是很漂亮,但是当放在一起时,它会将您的输入转换为:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too</b>«</p>
</root>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:call-template name="quote" />
</xsl:template>
<xsl:template name="quote">
<xsl:param name="text" select="." />
<xsl:param name="old" select="'""'" />
<xsl:param name="new" select="'»«'" />
<xsl:param name="context">
<xsl:call-template name="count-chars-mutiple">
<xsl:with-param name="nodes" select="preceding-sibling::text()" />
<xsl:with-param name="chars" select="$old" />
</xsl:call-template>
</xsl:param>
<xsl:variable name="state" select="($context mod 2) + 1" />
<xsl:variable name="o" select="substring($old, $state, 1)" />
<xsl:variable name="n" select="substring($new, $state, 1)" />
<xsl:choose>
<xsl:when test="not($o and contains($text, $o))">
<xsl:value-of select="$text" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($text, $o)" />
<xsl:value-of select="$n" />
<xsl:call-template name="quote">
<xsl:with-param name="text" select="substring-after($text, $o)" />
<xsl:with-param name="old" select="$old" />
<xsl:with-param name="new" select="$new" />
<xsl:with-param name="context" select="$context + 1" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="count-chars">
<xsl:param name="input" select="." />
<xsl:param name="chars" select="$input" />
<xsl:value-of select="
string-length($input) - string-length(translate($input, $chars, ''))
" />
</xsl:template>
<xsl:template name="count-chars-mutiple">
<xsl:param name="nodes" />
<xsl:param name="chars" />
<xsl:choose>
<xsl:when test="not($chars and count($nodes))">
<xsl:value-of select="0" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="c">
<xsl:call-template name="count-chars">
<xsl:with-param name="input" select="$nodes[1]" />
<xsl:with-param name="chars" select="$chars" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="d">
<xsl:call-template name="count-chars-mutiple">
<xsl:with-param name="nodes" select="$nodes[position() > 1]" />
<xsl:with-param name="chars" select="$chars" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$c + $d" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:transform>
旁注:<xsl:param> 允许引用之前在同一函数中声明的参数值,以便参数可以动态计算自己的默认值。