使用特定标记名称匹配当前节点和下一个节点之间的节点

时间:2014-05-05 15:36:19

标签: xml xslt xpath xslt-1.0

我通过以下方式构建XML:

<content>
    <sh1>A</sh1>
    <sh2>A1</sh2>
    <sh2>A2</sh2>
    <sh1>B</sh1>
    <sh2>B1</sh2>
    <sh2>B2</sh2>
    <sh2>B3</sh2>
    <sh1>C</sh1>
    <sh2>C1</sh2>
    <sh2>C2</sh2>
    <sh1>D</sh1>
    <sh2>D1</sh2>
    <sh2>D2</sh2>
    <sh2>D3</sh2>
    <sh2>D4</sh2>
</content>

正如您所看到的,这里有两个关注的标记名称:sh1sh2。这些表示标题和子标题,我需要通过嵌套它们的内容在输出XML中组织它们(您可以看到示例输出here)。到目前为止,我的方法是匹配每个sh1,然后尝试匹配它与下一个sh2之间的每个sh1

我发现了this超级有用的问题,它让我(我希望)大部分都在这里。我把以下的XSLT(1.0)放在一起:

<xsl:template match="content">
    <ul>
        <xsl:apply-templates select="//sh1" />
    </ul>
</xsl:template>

<xsl:template match="sh1">
    <li>
        <h1><xsl:value-of select="." /></h1>
        <ul>
            <xsl:apply-templates select="./following-sibling::sh1[1]/preceding-sibling::sh2[preceding-sibling::.]" />
        </ul>
    </li>
</xsl:template>

<xsl:template match="sh2">
    <li><xsl:value-of select="." /></li>
</xsl:template>

问题(可预测)是我不能使用./following-sibling::sh1[1]/preceding-sibling::sh2[preceding-sibling::.]使用.表示sh2应该将当前节点作为前一个兄弟。我可以使用其他一些关键字吗?或者或许另一种方法完全是为了得到我想要的嵌套结构?

2 个答案:

答案 0 :(得分:2)

我相信使用是最方便的方法:

XSLT 1.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="sh2" match="sh2" use="generate-id(preceding-sibling::sh1[1])" />

<xsl:template match="/content">
    <ul>
        <xsl:apply-templates select="sh1" />
    </ul>
</xsl:template>

<xsl:template match="sh1">
    <li>
        <h1><xsl:value-of select="." /></h1>
        <ul>
            <xsl:apply-templates select="key('sh2', generate-id())" />
        </ul>
    </li>
</xsl:template>

<xsl:template match="sh2">
    <li><xsl:value-of select="." /></li>
</xsl:template>

</xsl:stylesheet>

答案 1 :(得分:1)

在XSLT 2.0中,我会使用for-each-group来解决问题:

<xsl:template match="content">
    <ul>
        <xsl:for-each-group select="*" group-starting-with="sh1">
            <li>
                <h1><xsl:value-of select="." /></h1>
                <xsl:if test="current-group()[2]">
                    <ul>
                        <xsl:apply-templates select="current-group() except ." />
                    </ul>
                </xsl:if>
            </li>
        </xsl:for-each-group>
    </ul>
</xsl:template>

<xsl:template match="sh2">
    <li><xsl:value-of select="." /></li>
</xsl:template>

如果您被限制为1.0,那么基于密钥的方法as suggested by michael.hor257k可能是最有效的,但对现有方法的最简单修复就像

<xsl:apply-templates select="following-sibling::sh2[
     generate-id(preceding-sibling::sh1[1]) = generate-id(current())]" />

preceding-sibling::sh1[1]是我们当时正在过滤的节点的前一个sh1current()是模板正在运行的sh1元素。比较它们的generate-id值是XSLT 1.0方法来确定两个表达式是否选择相同的节点(如果你只是比较了值而不是它们生成的ID,那么你就会将误报作为两个不同的风险具有相同文本的sh1个元素将被视为相等。在2.0中,您可以使用is运算符执行此操作(preceding-sibling::sh1[1] is current())。

或者,您可以使用我所听到的称为“兄弟递归”的技术来模拟“while循环”:将模板应用于此标题下的第一个sh2,并将该模板递归到下一个兄弟,依此类推,直到我们用完了相应的节点:

<ul>
    <xsl:apply-templates select="following-sibling::*[1][self::sh2]" />
</ul>

和模板:

<xsl:template match="sh2">
    <li><xsl:value-of select="." /></li>
    <xsl:apply-templates select="following-sibling::*[1][self::sh2]" />
</xsl:template>

只要following-sibling::*[1]不是sh2元素,“循环”就会终止。