根据属性和值去除重复元素

Remove duplicate elements based on attributes and values

many questions 关于当您可以按某个属性或值对这些元素进行分组时如何删除重复元素,但是,在我的例子中,属性已经在 XSLT 中动态生成,我没有不想在每个元素的每个属性中编程以用作分组键。

如何在事先不知道其属性的情况下删除重复元素?到目前为止,我已经尝试在每个元素上使用 generate-id() 并以此进行分组,但问题是 generate-id 没有为具有相同属性的元素生成相同的 ID:

<xsl:template match="root">
    <xsl:variable name="tempIds">
        <xsl:for-each select="./*>
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:attribute name="tempID">
                    <xsl:value-of select="generate-id(.)"/>
                </xsl:attribute>
                <xsl:copy-of select="node()"/>
            </xsl:copy>
        </xsl:for-each>
    </xsl:variable>
    <xsl:for-each-group select="$tempIds" group-by="@tempID">
        <xsl:sequence select="."/>
    </xsl:for-each-group>
</xsl:template>

测试数据:

<root>
    <child1>
        <etc/>
    </child1>
    <dynamicElement1 a="2" b="3"/>
    <dynamicElement2 c="3" d="4"/>
    <dynamicElement2 c="3" d="5"/>
    <dynamicElement1 a="2" b="3"/>
</root>

最终结果只是剩下的两个 dynamicElement1 元素之一:

<root>
    <child1>
        <etc/>
    </child1>
    <dynamicElement1 a="2" b="3"/>
    <dynamicElement2 c="3" d="4"/>
    <dynamicElement2 c="3" d="5"/>
</root>

在 XSLT 3 中,如 https://xsltfiddle.liberty-development.net/pPqsHTi 所示,您可以使用所有属性的复合键,例如

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

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output indent="yes"/>

  <xsl:template match="root">
      <xsl:copy>
          <xsl:for-each-group select="*" composite="yes" group-by="@*">
              <xsl:sequence select="."/>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

请注意,从技术上讲,属性是无序的,因此通过 node-name() 或类似方式对属性进行分组可能更安全,就像 https://xsltfiddle.liberty-development.net/pPqsHTi/2 中没有高阶函数的 XSLT 3 所做的那样

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:mf="http://example.com/mf"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output indent="yes"/>

  <xsl:function name="mf:node-sort" as="node()*">
      <xsl:param name="input-nodes" as="node()*"/>
      <xsl:perform-sort select="$input-nodes">
          <xsl:sort select="namespace-uri()"/>
          <xsl:sort select="local-name()"/>
      </xsl:perform-sort>
  </xsl:function>

  <xsl:template match="root">
      <xsl:copy>
          <xsl:for-each-group select="*" composite="yes" group-by="mf:node-sort(@*)">
              <xsl:sequence select="."/>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

或者就像您可以使用

对 Saxon EE 所做的那样
<xsl:template match="root">
    <xsl:copy>
        <xsl:for-each-group select="*" composite="yes" group-by="sort(@*, (), function($att) { namespace-uri($att), local-name($att) })">
            <xsl:sequence select="."/>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="root/*[@a= following-sibling::*/@a]|root/*[@c= following-sibling::*/@c and @d= following-sibling::*/@d]"/>
You may try this