使用 XSL 将引号转换为斜引号
Transform quotes to angle quotes using XSL
我正在寻找一种使用 XSL 将 XML 文件中的引号自动转换为斜引号的方法。
示例XML:
<root>
<p>There "are" quotes</p>
<p>Here "are quotes <b>too</b>"</p>
</root>
这应该转换为:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too</b>«</p>
</root>
这在 XSL 中可行吗?另请注意,起始引语不需要与结束引语位于同一标签中。
对于连续的strings/text个节点,这个递归函数有效:
<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 ...),我们假设它是一个开场白,否则我们假设一个收盘价。
- 我们假设所有相关引用都发生在同级级别(没有 "incorrect nesting",例如
text "text <x>"</x>
,其中结束引号位于不同的嵌套级别)。
- 输入的引号必须正确,否则我们的计数将被取消。
- 为了获得最大的兼容性,我将采用普通的 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"
调用时,它会 return 2. 在没有任何参数的情况下调用它只会 return 输入的总字符串长度(默认到当前节点)。
接下来我们需要一个能够计算一组节点中的字符的模板。这基本上是通过迭代节点的输入集并在每个节点上调用 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"
调用时,它将 return 3.
设置支持函数后,我们现在可以从 post 的开头修改函数,以便能够从引用计数中获取其上下文。
为此,我们将计算所有前同级文本节点中的引号,并根据该计数加上手头文本节点中的引号数来决定使用哪个引号。
例如:
<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>
允许引用先前在同一函数中声明的参数值,以便参数可以动态计算自己的默认值。
我会尝试做一些像文本处理器那样的事情来设置排版正确的引号:如果后面有一个字符,我们就有了开头引号。如果一个字符在前面,我们有一个结束字符。
在下面的 XSLT 中,我展示了一个解决方案,我只是在引号前后寻找 white-space。它不是适用于所有情况的解决方案(想想标点符号或类似的东西)并且它不匹配人们能想到的所有情况,但它可能对您的用例有所帮助 - 至少您的示例打印得很好:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="@*|*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:variable name="this" select="."/>
<!-- the easy ones: -->
<xsl:variable name="this" select="replace($this,'"([^\s])','»')"/>
<xsl:variable name="this" select="replace($this,'([^\s])"','«')"/>
<!-- now, try handling " at the beginning/end of text() -->
<xsl:variable name="this">
<xsl:choose>
<xsl:when test="matches($this,'^"') and matches(preceding-sibling::*[1]/text(),'[^\s]$')">
<xsl:value-of select="replace($this,'^"','«')"/>
</xsl:when>
<xsl:when test="matches($this,'"$') and matches(following-sibling::*[1]/text(),'^[^\s]')">
<xsl:value-of select="replace($this,'^"','»')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$this"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$this"/>
</xsl:template>
</xsl:stylesheet>
解决方案 1:
这是一个转换,它比 Tomalak 的(非常好的)解决方案少了 20% 的模板(4 对 5),代码少了 19%(68(或Sol.2 中的 63)对比 81 行)。没有使用 <xsl:choose>
、<xsl:when>
、<xsl:otherwise>
或 translate()
。任何模板的最大参数数为 3,而在解决方案 2 中,最大参数数为 2(对 4)。简单来说,我相信这2个转换更简单:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vQ">"</xsl:variable>
<xsl:variable name="vShevrons" select="'»«'"/>
<xsl:variable name="vReplacedText">
<xsl:call-template name="replQuotes"/>
</xsl:variable>
<xsl:variable name="vNodeOffsets">
<xsl:call-template name="getTextOffsets"/>
<xsl:text>|</xsl:text>
</xsl:variable>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template name="replQuotes">
<xsl:param name="pText" select="."/>
<xsl:param name="pOddness" select="0"/>
<xsl:value-of select='substring-before(concat($pText, $vQ), $vQ)'/>
<xsl:variable name="vRemaining" select="substring-after($pText, $vQ)"/>
<xsl:if test="contains($pText, $vQ)">
<xsl:variable name="vShevInd" select="$pOddness + 1"/>
<xsl:value-of select="substring($vShevrons, $vShevInd, 1)"/>
<xsl:call-template name="replQuotes">
<xsl:with-param name="pText" select="$vRemaining"/>
<xsl:with-param name="pOddness" select="$vShevInd mod 2"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="text()[true()]">
<xsl:variable name="vInd" select="count(preceding::text()) +1"/>
<xsl:variable name="vStartMarker" select="concat('|N', $vInd, '|')"/>
<xsl:variable name="vOffset" select="substring-before(substring-after($vNodeOffsets, $vStartMarker), '|')"/>
<xsl:value-of select="substring($vReplacedText, $vOffset, string-length())"/>
</xsl:template>
<xsl:template name="getTextOffsets">
<xsl:param name="pNodes" select="//text()"/>
<xsl:param name="pNodeInd" select="1"/>
<xsl:param name="pAccumLength" select="0"/>
<xsl:if test="$pNodes">
<xsl:variable name="vNodeLength" select="string-length($pNodes[1])"/>
<xsl:value-of select="concat('|N', $pNodeInd, '|')"/>
<xsl:variable name="vNewAccum" select="$pAccumLength+$vNodeLength"/>
<xsl:value-of select="$pAccumLength+1"/>
<xsl:call-template name="getTextOffsets">
<xsl:with-param name="pNodes" select="$pNodes[position() > 1]"/>
<xsl:with-param name="pNodeInd" select="$pNodeInd+1"/>
<xsl:with-param name="pAccumLength" select="$vNewAccum"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
当此转换应用于最初提供的源 XML 文档时:
<root>
<p>There "are" quotes</p>
<p>Here "are quotes <b>too</b>"</p>
</root>
产生想要的正确结果:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too</b>«</p>
</root>
解决方案 2:
如果使用的 XSLT 1.0 处理器实现了 xxx:node-set()
扩展函数,则存在更简单和更短的解决方案:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vQ">"</xsl:variable>
<xsl:variable name="vShevrons" select="'»«'"/>
<xsl:variable name="vReplacedText">
<xsl:call-template name="replQuotes"/>
</xsl:variable>
<xsl:variable name="vrtfChunks">
<xsl:call-template name="getTextChunks"/>
</xsl:variable>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template name="replQuotes">
<xsl:param name="pText" select="."/>
<xsl:param name="pOddness" select="0"/>
<xsl:value-of select='substring-before(concat($pText, $vQ), $vQ)'/>
<xsl:variable name="vRemaining" select="substring-after($pText, $vQ)"/>
<xsl:if test="contains($pText, $vQ)">
<xsl:variable name="vShevInd" select="$pOddness + 1"/>
<xsl:value-of select="substring($vShevrons, $vShevInd, 1)"/>
<xsl:call-template name="replQuotes">
<xsl:with-param name="pText" select="$vRemaining"/>
<xsl:with-param name="pOddness" select="$vShevInd mod 2"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="text()[true()]">
<xsl:variable name="vInd" select="count(preceding::text()) +1"/>
<xsl:value-of select="ext:node-set($vrtfChunks)/*[position()=$vInd]"/>
</xsl:template>
<xsl:template name="getTextChunks">
<xsl:param name="pNodes" select="//text()"/>
<xsl:param name="pTextOffset" select="1"/>
<xsl:if test="$pNodes">
<xsl:variable name="vNodeLength" select="string-length($pNodes[1])"/>
<chunk>
<xsl:value-of select="substring($vReplacedText, $pTextOffset, $vNodeLength)"/>
</chunk>
<xsl:call-template name="getTextChunks">
<xsl:with-param name="pNodes" select="$pNodes[position() > 1]"/>
<xsl:with-param name="pTextOffset" select="$pTextOffset + $vNodeLength"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
重要说明:如果文档的文本节点并非都是兄弟节点,则这两种解决方案都会产生正确的结果。请注意,在这种情况下,Tomalak 的转换不会产生正确的结果。
让我们获取此来源 XML 文档:
<root>
<p>There "are" quotes</p>
<p>Here "are quotes <b>too " "yes?</b>"</p>
</root>
上面的解决方案 1 和解决方案 2 都产生了正确的结果:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too « »yes?</b>«</p>
</root>
Tomalak 答案的转换产生了这个不正确的结果:
<?xml version="1.0" encoding="utf-8"?>
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too » «yes?</b>«</p>
</root>
我正在寻找一种使用 XSL 将 XML 文件中的引号自动转换为斜引号的方法。
示例XML:
<root>
<p>There "are" quotes</p>
<p>Here "are quotes <b>too</b>"</p>
</root>
这应该转换为:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too</b>«</p>
</root>
这在 XSL 中可行吗?另请注意,起始引语不需要与结束引语位于同一标签中。
对于连续的strings/text个节点,这个递归函数有效:
<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 ...),我们假设它是一个开场白,否则我们假设一个收盘价。
- 我们假设所有相关引用都发生在同级级别(没有 "incorrect nesting",例如
text "text <x>"</x>
,其中结束引号位于不同的嵌套级别)。 - 输入的引号必须正确,否则我们的计数将被取消。
- 为了获得最大的兼容性,我将采用普通的 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"
调用时,它会 return 2. 在没有任何参数的情况下调用它只会 return 输入的总字符串长度(默认到当前节点)。
接下来我们需要一个能够计算一组节点中的字符的模板。这基本上是通过迭代节点的输入集并在每个节点上调用 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"
调用时,它将 return 3.
设置支持函数后,我们现在可以从 post 的开头修改函数,以便能够从引用计数中获取其上下文。
为此,我们将计算所有前同级文本节点中的引号,并根据该计数加上手头文本节点中的引号数来决定使用哪个引号。
例如:
<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>
允许引用先前在同一函数中声明的参数值,以便参数可以动态计算自己的默认值。
我会尝试做一些像文本处理器那样的事情来设置排版正确的引号:如果后面有一个字符,我们就有了开头引号。如果一个字符在前面,我们有一个结束字符。
在下面的 XSLT 中,我展示了一个解决方案,我只是在引号前后寻找 white-space。它不是适用于所有情况的解决方案(想想标点符号或类似的东西)并且它不匹配人们能想到的所有情况,但它可能对您的用例有所帮助 - 至少您的示例打印得很好:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="@*|*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:variable name="this" select="."/>
<!-- the easy ones: -->
<xsl:variable name="this" select="replace($this,'"([^\s])','»')"/>
<xsl:variable name="this" select="replace($this,'([^\s])"','«')"/>
<!-- now, try handling " at the beginning/end of text() -->
<xsl:variable name="this">
<xsl:choose>
<xsl:when test="matches($this,'^"') and matches(preceding-sibling::*[1]/text(),'[^\s]$')">
<xsl:value-of select="replace($this,'^"','«')"/>
</xsl:when>
<xsl:when test="matches($this,'"$') and matches(following-sibling::*[1]/text(),'^[^\s]')">
<xsl:value-of select="replace($this,'^"','»')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$this"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$this"/>
</xsl:template>
</xsl:stylesheet>
解决方案 1:
这是一个转换,它比 Tomalak 的(非常好的)解决方案少了 20% 的模板(4 对 5),代码少了 19%(68(或Sol.2 中的 63)对比 81 行)。没有使用 <xsl:choose>
、<xsl:when>
、<xsl:otherwise>
或 translate()
。任何模板的最大参数数为 3,而在解决方案 2 中,最大参数数为 2(对 4)。简单来说,我相信这2个转换更简单:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vQ">"</xsl:variable>
<xsl:variable name="vShevrons" select="'»«'"/>
<xsl:variable name="vReplacedText">
<xsl:call-template name="replQuotes"/>
</xsl:variable>
<xsl:variable name="vNodeOffsets">
<xsl:call-template name="getTextOffsets"/>
<xsl:text>|</xsl:text>
</xsl:variable>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template name="replQuotes">
<xsl:param name="pText" select="."/>
<xsl:param name="pOddness" select="0"/>
<xsl:value-of select='substring-before(concat($pText, $vQ), $vQ)'/>
<xsl:variable name="vRemaining" select="substring-after($pText, $vQ)"/>
<xsl:if test="contains($pText, $vQ)">
<xsl:variable name="vShevInd" select="$pOddness + 1"/>
<xsl:value-of select="substring($vShevrons, $vShevInd, 1)"/>
<xsl:call-template name="replQuotes">
<xsl:with-param name="pText" select="$vRemaining"/>
<xsl:with-param name="pOddness" select="$vShevInd mod 2"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="text()[true()]">
<xsl:variable name="vInd" select="count(preceding::text()) +1"/>
<xsl:variable name="vStartMarker" select="concat('|N', $vInd, '|')"/>
<xsl:variable name="vOffset" select="substring-before(substring-after($vNodeOffsets, $vStartMarker), '|')"/>
<xsl:value-of select="substring($vReplacedText, $vOffset, string-length())"/>
</xsl:template>
<xsl:template name="getTextOffsets">
<xsl:param name="pNodes" select="//text()"/>
<xsl:param name="pNodeInd" select="1"/>
<xsl:param name="pAccumLength" select="0"/>
<xsl:if test="$pNodes">
<xsl:variable name="vNodeLength" select="string-length($pNodes[1])"/>
<xsl:value-of select="concat('|N', $pNodeInd, '|')"/>
<xsl:variable name="vNewAccum" select="$pAccumLength+$vNodeLength"/>
<xsl:value-of select="$pAccumLength+1"/>
<xsl:call-template name="getTextOffsets">
<xsl:with-param name="pNodes" select="$pNodes[position() > 1]"/>
<xsl:with-param name="pNodeInd" select="$pNodeInd+1"/>
<xsl:with-param name="pAccumLength" select="$vNewAccum"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
当此转换应用于最初提供的源 XML 文档时:
<root>
<p>There "are" quotes</p>
<p>Here "are quotes <b>too</b>"</p>
</root>
产生想要的正确结果:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too</b>«</p>
</root>
解决方案 2:
如果使用的 XSLT 1.0 处理器实现了 xxx:node-set()
扩展函数,则存在更简单和更短的解决方案:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vQ">"</xsl:variable>
<xsl:variable name="vShevrons" select="'»«'"/>
<xsl:variable name="vReplacedText">
<xsl:call-template name="replQuotes"/>
</xsl:variable>
<xsl:variable name="vrtfChunks">
<xsl:call-template name="getTextChunks"/>
</xsl:variable>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template name="replQuotes">
<xsl:param name="pText" select="."/>
<xsl:param name="pOddness" select="0"/>
<xsl:value-of select='substring-before(concat($pText, $vQ), $vQ)'/>
<xsl:variable name="vRemaining" select="substring-after($pText, $vQ)"/>
<xsl:if test="contains($pText, $vQ)">
<xsl:variable name="vShevInd" select="$pOddness + 1"/>
<xsl:value-of select="substring($vShevrons, $vShevInd, 1)"/>
<xsl:call-template name="replQuotes">
<xsl:with-param name="pText" select="$vRemaining"/>
<xsl:with-param name="pOddness" select="$vShevInd mod 2"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="text()[true()]">
<xsl:variable name="vInd" select="count(preceding::text()) +1"/>
<xsl:value-of select="ext:node-set($vrtfChunks)/*[position()=$vInd]"/>
</xsl:template>
<xsl:template name="getTextChunks">
<xsl:param name="pNodes" select="//text()"/>
<xsl:param name="pTextOffset" select="1"/>
<xsl:if test="$pNodes">
<xsl:variable name="vNodeLength" select="string-length($pNodes[1])"/>
<chunk>
<xsl:value-of select="substring($vReplacedText, $pTextOffset, $vNodeLength)"/>
</chunk>
<xsl:call-template name="getTextChunks">
<xsl:with-param name="pNodes" select="$pNodes[position() > 1]"/>
<xsl:with-param name="pTextOffset" select="$pTextOffset + $vNodeLength"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
重要说明:如果文档的文本节点并非都是兄弟节点,则这两种解决方案都会产生正确的结果。请注意,在这种情况下,Tomalak 的转换不会产生正确的结果。
让我们获取此来源 XML 文档:
<root>
<p>There "are" quotes</p>
<p>Here "are quotes <b>too " "yes?</b>"</p>
</root>
上面的解决方案 1 和解决方案 2 都产生了正确的结果:
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too « »yes?</b>«</p>
</root>
Tomalak 答案的转换产生了这个不正确的结果:
<?xml version="1.0" encoding="utf-8"?>
<root>
<p>There »are« quotes</p>
<p>Here »are quotes <b>too » «yes?</b>«</p>
</root>