xsl / xpath选择兄弟姐妹,但不是下一个类似的兄弟姐妹

时间:2015-03-26 12:20:07

标签: xml xslt xpath

这是我在StackExchange的第一篇文章,如果我做错了,请耐心等待:

我有一个XML文件,该文件派生自产品数据库,除了元素的顺序外,所有分组信息都丢失了。所有产品都有一个首先出现的商品编号元素,后面是未知数量的其他元素,直到下一个产品以新的商品编号元素开头,如下所示:

<?xml version="1.0" encoding="ISO-8859-1"?>
<Envelope>
    <body>
        <products>
            <ARTNO>10-0001</ARTNO>
            <LEVARTNO>K01-300</LEVARTNO>
            <EAN></EAN>
            <WEBGRUPP1>200</WEBGRUPP1>
            <ARTNO>10C0414</ARTNO>
            <LEVARTNO>0505-0906</LEVARTNO>
            <EAN></EAN>
            <WEBGRUPP1>701</WEBGRUPP1>
            <WEBGRUPP2></WEBGRUPP2>
        </products>
    </body>
</Envelope>

我需要将其重组为:

<?xml version="1.0" encoding="ISO-8859-1"?>
<Envelope>
    <body>
        <products>
            <Product>
                <ARTNO>10-0001</ARTNO>
                <LEVARTNO>K01-300</LEVARTNO>
                <EAN></EAN>
                <WEBGRUPP1>200</WEBGRUPP1>
            </Product>
            <Product>
                <ARTNO>10C0414</ARTNO>
                <LEVARTNO>0505-0906</LEVARTNO>
                <EAN></EAN>
                <WEBGRUPP1>701</WEBGRUPP1>
                <WEBGRUPP2></WEBGRUPP2>
            </Product>
        </products>
    </body>
</Envelope>

到目前为止,我已经尝试了几个小时才能找到一个我能理解的解决方案,但却无法解决。我发现了一个非常相似的问题here,但由于我需要匹配除ARTNO之外的其他(未知)元素,我尝试将其应用到我的案例中并没有成功。

我非常简单的XSL(XSL 1)是基于我的假设,即一个人应该能够将所有后续兄弟姐妹提升到下一个ARTNO元素,但仅此一点(测试元素只是在试用时)

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

    <xsl:template match="/">
        <Root>
            <Products>
                <xsl:for-each select="Envelope/body/products/*">
                    <xsl:apply-templates select="."/>
                </xsl:for-each>
            </Products>
        </Root>
    </xsl:template>


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

    <xsl:template match="ARTNO">
        <Product>
            <xsl:copy-of select="."/>
            <Test>
                <xsl:copy-of select="following-sibling::ARTNO[1]"/>
                <!-- <xsl:value-of select="following-sibling::*[not(self::ARTNO)][2]"/> -->
            </Test>
        </Product>
    </xsl:template>

</xsl:stylesheet>

我想我可以做一些非常丑陋的事情,循环整个结构,并使用position等解决它,但我确信有更好的方法,我希望一些XSLT向导可以提供一些指导。非常感谢。

3 个答案:

答案 0 :(得分:3)

定义一个键<xsl:key name="group" match="products/*[not(self::ARTNO)]" use="generate-id(preceding-sibling::ARTNO[1])"/>,然后在

中使用它
    <xsl:template match="/">
        <Root>
            <Products>
                    <xsl:apply-templates select="Envelope/body/products/ARTNO"/>
            </Products>
        </Root>
    </xsl:template>

<xsl:template match="ARTNO">
  <Product>
    <xsl:copy-of select=". | key('group', generate-id())"/>
  </Product>
</xsl:template>

答案 1 :(得分:0)

在XSLT 2.0中,使用<xsl:for-each-group group-starting-with="ARTNO"/>这个问题变得非常容易。如果可以,请切换到XSLT 2.0。

在XSLT 1.0中,我首选的方法是“兄弟递归”。像这样:

<xsl:template match="products">
  <xsl:apply-templates select="ARTNO"/>
</xsl:template>

<xsl:template match="ARTNO">
  <product>
    <xsl:copy-of select="."/>
    <xsl:apply-templates select="following-sibling::*[1]" mode="copy-siblings"/>
  </product>
</xsl:template>

<xsl:template match="*" mode="copy-siblings">
    <xsl:copy-of select="."/>
    <xsl:apply-templates select="following-sibling::*[1]" mode="copy-siblings"/>
</xsl:template>

<xsl:template match="ARTNO" mode="copy-siblings"/>

这个想法是,当你处理ARTNO或其中一个兄弟姐妹时,你会调用一个复制该元素的模板,然后转移到下一个兄弟;当你到达另一个ARTNO时,你什么都不做,这会终止递归。

答案 2 :(得分:0)

为了完整起见,我发布了整个工作样式表:

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

    <xsl:template match="/">
        <Root>
            <Products>
                <xsl:for-each select="Envelope/body/products/*">
                    <xsl:apply-templates select="."/>
                </xsl:for-each>
            </Products>
        </Root>
    </xsl:template>

    <xsl:template match="products">
        <xsl:apply-templates select="ARTNO"/>
    </xsl:template>

    <xsl:template match="ARTNO">
        <product>
            <xsl:copy-of select="."/>
            <xsl:apply-templates select="following-sibling::*[1]" mode="copy-siblings"/>
        </product>
    </xsl:template>

    <xsl:template match="*" mode="copy-siblings">
        <xsl:copy-of select="."/>
        <xsl:apply-templates select="following-sibling::*[1]" mode="copy-siblings"/>
    </xsl:template>

    <xsl:template match="ARTNO" mode="copy-siblings"/>

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

</xsl:stylesheet>

作为一名自学成才的开发人员,我有时会为了解决XSL问题而苦苦挣扎,所以让我确定我已经理解了一些事情:

我知道在XSL中,一个更具体的表达式优先,所以我认为当处理节点时,没有模式匹配“ARTNO”的模板只是复制节点,然后应用模板“copy-siblings”,应用模式的ARTNO模板,有效地充当“退出”,“*”负责所有其他兄弟姐妹的第一个实例。

这有点正确理解吗?

感谢您的帮助!