Удаление повторяющихся элементов на основе атрибутов и значений

Существует много вопросов о том, как удалить повторяющиеся элементы, когда вы можете сгруппировать эти элементы по определенному атрибуту или значению, однако в В моем случае атрибуты уже динамически генерируются в XSLT, и я не хочу программировать каждый атрибут для каждого элемента, чтобы использовать его в качестве ключа группировки.

Как удалить повторяющиеся элементы, не зная заранее их атрибутов? До сих пор я пытался использовать generate-id() для каждого элемента и группировать по нему, но проблема в том, что generate-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>

person CC Inc    schedule 02.06.2018    source источник
comment
Какой процессор XSLT вы используете? И какой результат вы хотите для тестовых данных? Звучит так, как будто XSLT 3 с xsl:for-each-group select="*" composite="yes" group-by="@*" может быть вариантом. Но я не понимаю, как элемент может иметь два атрибута c, как ваши элементы dynamicElement2.   -  person Martin Honnen    schedule 02.06.2018
comment
@MartinHonnen Прошу прощения, это опечатка. Моя таблица стилей использует XSLT 2.0, но я могу создать другую таблицу стилей в версии 3.0, которая будет связана с другой, если это будет проще. Я использую SAXON EE в качестве процессора   -  person CC Inc    schedule 02.06.2018
comment
проблема в том, что generate-id не генерирует один и тот же идентификатор для элементов с одинаковыми атрибутами Хм, откуда вы взяли, что это должно быть так?   -  person Tomalak    schedule 02.06.2018
comment
Ну, весь смысл generate-id() в том, чтобы генерировать уникальные идентификаторы, так что это не должно быть сюрпризом.   -  person Tomalak    schedule 02.06.2018
comment
@Tomalak Я только что видел этот ответ. Я неправильно понял и подумал, что generate-id вернет то же значение для узла с аналогичным значением и атрибутами, но теперь я вижу, что он также учитывает контекст.   -  person CC Inc    schedule 02.06.2018


Ответы (1)


В 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() или аналогичному, как это делается с XSLT 3 без функций более высокого порядка в https://xsltfiddle

<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>
person Martin Honnen    schedule 02.06.2018
comment
Это прекрасно работает, спасибо! Насколько мне известно, есть идеи, почему мое generate-id решение не сработало? - person CC Inc; 02.06.2018
comment
@CCInc, generate-id генерирует отдельный идентификатор для любого отдельного узла на основе идентификатора узла. Таким образом, два узла в одном документе никогда не имеют одинаковых идентификаторов. - person Martin Honnen; 02.06.2018
comment
Я бы определенно не стал полагаться на то, что атрибуты находятся в одном и том же порядке для двух разных элементов. Технически использование string(node-name()) в качестве ключа сортировки также небезопасно, поскольку оно зависит от одних и тех же привязок пространства имен, находящихся в области видимости для обоих элементов. Безопаснее использовать составной ключ сортировки (namespace-uri($att), local-name($att)). - person Michael Kay; 03.06.2018
comment
@MichaelKay, вы правы, я адаптировал примеры кода, чтобы включить ваше предложение. - person Martin Honnen; 03.06.2018