XSL - 如何匹配连续的逗号分隔标记

时间:2011-06-24 15:13:39

标签: xslt xpath

我正在尝试匹配一系列以逗号分隔的xml标记,然后对整个节点组加上文本应用xslt转换。例如,给定以下部分XML:

<p>Some text here
    <xref id="1">1</xref>,
    <xref id="2">2</xref>,
    <xref id="3">3</xref>.
</p>

我想最终:

<p>Some text here <sup>1,2,3</sup>.</p>

此时,一个更加混乱的替代方案也是可以接受的:

<p>Some text here <sup>1</sup><sup>,</sup><sup>2</sup><sup>,</sup><sup>3</sup>.</p>

我有从单个外部参照到sup的转换:

<xsl:template match="xref"">
    <sup><xsl:apply-templates/></sup>
</xsl:template>

但是我对如何匹配用逗号分隔的一组节点感到茫然。

感谢。

5 个答案:

答案 0 :(得分:3)

更新:感谢@ Flynn1179提醒我解决方案没有产生完全想要的输出,我稍微修改了它。 现在生成了想要的“好”格式

此XSLT 1.0转换

<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes"/>

     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()[1]|@*"/>
      </xsl:copy>
      <xsl:apply-templates select="following-sibling::node()[1]"/>
     </xsl:template>

     <xsl:template match=
     "xref[not(preceding-sibling::node()[1]
                  [self::text() and starts-with(.,',')]
               )
          ]">

      <xsl:variable name="vBreakText" select=
      "following-sibling::text()[not(starts-with(.,','))][1]"/>

      <xsl:variable name="vPrecedingTheBreak" select=
       "$vBreakText/preceding-sibling::node()"/>

      <xsl:variable name="vFollowing" select=
      ".|following-sibling::node()"/>

      <xsl:variable name="vGroup" select=
      "$vFollowing[count(.|$vPrecedingTheBreak)
                  =
                   count($vPrecedingTheBreak)
                  ]
      "/>

      <sup>
       <xsl:apply-templates select="$vGroup" mode="group"/>
      </sup>
      <xsl:apply-templates select="$vBreakText"/>
     </xsl:template>

     <xsl:template match="text()" mode="group">
       <xsl:value-of select="normalize-space()"/>
     </xsl:template>
</xsl:stylesheet>

应用于以下XML文档(基于提供的文档,但更加复杂和有趣):

<p>Some text here    
    <xref id="1">1</xref>,    
    <xref id="2">2</xref>,    
    <xref id="3">3</xref>.
    <ttt/>
    <xref id="4">4</xref>,
    <xref id="5">5</xref>,
    <xref id="6">6</xref>.
    <zzz/>
</p>

产生完全正确的结果

<p>Some text here        
    <sup>1,2,3</sup>.    
    <ttt/>
    <sup>4,5,6</sup>.    
    <zzz/>
</p>

