Главная » XSLT » Игнорирование элементов-дубликатов

0

Задача

Требуется отобрать все узлы, уникальные в данном контексте, используя за­данный критерий уникальности.

Решение XSLT 1.0

Задача отбора уникальных узлов – типичное применение осей preceding и preceding-sibling. Если не все отбираемые элементы являются братьями, пользуйтесь осью preceding. Следующий код порождает список уникальных продуктов, встречающихся в документе SalesByPerson.xml:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/">

<products>

<xsl:for-each select="//product[not(@sku=preceding::product/@sku)]">

<xsl:copy-of select="."/> </xsl:for-each> </products> </xsl:template>

</xsl:stylesheet>

Если все элементы являются братьями, пользуйтесь осью preceding- sibling:

<?xml version="1.0" encoding="UTF-8"?> <products>

<product sku="10000" totalSales="10000.00"/> <product sku="10000" totalSales="990000.00"/> <product sku="10000" totalSales="1110000.00"/> <product sku="20000" totalSales="50000.00"/> <product sku="20000" totalSales="150000.00"/> <product sku="20000" totalSales="150000.00"/> <product sku="25 0 0 0" totalSales="92 0 0 0 0.0 0"/> <product sku="25 0 0 0" totalSales="2 92 0 0 0 0.0 0"/> <product sku="30 0 0 0" totalSales="55 0 0.0 0"/> <product sku="30 0 0 0" totalSales="1155 0 0.0 0"/> <product sku="70000" totalSales="10000.00"/> </products>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/products"> <products>

<xsl:for-each select="product[not(@sku=preceding-sibling::product/@sku)]">

<xsl:copy-of select="."/> </xsl:for-each> </products> </xsl:template>

</xsl:stylesheet>

Чтобы избежать не всегда эффективного использования оси preceding, под­нимитесь вверх до предков, которые являются братьями, а затем воспользуйтесь осью preceding-sibling и спуститесь до узлов, подлежащих проверке:

<xsl:for-each select="//product[not(@sku=../preceding-sibling::*/

product/@sku)]"> <xsl:copy-of select="."/> </xsl:for-each>

Если вы точно знаете, что элементы отсортированы и, значит, узлы-дубликаты расположены по соседству (как в приведенном выше документе products), то мо­жете ограничиться рассмотрением лишь непосредственно предшествующего брата:

<xsl:for-each

select="/salesperson/product[not(@name=preceding-

sibling::product[1]/@name)]"> <!– сделать что-то с каждым продуктом с уникальным названием –> </xsl:for-each>

XSLT 2.0

В XSLT 2.0 эту задачу можно рассматривать как задачу группировки. Для ре­шения достаточно применить команду for-each-group и задать критерий уни­кальности с помощью атрибута group-by. В качестве уникального берем первый узел в каждой группе:

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

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/"> <products>

<xsl:for-each-group select="//product" group-by="@sku">

<xsl:copy-of select="current-group()[1]"/> </xsl:for-each-group>

</products> </xsl:template>

</xsl:stylesheet>

Обсуждение XSLT 1.0

C помощью функции расширения node-set() можно также решить задачу следующим образом:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/">

<xsl:variable name="products">

<xsl:for-each select="//product"> <xsl:sort select="@sku"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable>

<products>

<xsl:for-each select="exsl:node-set($products)/product"> <xsl:variable name="pos" select="position()"/> <xsl:if test="$pos = 1 or not(@sku = $products/preceding-sibling::product[1]/@sku"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </products> </xsl:template>

</xsl:stylesheet>

Я не встречал реализации, в которой эта техника оказалась бы быстрее, чем использование оси preceding. Однако у нее есть преимущества в случаях, когда проверка на дубликат не тривиальна. Рассмотрим, например, ситуацию, где для определения одинаковости необходимо конкатенировать два атрибута:

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:exslt="http://exslt.org">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/">

<xsl:variable name="people">

<xsl:for-each select="//person">

