Главная » XSLT » Реорганизация иерархии XML

0

Задача

Требуется реорганизовать информацию в XML-документе, так чтобы неявно закодированная информация стала явной и наоборот.

Решение XSLT 1.0

Снова рассмотрим документ SalesBySalesPerson.xml из главы 4:

<salesBySalesperson>

<salesperson name="John Adams" seniority="1"> <product sku="10000" totalSales="10000.00"/> <product sku="20000" totalSales="50000.00"/>

<product sku="25000" totalSales="920000.00"/> </salesperson>

<salesperson name="Wendy Long" seniority="5"> <product sku="10000" totalSales="990000.00"/> <product sku="20000" totalSales="150000.00"/> <product sku="30000" totalSales="5500.00"/> </salesperson>

<salesperson name="Willie B. Aggressive" seniority="10"> <product sku="10000" totalSales="1110000.00"/> <product sku="20000" totalSales="150000.00"/> <product sku="25000" totalSales="2920000.00"/> <product sku="30000" totalSales="115500.00"/> <product sku="70000" totalSales="10000.00"/> </salesperson>

<salesperson name="Arty Outtolunch" seniority="10"/> </salesBySalesperson>

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

Для реорганизации этого документа нужно преобразовать его к виду, где будут показаны итоги по каждому товару. Это делает следующая таблица стилей:

<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:key name="sales_key" match="salesperson" use="product/@sku"/>

<xsl:variable name="products" select="//product"/> <xsl:variable name="unique-products"

select="$products[not(@sku = preceding::product/@sku)]"/>

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

<xsl:for-each select="$unique-products"> <xsl:variable name="sku" select="@sku"/> <xsl:copy>

<xsl:copy-of select="$sku"/> <xsl:attribute name="totalSales">

<xsl:value-of select="sum($products[@sku=$sku]/@totalSales)"/> </xsl:attribute>

<xsl:for-each select="key(‘sales_key’,$sku)">

<xsl:copy>

<xsl:copy-of select="@*"/> <xsl:attribute name="sold">

<xsl:value-of select="product[@sku=$sku]/@totalSales"/> </xsl:attribute> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:for-each> </salesByProduct> </xsl:template>

</xsl:stylesheet>

Результирующий документ выглядит так:

<salesByProduct> <product sku="10000" totalSales="2110000">

<salesperson name="John Adams" seniority="1" sold="10000.00"/> <salesperson name="Wendy Long" seniority="5" sold="990000.00"/> <salesperson name="Willie B. Aggressive" seniority="10" sold="1110000.00"/> </product>

<product sku="20000" totalSales="350000">

<salesperson name="John Adams" seniority="1" sold="50000.00"/> <salesperson name="Wendy Long" seniority="5" sold="150000.00"/> <salesperson name="Willie B. Aggressive" seniority="10" sold="150000.00"/> </product>

<product sku="25 0 0 0" totalSales="38 4 0 0 0 0">

<salesperson name="John Adams" seniority="1" sold="920000.00"/> <salesperson name="Willie B. Aggressive" seniority="10" sold="2920000.00"/> </product>

<product sku="30 0 0 0" totalSales="1210 0 0">

<salesperson name="Wendy Long" seniority="5" sold="5500.00"/> <salesperson name="Willie B. Aggressive" seniority="10" sold="115500.00"/> </product>

<product sku="70000" totalSales="10000">

<salesperson name="Willie B. Aggressive" seniority="10" sold="10000.00"/> </product> </salesByProduct>

Альтернативное решение основано на методе Мюнха. В нем для извлече­ния уникальных товаров применяется элемент xsl:key. Выражение $products[count(.|key(‘product_key’, @sku)[1]) = 1] отбирает первый товар в каждой группе, причем группировка производится по атрибу­ту sku:

<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:variable name="doc" select="/"/>

<xsl:key name="product_key" match="product" use="@sku"/> <xsl:key name="sales_key" match="salesperson" use="product/@sku"/>

<xsl:variable name="products" select="//product"/>

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

<xsl:for-each select="$products[count(.|key(‘product_key’,@sku)[1])

= 1]">

<xsl:variable name="sku" select="@sku"/> <xsl:copy>

<xsl:copy-of select="$sku"/> <xsl:attribute name="totalSales">

<xsl:value-of select="sum(key(‘product_key’,$sku)/@totalSales)"/> </xsl:attribute>

<xsl:for-each select="key(‘sales_key’,$sku)"> <xsl:copy>

<xsl:copy-of select="@*"/> <xsl:attribute name="sold">

<xsl:value-of select="product[@sku=$sku]/@totalSales"/> </xsl:attribute> </xsl:copy> </xsl:for-each> </xsl:copy> </xsl:for-each> </salesByProduct> </xsl:template>

</xsl:stylesheet>

XSLT 2.0

В XSLT 2.0 задача решается прямолинейно за счет применения команды

xsl:for-each-group:

<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="/"> <salesByProduct>

<!– Cгpyппиpoвaть тoвapы по sku –> <xsl:for-each-group select="//product" group-by="@sku"> <xsl:copy> <xsl:copy-of select="@sku"/>

<!– Для подведения итогов по тoвapy используем current-group() –> <xsl:attribute name="totalSales"

select="format-number(sum(current-group()/@totalSales),’#’)"/> <!– Koпиpyем элементы salesperson, кoтopые coдеpжaт в качестве дoчеpнегo элемент со значением sku для текущей гpyппы тoвapoв –> <xsl:for-each select="/*/salesperson">

<xsl:if test="product[@sku eq current-grouping-key()]"> <xsl:copy>

<xsl:copy-of select="@*"/> </xsl:copy> </xsl:if> </xsl:for-each> </xsl:copy> </xsl:for-each-group> </salesByProduct> </xsl:template>

</xsl:stylesheet>

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

Это решение годится только для одного конкретного приложения. И тут ниче­го не поделаешь. Написать обобщенную таблицу стилей для реорганизации доку­мента трудно, если вообще возможно, поскольку преобразование сильно зависит от природы преобразовываемого документа.

Однако можно сформулировать соображения, применимые ко многим видам реорганизации.

Во-первых, поскольку дерево документа реорганизуется полностью, малове­роятно, что в основе решения будет лежать только сопоставление и применение шаблонов. Скорее, таблицы стилей будут итеративными, то есть в них будет ши­роко использоваться команда xsl:for-each.

Во-вторых, почти всегда придется инициализировать глобальные перемен­ные для хранения элементов, извлеченных с нижних уровней структуры XML- документа. Кроме того, вероятно, придется выделять подмножество, содержащее уникальные элементы такого рода. Детальное обсуждение методов построения таких подмножество см. в рецепте 5.3.

В-третьих, реорганизация часто включает агрегирование данных с вычисле­нием сумм, произведений и более сложных агрегатов. Методы агрегирования рас­сматриваются в главах 3 и 16.

Ясно, что команда xsl:for-each-group заметно упрощает реорганизацию XML-документов. Главное – четко уяснить, на основе какого критерия про­изводится группировка, а затем реорганизовать элементы по их соотноше­нию с группой.

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

По теме:

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