我有以下xml。请注意,节点 n1 和 n3 具有相同的子节点(顺序可以不同(。如何编写 XSL 转换来标识此类节点?
<Document>
<Node name="n1">
<Item value="v1">
<Item value="v2">
<Item value="v3">
</Node>
<Node name="n2">
<Item value="p1">
<Item value="p2">
<Item value="p3">
</Node>
<Node name="n3">
<Item value="v3">
<Item value="v1">
<Item value="v2">
</Node>
</Document>
下面是使用 XSLT 1.0 执行此操作的尝试:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="sep" select="' '"/>
<xsl:output indent="yes"/>
<xsl:template match="Node">
<Node name="{@name}">
<xsl:attribute name="matches">
<xsl:apply-templates
select="../Node[not(generate-id() = generate-id(current()))]
[count(Item) = count(current()/Item)]
[not(Item[not(@value = current()/Item/@value)])]"
mode="check"/>
</xsl:attribute>
</Node>
</xsl:template>
<xsl:template match="Node" mode="check">
<xsl:if test="position() > 1">
<xsl:value-of select="$sep"/>
</xsl:if>
<xsl:value-of select="@name"/>
</xsl:template>
</xsl:stylesheet>
针对示例输入使用 Saxon 6.5.5 运行样式表时
<Document>
<Node name="n1">
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
</Node>
<Node name="n2">
<Item value="p1"/>
<Item value="p2"/>
<Item value="p3"/>
</Node>
<Node name="n3">
<Item value="v3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n4">
<Item value="p3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n5">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
</Node>
<Node name="n6">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
<Node name="n7">
<Item value="v1"/>
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
</Document>
我得到以下结果:
<Node name="n1" matches="n3 n5"/>
<Node name="n2" matches=""/>
<Node name="n3" matches="n1 n5"/>
<Node name="n4" matches=""/>
<Node name="n5" matches="n1 n3"/>
<Node name="n6" matches=""/>
<Node name="n7" matches=""/>
下面是一个完整的 XSLT 1.0 解决方案,它足够通用,因此即使允许Node
具有任何名称的子项,它也会产生正确的结果:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kNodeBySign" match="Node" use="@signature"/>
<xsl:template match="/*">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates mode="pass2"
select="ext:node-set($vrtfPass1)"/>
</xsl:template>
<xsl:template match="Node">
<Node name="{@name}">
<xsl:variable name="vSignature">
<xsl:for-each select="*">
<xsl:sort select="name()"/>
<xsl:sort select="@value"/>
<xsl:value-of select="concat(name(),'+++',@value)"/>
</xsl:for-each>
</xsl:variable>
<xsl:attribute name="signature">
<xsl:value-of select="$vSignature"/>
</xsl:attribute>
</Node>
</xsl:template>
<xsl:template match="/" mode="pass2">
<xsl:for-each select=
"Node[generate-id()
=
generate-id(key('kNodeBySign',@signature)[1])
]
">
<Node name="{@name}">
<xsl:variable name="vNodesInGroup">
<xsl:for-each select=
"key('kNodeBySign',@signature)[position()>1]">
<xsl:value-of select="concat(@name, ' ')"/>
</xsl:for-each>
</xsl:variable>
<xsl:attribute name="matches">
<xsl:value-of select="$vNodesInGroup"/>
</xsl:attribute>
</Node>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
应用于此 XML 文档时:
<Document>
<Node name="n1">
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
</Node>
<Node name="n2">
<Item value="p1"/>
<Item value="p2"/>
<Item value="p3"/>
</Node>
<Node name="n3">
<Item value="v3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n4">
<Item value="p3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n5">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
</Node>
<Node name="n6">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
<Node name="n7">
<Item value="v1"/>
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
</Document>
生成所需的正确结果:
<Node name="n1" matches="n3 n5 "/>
<Node name="n2" matches=""/>
<Node name="n4" matches=""/>
<Node name="n6" matches=""/>
<Node name="n7" matches=""/>
解释:
这是一个两遍转换。
第一次传递的结果是一个 XML 片段,其中包含
Node
元素及其name
属性和一个新添加的属性:signature
。这是所有子项的名称和值的串联(以正常的排序形式(。在此具体案例中,pass1 的结果如下:在第 2 阶段,我们使用 Muenchian 方法按元素的
signature
属性对所有Node
元素进行分组。每个组中的第一个Node
在输出中用一个新属性matches
表示,该属性的值是当前组中其余Node
元素的name
属性的空格分隔串联。
[编辑]XSLT 2.0 样式表
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="Node">
<Node name="{@name}" matches="{../Node[not(. is current())][every $item in current()/Item satisfies $item/@value = ./Item/@value]/@name}"/>
</xsl:template>
</xsl:stylesheet>
当应用于时
<Document>
<Node name="n1">
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
</Node>
<Node name="n2">
<Item value="p1"/>
<Item value="p2"/>
<Item value="p3"/>
</Node>
<Node name="n3">
<Item value="v3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n4">
<Item value="p3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n5">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
</Node>
</Document>
输出
<Node name="n1" matches="n3 n5"/>
<Node name="n2" matches=""/>
<Node name="n3" matches="n1 n5"/>
<Node name="n4" matches=""/>
<Node name="n5" matches="n1 n3"/>
如果所有子项都必须匹配,那么我认为马丁的解决方案需要修改才能使用
<Node name="{@name}"
matches="{../Node[not(. is current())]
[count(Item) = count(current()/Item)]
[every $c in Item/@value
satisfies
$c = current()/Item/@value
]
/@name
}"/>
这可能不完全满足要求,例如,如果有另一个节点的值为 v1,v2,v3,它将匹配值为 v1,v2,v2 的节点。但由于要求没有非常精确地指定,我不得不猜测一下。
(注意Martin的解决方案是XSLT 1.0,而我的是XSLT 2.0。我不会努力编写 XSLT 1.0 代码,除非人们明确表示这是他们需要的。
(谦虚的(XSLT 1.0方法。它不像其他人那么优雅,在许多情况下可能是失败的,但是,根据问题,它可以完成工作,并且还具有一定程度的定制。
节点之间的检查是针对使用命名模板构建的模式执行的:build-pattern
。例如,在您的情况下,我将与使用节点的所有value
属性构建的模式进行比较;也就是说,将第一个节点与类似 v1v2v3
的模式进行比较。该模式建立在名称为 Item
的元素上。显然,这种模式可以根据要求进行更改。
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Node">
<xsl:variable name="current">
<xsl:call-template name="build-pattern">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:copy>
<xsl:copy-of select="@name"/>
<xsl:attribute name="matches">
<xsl:for-each select="../Node[not(generate-id()
= generate-id(current()))]">
<xsl:variable name="node">
<xsl:call-template name="build-pattern">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$current=$node">
<xsl:value-of select="@name"/>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template name="build-pattern">
<xsl:param name="node"/>
<xsl:for-each select="$node/Item">
<xsl:sort select="@value"/>
<xsl:value-of select="@value"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
应用于此输入时:
<Document>
<Node name="n1">
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
</Node>
<Node name="n2">
<Item value="p1"/>
<Item value="p2"/>
<Item value="p3"/>
</Node>
<Node name="n3">
<Item value="v3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n4">
<Item value="p3"/>
<Item value="v1"/>
<Item value="v2"/>
</Node>
<Node name="n5">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
</Node>
<Node name="n6">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
<Node name="n7">
<Item value="v1"/>
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
</Document>
生产:
<Node name="n1" matches="n3n5"></Node>
<Node name="n2" matches=""></Node>
<Node name="n3" matches="n1n5"></Node>
<Node name="n4" matches=""></Node>
<Node name="n5" matches="n1n3"></Node>
<Node name="n6" matches=""></Node>
<Node name="n7" matches=""></Node>
我已经概括了上述转换以查找具有相同子级的节点:
- 任何名称
- 以任何顺序
- 具有任意数量的属性和任何名称
只需将命名模板build-pattern
替换为以下两个:
<xsl:template name="build-pattern">
<xsl:param name="node"/>
<xsl:for-each select="$node/*">
<xsl:sort select="name()"/>
<xsl:sort select="@*[1]"/>
<xsl:value-of select="name()"/>
<xsl:apply-templates select="attribute::*">
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="@*">
<xsl:value-of select="concat(name(),.)"/>
</xsl:template>
例如,当新转换应用于以下文档时:
<Document>
<Node name="n1">
<Item value="v1" x="a2"/>
<foo value="v2" x="a1"/>
<Item value="v3"/>
</Node>
<Node name="n2">
<Item value="p1"/>
<Item value="p2"/>
<Item value="p3"/>
</Node>
<Node name="n3">
<Item value="v3"/>
<Item value="v1" x="a2"/>
<foo value="v2" x="a1"/>
</Node>
<Node name="n4">
<Item value="v3"/>
<Item value="v1"/>
<xxxx value="v2"/>
</Node>
<Node name="n5">
<xxxx value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
</Node>
<Node name="n6">
<Item value="v2"/>
<Item value="v1"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
<Node name="n7">
<Item value="v1"/>
<Item value="v1"/>
<Item value="v2"/>
<Item value="v3"/>
<Item value="v4"/>
</Node>
</Document>
生产:
<Node name="n1" matches="n3"/>
<Node name="n2" matches=""/>
<Node name="n3" matches="n1"/>
<Node name="n4" matches="n5"/>
<Node name="n5" matches="n4"/>
<Node name="n6" matches=""/>
<Node name="n7" matches=""/>