<xsl:sort select="concat(@lastname,@firstname)"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:variable>

<products>

<xsl:for-each select="exsl:node-set($people)/person"> <xsl:variable name="pos" select="position()"/> <xsl:if test="$pos = 1 or

concat(@lastname,@firstname) !=

concat(people/person[$pos – 1]/@lastname,

people/person[$pos – 1]/@firstname)"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </products>

</xsl:template>

Следующая попытка удалить дубликаты работать не будет:

<xsl:template match="/"> <products>

<xsl:for-each select="//product[not(@sku=preceding::product[1]/@sku)]"> <xsl:sort select="@sku"/>

<xsl:copy-of select="."/> </xsl:for-each> </products> </xsl:template>

Не пытайтесь сортировать для того, чтобы избежать рассмотрения всех эле­ментов, кроме непосредственного предшественника. По оси preceding узлы расположены в исходном порядке документа. То же касается и оси preceding- sibling. Следующий код также заведомо неработоспособен:

<xsl:template match="/">

<xsl:variable name="products">

<xsl:for-each select="//product">

<!– сортировка удалена –>

<xsl:copy-of select="."/> </xsl:for-each> </xsl:variable>

<products>

<xsl:for-each select="exsl:node-set($products)/product"> <xsl:sort select="@sku"/>

<xsl:variable name="pos" select="position()"/> <xsl:if test="$pos = 1 or

@sku != $products/product[$pos – 1]/@sku"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </products> </xsl:template>

Этот код не работает, так как position() возвращает позицию после сорти­ровки, но отсортировано было не содержимое $products, а содержимое недо­ступной копии.

XSLT 2.0

Иногда нужно удалять только соседние дубликаты. Рассмотрим, к примеру, набор данных, полученный в результате последовательности измерений в опре­деленные моменты времени. Если измеряемая система ведет себя стабильно, то ре­зультаты многих соседних измерений будут одинаковы. Было бы хорошо избавить­ся от соседних дубликатов, не удаляя такие же результаты, если они появляются в последовательности позже.

Для решения этой задачи тоже подойдет команда xsl:for-each-group, только с атрибутом group-adjacent, а не group-by:

<measurements>

<data time="12:00:00" value="1.0"/> <data time="12:00:01" value="1.0"/> <data time="12:00:02" value="1.1"/> <data time="12:00:03" value="1.1"/> <data time="12:00:04" value="1.0"/> <data time="12:00:05" value="1.1"/> <data time="12:00:06" value="1.2"/> <data time="12:00:07" value="1.3"/> <data time="12:00:08" value="1.4"/> <data time="12:00:09" value="1.6"/> <data time="12:00:10" value="1.9"/> <data time="12:00:11" value="2.1"/> <data time="12:00:12" value="1.7"/> <data time="12:00:13" value="1.5"/>

</measurements>

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/

<xsl:template match="/measurements"> <xsl:copy>

<xsl:for-each-group select="data" group-adjacent="@value">

<xsl:copy-of select="current-group()[1]"/> </xsl:for-each-group> </xsl:copy> </xsl:template>

</xsl:stylesheet>

<!– Результат –>

<measurements>

<data time="12:00:00" value="1.0"/> <data time="12:00:02" value="1.1"/> <data time="12:00:04" value="1.0"/> <data time="12:00:05" value="1.1"/> <data time="12:00:06" value="1.2"/> <data time="12:00:07" value="1.3"/> <data time="12:00:08" value="1.4"/> <data time="12:00:09" value="1.6"/> <data time="12:00:10" value="1.9"/> <data time="12:00:11" value="2.1"/> <data time="12:00:12" value="1.7"/> <data time="12:00:13" value="1.5"/>

</measurements>

См. также

В FAQc по XSLT (http://www.dpawson.co.uk/xsl/sect2/N2696.html) описывает­ся решение этой задачи с использованием ключей, а также приводятся решения родственных задач.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии