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 模板只是检查在UnitUnit2 中是否存在具有相同值的后续Test 元素,如果没有,则正常输出。

【讨论】:

谢谢弗林。给你满分。 嗨弗林。如果我想添加另一个匹配短语,我将如何调整上述内容。 IE:搜索 Unit 和 Unit2 的重复项,但同时也在寻找 Unit3 是已知值 4567 的匹配项。我们必须想象 Unit3 也存在于上述所有测试节点中。 这很简单,只需在xsl:iftest 属性的Test 谓词中添加and Unit3 = 4567 即可 再次感谢弗林,Unit3 以 456 开头怎么样?和 Unit3[starts-with(.,'456')] 之类的东西? 差不多就是这样,虽然我个人会做starts-with(Unit3, '456'),但本质上是一样的。

以上是关于XSL 根据 Element 值删除所有前面的同级的主要内容,如果未能解决你的问题,请参考以下文章

如果祖先的属性使用 xsl 持有某个值,我如何删除属性和元素

关于使用python脚本将同级的其他目录下的所有文件根据年份移动到当脚本位置的年份目录

XPath 根据基本同级值获取同级

Element-ui 中树形控件(Tree)实现增删改功能

在 XML 中查找重复值并在 XSL 中删除节点

XSL:根据属性计数和匹配创建组