嵌套for-each xslt中的position()和last()

时间:2012-11-21 13:12:11

标签: xslt foreach nested

我有一个样式表,我正在使用的perl模块只适用于XSLT 1.0。我想在JSON字典中创建一个JSON数组,所以我需要对元素进行适当的逗号分隔。我正在解析一个XHTML表,其中第二个单元格中有一个或多个跨度。所以for-each select =“./ tr”然后for-each select =“./ td [1] / span”或类似的东西。

稍微改变之后,它的行为与Ian所说的一样。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" doctype-public="-//W3C//DTD HTML 4.01//EN"  encoding="UTF-8" />
    <xsl:template match="text()">
    </xsl:template>
    <xsl:template match="table/tbody">
        <xsl:text>[</xsl:text>
        <xsl:for-each select="./tr[not(@class='no-results')]">
            <xsl:text>{"</xsl:text>
            <xsl:value-of select="normalize-space(.//strong)" />
            <xsl:text>":{"ingredients":{</xsl:text>
            <xsl:for-each select=".//div[@class='reagent-list']//a[@class='item-link reagent']">
                <xsl:value-of select="substring(./@href, 14)" />:<xsl:value-of select="normalize-space(./span[1])" />
                <xsl:if test="position() != last()">
                    <xsl:text>,</xsl:text>
                </xsl:if>
            </xsl:for-each>
            <xsl:text>}</xsl:text>
            <xsl:if test="position() != last()">
                <xsl:text>,</xsl:text>
            </xsl:if>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>
        <xsl:text>]</xsl:text>
    </xsl:template>
</xsl:stylesheet>

我意识到样式表与下面的xml不匹配。实际文件很大。不过,我希望你理解我的意思。我刚刚做了这个:

<table>
  <thead>
    <tr>
      <th>a</th>
      <th>b</th>
      <th>c</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>foo</td>
      <td><span>an element</span></td>
      <td>bar</td>
    </tr>
    <tr>
      <td>foo</td>
      <td><span>an element</span><span>an element</span></td>
      <td>bar</td>
    </tr>
    <tr>
      <td>foo</td>
      <td><span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
      <td>bar</td>
    </tr>
  </tbody>
</table>

=&GT;

{
  "Row one":["an element"],
  "Row two":["an element", "an element"],
  "Row three":["an element", "an element", "an element", "an element"]
}

相反,我得到了这个:

{
  "Row one":["an element",],
  "Row two":["an element", "an element",],
  "Row three":["an element" "an element" "an element" "an element"]
}

我一直在测试标记中使用position()last()来打印逗号,它似乎对外循环正常工作,但是如何告诉我的测试标记使用内部打印分隔数组的逗号时的每个范围?

1 个答案:

答案 0 :(得分:2)

如@IanRoberts所述,如果不了解现有的XSLT,很难提供有针对性的帮助。

也就是说,这是一个面向推送的解决方案(即没有<xsl:for-each>),不需要last()

当这个XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:my="my"
  exclude-result-prefixes="my"
  version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes" method="text" />
  <xsl:strip-space elements="*" />

  <my:ones>
    <num>one</num>
    <num>two</num>
    <num>three</num>
    <num>four</num>
    <num>five</num>
    <num>six</num>
    <num>seven</num>
    <num>eight</num>
    <num>nine</num>
  </my:ones>

  <xsl:template match="/*">
    <xsl:text>{&#10;</xsl:text>
    <xsl:apply-templates select="tbody/tr" />
    <xsl:text>&#10;}</xsl:text>
  </xsl:template>

  <xsl:template match="tr">
    <xsl:variable name="vPos" select="position()" />
    <xsl:if test="$vPos &gt; 1">,&#10;</xsl:if>
    <xsl:text>&#09;"Row </xsl:text>
    <xsl:value-of select="document('')/*/my:ones/*[$vPos]" />
    <xsl:text>":[</xsl:text>
    <xsl:apply-templates select="td[2]/span" />
    <xsl:text>]</xsl:text>
  </xsl:template>

  <xsl:template match="span">
    <xsl:if test="position() &gt; 1">, </xsl:if>
    <xsl:value-of select="concat('&quot;', ., '&quot;')" />
  </xsl:template>

</xsl:stylesheet>

...针对提供的XML运行:

<table>
  <thead>
    <tr>
      <th>a</th>
      <th>b</th>
      <th>c</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>foo</td>
      <td>
        <span>an element</span></td>
      <td>bar</td>
    </tr>
    <tr>
      <td>foo</td>
      <td>
        <span>an element</span><span>an element</span></td>
      <td>bar</td>
    </tr>
    <tr>
      <td>foo</td>
      <td>
        <span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
      <td>bar</td>
    </tr>
  </tbody>
</table>

...生成了想要的结果:

{
  "Row one":["an element"],
  "Row two":["an element", "an element"],
  "Row three":["an element", "an element", "an element", "an element"]
}

<强>解释

  • 第一个模板与根元素匹配。其目的是将模板应用于该元素的<tr>孙子,并将这些结果夹在{}之间(根据需要添加换行符)。

  • 第二个模板匹配<tr>个元素。它输出行信息,并被指示将模板应用于第二个<span>元素的所有<td>个子元素(同样,根据需要将结果夹在大括号和其他文本之间)。

    • 注意:而不是使用last(),您会看到第一个元素输出一个逗号,后跟一个换行符,如果此{{1}的位置在当前上下文中大于1.这与正确应用逗号具有相同的效果;它只是一种看待同一问题的不同方式(也就是我使用它,因为它对我来说似乎更有效;))。

    • 注意:为了使此解决方案更具可扩展性,您会发现我并非静态输出单词<tr>"one"等。每一行。相反,在XSLT的顶部,我已经定义了一个"two"元素来保存每个“ones”数字的文本值。在处理每个<my:ones>时,我使用当前上下文中<tr>的位置来检索正确的<tr>元素的值。 我已将其作为练习留给读者,但确实可以定义<num><my:tens>等来将此解决方案扩展到可能的大量行。< /强>

  • 最终模板与<my:hundreds>元素匹配。同样,它使用<span>元素来测试当前上下文中此<xsl:if>的位置是否大于1;如果是,则输出逗号(后跟空格)。在那之后,我们只将两个<span>符号连接起来,并将跨度夹在中间。