XSL 根据 Element 值删除所有前面的同级
Posted
技术标签:
【中文标题】XSL 根据 Element 值删除所有前面的同级【英文标题】:XSL Remove all preceding siblings based on Element values 【发布时间】:2012-07-20 08:35:47 【问题描述】:您好,我需要帮助来解析以下 XML。
<xmeml>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>bcd</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>3456</Unit2>
</Test>
<Test>
<Unit>cde</Unit>
<Unit2>3456</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>def</Unit>
<Unit2>4567</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>efg</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
</xmeml>
以下内容结束
<xmeml>
<Doc>
<Test>
<Unit>bcd</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>3456</Unit2>
</Test>
<Test>
<Unit>cde</Unit>
<Unit2>3456</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>def</Unit>
<Unit2>4567</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>efg</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
</xmeml>
我正在尝试创建一个 XSLT 文档来执行此操作,但尚未找到有效的文档。 我应该注意,“Doc”中所需的匹配参数是,在本例中为“abc”和“1234”,在现实世界中,这些是变量,永远不会是静态可搜索实体。
所以在英语中我的 XSL 会是这样的: 对于同时包含匹配 'Unit' 和 'unit2' 值的任何父级 删除前面所有包含重复值“Unit”和“Unit2”的父级“Test”,最后一个除外。
非常感谢所有帮助 谢谢
【问题讨论】:
【参考方案1】:在 XSLT 2.0 中使用 for-each-group 构造可以解决许多涉及消除重复的问题。在这种情况下,使用 for-each-group 的解决方案并不明显,因为它实际上不是分组问题(对于分组问题,我们通常在输出中生成一个元素,该元素对应于输入中的一组元素,并且这里不是这种情况。)我会以与 Dimitre 相同的方式处理它:使用 for-each-group 来识别组,因此需要保留的测试元素与需要删除的元素。事实上,我开始解决这个问题并提出了一个与 Dimitre 非常相似的解决方案,除了我认为最后一个模板规则可以简化为
<xsl:template match="Test[not(. intersect $vLastInGroup)]"/>
这是我有时使用的编码模式的一个示例,您可以设置包含具有特定特征的所有元素的节点集值全局变量,然后使用模板规则来测试全局节点集的成员资格(使用谓词[. intersect $node-set]
)。按照这种模式,并使用 XSLT 3.0 中可用的一些新语法,我倾向于编写如下代码:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:variable name="deletedElements" as="element()*">
<xsl:for-each-group select="/*/Doc/Test"
group-by="Unit, Unit2" composite="yes">
<xsl:sequence select="current-group()[position() ne last()]"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template match="$deletedElements"/>
</xsl:stylesheet>
【讨论】:
【参考方案2】:我。 XSLT 1.0 解决方案:
这里是最有效的已知 XSLT 1.0 分组方法 -- Muenchian grouping 的简单应用(无变量,无 xsl:if
,无轴,无 xsl:call-template
):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kTestByData" match="Test"
use="concat(Unit, '|', Unit2)"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"Test[not(generate-id()
= generate-id(key('kTestByData',concat(Unit, '|', Unit2))[last()])
)]"/>
</xsl:stylesheet>
当此转换应用于提供的 XML 文档时:
<xmeml>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>bcd</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>3456</Unit2>
</Test>
<Test>
<Unit>cde</Unit>
<Unit2>3456</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>def</Unit>
<Unit2>4567</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>efg</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
</xmeml>
产生想要的正确结果:
<xmeml>
<Doc>
<Test>
<Unit>bcd</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>3456</Unit2>
</Test>
<Test>
<Unit>cde</Unit>
<Unit2>3456</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>def</Unit>
<Unit2>4567</Unit2>
</Test>
</Doc>
<Doc>
<Test>
<Unit>abc</Unit>
<Unit2>1234</Unit2>
</Test>
<Test>
<Unit>efg</Unit>
<Unit2>2345</Unit2>
</Test>
</Doc>
</xmeml>
注意:对于具有大量节点的节点集进行重复数据删除,Muenchian 分组方法比二次 (O(N^2)) 兄弟比较快很多数量级分组。
二。 XSLT 2.0 解决方案:
II.1 这是一个简单(效率低,适用于长度较小的节点集)XSLT 2.0 解决方案:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"Test[concat(Unit, '+', Unit2) = following::Test/concat(Unit, '+', Unit2)]"/>
</xsl:stylesheet>
II.2 使用xsl:for-each-group
的高效解决方案:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vLastInGroup" as="element()*">
<xsl:for-each-group select="/*/Doc/Test"
group-by="concat(Unit, '+', Unit2)">
<xsl:sequence select="current-group()[last()]"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"Test[for $t in .
return
not($vLastInGroup[. is $t])
]"/>
</xsl:stylesheet>
【讨论】:
“简单”是一个相对概念。对于像您这样的专家用户,我同意这是一个更简单的解决方案,但对于 XSLT 的初学者/新手来说,Muenchian 分组实际上很难掌握。虽然它不是最有效的(正如我已经说过的),但检查是否缺少以下兄弟姐妹更容易理解,因为它在概念上类似于所需解决方案的描述。但是,如果您需要速度并且可以理解,我同意此解决方案是更好的选择。 @Flynn1179:至于可理解性——我同意你的看法。但是,您的转换是不必要的复杂,这实际上使它更难以理解。有了这个评论,我实际上有助于使您的解决方案更简单、更优雅。 @Flynn1179:有一些客观的可理解性衡量标准——简单地说,就是“活动部件”的数量。如果一种解决方案有 1 个xsl:if
、2 个xsl:variable
、1 个following
轴、1 个xsl:call-template
——而另一个没有这些,那么哪个更简单?
@MichaelKay,令我惊讶的是,使用xsl:for-each-group
解决这个问题非常困难和复杂——与第一个 Muenchian 解决方案相比。您能提出更好的解决方案吗?
再次感谢 Dimitre,重新使用 xsl v1.0 解决方案,很抱歉对您这样做,但是....如果我还想在密钥中添加第三项,我该怎么办? Unit3 以“345”开头的效果。那么必须假设每个测试节点中也存在 Unit3。【参考方案3】:
这是一种相对简单的方法,尽管我相当确定使用 Meunchian 方法有一种更有效的方法。但是,如果性能不是问题,这可能更容易理解:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="Test">
<xsl:variable name="vUnit" select="Unit" />
<xsl:variable name="vUnit2" select="Unit2" />
<xsl:if test="not(following::Test[Unit = $vUnit and Unit2 = $vUnit2])">
<xsl:call-template name="identity" />
</xsl:if>
</xsl:template>
<xsl:template match="@* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Test
模板只是检查在Unit
和Unit2
中是否存在具有相同值的后续Test
元素,如果没有,则正常输出。
【讨论】:
谢谢弗林。给你满分。 嗨弗林。如果我想添加另一个匹配短语,我将如何调整上述内容。 IE:搜索 Unit 和 Unit2 的重复项,但同时也在寻找 Unit3 是已知值 4567 的匹配项。我们必须想象 Unit3 也存在于上述所有测试节点中。 这很简单,只需在xsl:if
的test
属性的Test
谓词中添加and Unit3 = 4567
即可
再次感谢弗林,Unit3 以 456 开头怎么样?和 Unit3[starts-with(.,'456')] 之类的东西?
差不多就是这样,虽然我个人会做starts-with(Unit3, '456')
,但本质上是一样的。以上是关于XSL 根据 Element 值删除所有前面的同级的主要内容,如果未能解决你的问题,请参考以下文章
如果祖先的属性使用 xsl 持有某个值,我如何删除属性和元素