使用XSL将引号转换为角度引号

时间:2015-04-07 13:52:24

标签: xml xslt quotes

我正在寻找一种方法,使用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中是否可行?另请注意,起始报价不必与结束报价位于同一标签中。

3 个答案:

答案 0 :(得分:4)

对于连续的字符串/文本节点,此递归函数有效:

<xsl:template name="quote">
  <xsl:param name="text" select="." />
  <xsl:param name="old" select="'&quot;&quot;'" />
  <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 ......),我们假设它是一个开头报价,否则我们假设一个收盘报价
  • 我们将假设所有相关引用都发生在兄弟级别上(没有&#34;不正确的嵌套&#34;,例如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"调用时,它将返回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() &gt; 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="'&quot;&quot;'" />
    <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>

&#13;
&#13;
<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="'&quot;&quot;'" />
    <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() &gt; 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;
&#13;
&#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,'&quot;([^\s])','»$1')"/>
    <xsl:variable name="this" select="replace($this,'([^\s])&quot;','$1«')"/>

    <!-- now, try handling &quot; at the beginning/end of text() -->
    <xsl:variable name="this">  
        <xsl:choose>
          <xsl:when test="matches($this,'^&quot;') and matches(preceding-sibling::*[1]/text(),'[^\s]$')">
            <xsl:value-of select="replace($this,'^&quot;','«')"/>
          </xsl:when>
          <xsl:when test="matches($this,'&quot;$') and matches(following-sibling::*[1]/text(),'^[^\s]')">
            <xsl:value-of select="replace($this,'^&quot;','»')"/>
          </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>