<强>解释

  1. 我们使用“细粒度”身份规则,按规则处理文档逐节点并按“原样”复制匹配的节点

  2. 我们使用匹配任何xref元素的模板覆盖标识规则,该元素是xref元素组中的第一个元素,每个元素(但最后一个)后面跟着以','字符开头的直接text-node-sibling。在这里,我们找到第一个破坏规则的text-node-sibling(它的起始字符不是','。

  3. 然后我们使用Kayessian(在@Michael Kay之后)公式找到组中的所有节点,用于两个节点集的交集。此公式为:$ns1[count(.|$ns2) = count($ns2)]

  4. 然后我们以名为“group”的模式处理组中的所有节点。

  5. 最后,我们将模板(以匿名模式)应用于中断文本节点(即该组后面的第一个节点),以便继续处理链。

答案 1 :(得分:2)

第二种替代方案可以通过

实现
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="@* | node()">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="p/text()[normalize-space() = ',' and preceding-sibling::node()[1][self::xref]]">
  <sup>,</sup>
</xsl:template>

<xsl:template match="xref">
  <sup>
    <xsl:apply-templates/>
  </sup>
</xsl:template>

</xsl:stylesheet>

答案 2 :(得分:2)

有趣的问题。 1。

这是一个XSLT 2.0解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   exclude-result-prefixes="xs"
   version="2.0">
   <xsl:variable name="comma-regex">^\s*,\s*$</xsl:variable>

   <!-- Identity transform -->
   <xsl:template match="@* | node()">
      <xsl:copy>
         <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
   </xsl:template>

   <!-- Don't directly process xrefs that are second or later in a comma-separated series.
      Note that this template has a higher default priority than the following one,
      because of the predicate. -->
   <xsl:template match="xref[preceding-sibling::node()[1]/
      self::text()[matches(., $comma-regex)]/
      preceding-sibling::*[1]/self::xref]" />

   <!-- Don't directly process comma text nodes that are in the middle of a series. -->
   <xsl:template match="text()[matches(., $comma-regex) and
      preceding-sibling::*[1]/self::xref and following-sibling::*[1]/self::xref]" />

   <!-- for xrefs that first (or solitary) in a comma-separated series: -->
   <xsl:template match="xref">
      <sup>
         <xsl:call-template name="process-xref-series">
            <xsl:with-param name="next" select="." />
         </xsl:call-template>
      </sup>
   </xsl:template>

   <xsl:template name="process-xref-series">
      <xsl:param name="next"/>
      <xsl:if test="$next">
         <xsl:value-of select="$next"/>
         <xsl:variable name="followingXref"
            select="$next/following-sibling::node()[1]/
                     self::text()[matches(., $comma-regex)]/
                     following-sibling::*[1]/self::xref"/>
         <xsl:if test="$followingXref">
            <xsl:text>,</xsl:text>
            <xsl:call-template name="process-xref-series">
               <xsl:with-param name="next" select="$followingXref"/>
            </xsl:call-template>
         </xsl:if>         
      </xsl:if>

   </xsl:template>
</xsl:stylesheet>

(如果我们可以对输入做出一些假设,这可以简化。)

针对您提供的示例输入运行,结果为:

<p>Some text here
   <sup>1,2,3</sup>.
</p>

答案 3 :(得分:2)

对于你的“凌乱的替代方案”,有一个几乎无足轻重的解决方案:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="xref">
  <sup>
    <xsl:apply-templates />
  </sup>
</xsl:template>

<xsl:template match="text()[normalize-space(.)=',']">
  <sup>,</sup>
</xsl:template>

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*| node()" />
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>
编辑:我刚刚注意到它几乎是Martin解决方案的一个克隆,除非在逗号上没有额外检查前面的外部参照元素。他可能更安全:))

对您的首选结果稍微不那么简单的解决方案,尽管只有在任何xref标记中只有一个p标记集合时才有效。你没有提到多个集合的可能性,即使有,我也认为它们不太可能属于同一个包含p标签。如果可能发生这种情况,可以进一步扩展它以实现这一点,尽管它会变得更加复杂。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="xref[not(preceding-sibling::text()[normalize-space(.)=','])]">
  <sup>
    <xsl:value-of select="." />
    <xsl:for-each select="following-sibling::text() | following-sibling::xref">
      <xsl:if test="following-sibling::text()[substring(.,1,1)='.']">
        <xsl:value-of select="normalize-space(.)" />
      </xsl:if>
    </xsl:for-each>
  </sup>
</xsl:template>

<xsl:template match="xref | text()[normalize-space(.)=',']" />

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*| node()" />
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

答案 4 :(得分:1)

如果您可以使用XSLT 2.0(例如使用Saxon 9或AltovaXML或XQSharp),那么这是一个XSLT 2.0解决方案,它应该产生您要求的第一个输出:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="@* | node()">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="p">
  <xsl:for-each-group select="node()" group-adjacent="self::xref or self::text()[normalize-space() = ',']">
    <xsl:choose>
      <xsl:when test="current-grouping-key()">
        <sup>
          <xsl:value-of select="current-group()/normalize-space()" separator=""/>
        </sup>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="current-group()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>