如何根据属性值合并节点?

How can I merge nodes based on the value of an attribute?

在第一次 xsl 转换后,我有一个 xml 类似于下面的输出:

<?xml version="1.0" encoding="UTF-8"?>
<analysis type="1">
    <file path="a.txt">
        <line nb="23" found="true"/>
        <line nb="36" found="true" count="2"/>
        <line nb="98" found="true"/>
    </file>
    <file path="a.txt">
        <line nb="100" found="false"/>
    </file>
    <file path="b.txt">
        <line nb="10" found="false"/>
    </file>
    <!-- more file nodes below with different @path -->
</analysis>

但现在我需要获得第二个输出,其中 file 节点合并,如果它们具有相同的 path 属性,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<analysis type="1">
    <file path="a.txt">
        <line nb="23" found="true"/>
        <line nb="36" found="true" count="2"/>
        <line nb="98" found="true"/>
        <line nb="100" found="false"/>
    </file>
    <file path="b.txt">
        <line nb="10" found="false"/>
    </file>
</analysis>

我事先不知道可能的@path值。

我查看了多篇关于节点合并的帖子,但找不到实现我想要的方法的方法。我迷失了节点分组、键、id 生成......到目前为止只获得了错误消息。

你能帮我从第一个输出开始得到第二个输出吗(xls 1.0)?如果您能提供一些参考资料(网站),我可以在其中找到有关此类转换的解释,那就太好了。

注意:具有相同@path的两个file节点的两个line节点的@nb属性永远不会发生冲突,它是唯一的,即这永远不会发生:

<?xml version="1.0" encoding="UTF-8"?>
<analysis type="1">
    <file path="a.txt">
        <line nb="36" found="true" count="2"/>
    </file>
    <file path="a.txt">
        <line nb="36" found="true"/>
    </file>
</analysis>

非常感谢您的帮助!

没有键的 XPath 1.0

由于您在问题中声明您无法理解键,这里有一种方法无需键,使用一种称为兄弟递归的技术。它被认为不如使用键好,因为它使用兄弟轴,这通常很慢。然而,在大多数实际情况下,您不会注意到其中的区别:

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

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

    <xsl:template match="analysis">
        <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:apply-templates select="file[not(preceding-sibling::file/@path = @path)]" mode="sibling-recurse" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="file" mode="sibling-recurse">
        <xsl:copy>
            <!-- back to default mode -->
            <xsl:apply-templates select="node() | @*" />
            <xsl:apply-templates select="following-sibling::file[current()/@path = @path]" />
        </xsl:copy>
    </xsl:template>

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

XPath 1.0 和 Münchian 分组键

此方法使用 Münchian 分组,这在其他地方有解释(只需按照教程 like this one 手头的代码)。它还使用兄弟轴,但破坏性要小得多(即,不需要在每个节点测试中遍历整个兄弟轴)。

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

    <xsl:key match="file" use="@path" name="path" />

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

    <xsl:template match="analysis">
        <xsl:copy>
            <xsl:copy-of select="@*" />
            <xsl:apply-templates select="file[generate-id(.) = generate-id(key('path', @path))]" mode="sibling-recurse" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="file" mode="sibling-recurse">
        <xsl:copy>
            <!-- back to default mode -->
            <xsl:apply-templates select="node() | @*" />
            <xsl:apply-templates select="following-sibling::file[@path = current()/@path]/node()" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

注意:对于这两种方法,模式切换并非完全必要,但它可以更轻松地编写简单的匹配模式并防止优先级冲突或难以发现的错误 (imo)。