我正在寻找一种方法,使用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中是否可行?另请注意,起始报价不必与结束报价位于同一标签中。
答案 0 :(得分:4)
对于连续的字符串/文本节点,此递归函数有效:
<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>
)的一部分,那么事情就会复杂一些。
要在非连续文本节点上实现非对称引用(不同的开启和关闭引号),我们需要做出一些假设:
text "text <x>"</x>
,其中结束引用位于不同的嵌套级别)。首先,我们需要一个可以计算输入文本中字符的函数。我们将把它作为我们报价计数方法的基础。这很容易;作为一个小的复杂功能,我们设计它可以计算多个不同的字符:
<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),所以我们知道quote(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>
&#13;
旁注:<xsl:param>
允许引用先前在同一函数中声明的params的值,以便参数可以动态计算它们自己的默认值。
答案 1 :(得分:1)
我会尝试像Text-Processors那样设置印刷正确的引号:如果有一个字符跟随,我们有开头的引用。如果一个角色出现,我们有一个结束。
在下面的XSLT中,我展示了一个解决方案,我只是在报价之前或之后寻找空白区域。它不是所有情况下的解决方案(想想标点符号或类似的东西)并且它与人们可以想到的所有情况都不匹配,但它可能对您的用例有所帮助 - 至少您的示例打印得很好:
<?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])','»$1')"/>
<xsl:variable name="this" select="replace($this,'([^\s])"','$1«')"/>
<!-- 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>
答案 2 :(得分:1)
解决方案1 :
这是一个转型,比Tomalak的(非常好的)解决方案少了20%的模板(4比5),减少了19%的代码(68(或Sol.2中的63) )与81行)。不使用<xsl:choose>
,<xsl:when>
,<xsl:otherwise>
或translate()
。任何模板的最大参数数量为3,而在解决方案2中,此参数的最大数量为2(vs.4)。简单来说,我相信这两个转换更简单:
<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>