XPath表达式(在XSLT中)以匹配节点的兄弟节点,直到基于其子节点的兄弟节满足条件

时间:2012-04-16 22:43:27

标签: xml xslt xpath

我有以下标记,一个包含TR和一个或两个子TD的表格

  <table>
    <tr>
      <td>
         <p>XXX</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>XXX</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>XXX</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
    <tr>
      <td>
        <p>YYYYYY</p>
      </td>
      <td>
        <p>YYYYYY</p>
      </td>
    </tr>
  </table>

我正在尝试使用XSLT 1.0将其转换为

<AAA>
  XXX
  <BBB>YYYYY</BBB>
  <BBB>YYYYY</BBB>
  ...
</AAA>
<AAA>
  XXX
  <BBB>YYYYY</BBB>
  <BBB>YYYYY</BBB>
  ...
</AAA>
...

从这个事实中抽象出来这可能是在XSLT中接近嵌套循环的最佳方法,XPath表达式放入了什么?下面将选择当前TR(XXX)的所有TR兄弟,它们有两个TD子节点,但只到下一个TR(XXX)。我根据他们有多少TD孩子来区分XXX和YYYYY的节点。 1 = XXX,2 = YYYYY。

<xsl:for-each select="//table/tr">
   <xsl:if test="count(td) = '1'">
      XXX
   </xsl:if>
   <xsl:for-each select="???">
      all YYYYY up until the next XXX
   </xsl:for-each>
</xsl:for-each>

我有“follow-sibling :: tr [child :: td [following-sibling :: td]”但是匹配所有以下YYYYY到最后 - 我如何让它只选择那些直到下一个TR与XXX?

谢谢!

2 个答案:

答案 0 :(得分:1)

<强>予。 XSLT 1.0解决方案:

<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:key name="kFollowing" match="tr[td[2]]"
  use="generate-id(preceding-sibling::tr
                                  [not(td[2])]
                                     [1]
                   )"/>

 <xsl:template match="/*">
   <xsl:apply-templates select="tr[not(td[2])]"/>
 </xsl:template>

 <xsl:template match="tr[not(td[2])]">
  <AAA>
    <xsl:value-of select="concat('&#xA;', td/p, '&#xA;')"/>

    <xsl:apply-templates select="key('kFollowing', generate-id())"/>
  </AAA>
 </xsl:template>

 <xsl:template match="p">
  <BBB><xsl:value-of select="."/></BBB>
 </xsl:template>
</xsl:stylesheet>

在提供的XML文档上应用此XSLT 1.0转换

<table>
    <tr>
        <td>
            <p>XXX</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>XXX</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>XXX</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
    <tr>
        <td>
            <p>YYYYYY</p>
        </td>
        <td>
            <p>YYYYYY</p>
        </td>
    </tr>
</table>

生成想要的正确结果

<AAA>
XXX
<BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
</AAA>
<AAA>
XXX
<BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
</AAA>
<AAA>
XXX
<BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
</AAA>

<强>解释

正确使用 key 来定义具有第二个tr子项的所有td作为 generate-id()的函数前一个单子tr元素。


<强> II。 XSLT 2.0解决方案:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
  <xsl:for-each-group group-starting-with="tr[not(td[2])]"
       select="tr">
    <xsl:apply-templates select="current-group()[1]"/>
  </xsl:for-each-group>
 </xsl:template>

 <xsl:template match="tr[not(td[2])]">
  <AAA>
    <xsl:value-of select="concat('&#xA;', td/p, '&#xA;')"/>

    <xsl:apply-templates select="current-group()[position() gt 1]"/>
  </AAA>
 </xsl:template>

 <xsl:template match="p">
  <BBB><xsl:value-of select="."/></BBB>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于同一XML文档(上图)时,会产生相同的正确结果

<AAA>
XXX
<BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
</AAA>
<AAA>
XXX
<BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
</AAA>
<AAA>
XXX
<BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
   <BBB>YYYYYY</BBB>
</AAA>

<强>解释

适当使用带有group-starting-with属性的 xsl:for-each-group current-group() 功能。

答案 1 :(得分:0)

我一直认为让XPath结构完全符合你的要求是有用的:序列中的所有项目(包括/排除)匹配某些条件的第一项。我已经考虑过这样的语法,例如expr until condition。 Saxon和EXSLT尝试提供更高阶的扩展函数来满足要求,例如saxon:leading()。可悲的是,规范中没有任何内容;虽然在XPath 3.0中,您可以使用高阶函数轻松完成。

您有时可以使用分组来解决问题(xsl:for-each-group),但通常最简单和最优雅的方法是使用递归:即编写一个处理序列中第一个项目的函数,如果某些条件成立,则调用自己处理下一个项目。

另一种方法是在序列中搜索与条件匹配的第一个节点,然后使用subsequence()选择到该位置的节点:

<xsl:variable name="positions" select="
  for $i in 1 to count($seq)
  return if (my:condition($seq[$i])) then $i else ()"/>
<xsl:apply-templates select="subsequence($seq, 1, $positions[1])"/>