Главная » XSLT » Соединения

0

Задача

Требуется соотнести элементы в некотором документе с другими элементами в том же или ином документе.

Решение

Чтобы выполнить соединение, мы рассматриваем все возможные пары элементов (то есть берем декартово произведение) и оставляем только те, которые удовлетворя­ют критерию, по которому выполняется соединение (обычно это равенство).

Для демонстрации я адаптировал для XML базу данных о деталях, приведен­ную в книге Date An Introduction to Database Systems (Addison Wesley, 1986)1:

<database> <suppliers>

<supplier id="S1" name="Smith" status="20" city="London"/> <supplier id="S2" name="Jones" status="10" city="Paris"/> <supplier id="S3" name="Blake" status="30" city="Paris"/> <supplier id="S4" name="Clark" status="20" city="London"/> <supplier id="S5" name="Adams" status="30" city="Athens"/> </suppliers> <parts>

<part id="P1" name="Nut" color="Red" weight="12" city="Londo"/> <part id="P2" name="Bult" color="Green" weight="17" city="Paris"/> <part id="P3" name="Screw" color="Blue" weight="17" city="Rome"/> <part id="P4" name="Screw" color="Red" weight="14" city="London"/> <part id="P5" name="Cam" color="Blue" weight="12" city="Paris"/> <part id="P6" name="Cog" color="Red" weight="19" city="London"/> </parts> <inventory">

<invrec sid="S1" pid="P1" qty="300"/>

<invrec sid="S1" pid="P2" qty="200"/> <invrec sid="S1" pid="P3" qty="400"/> <invrec sid="S1" pid="P4" qty="200"/> <invrec sid="S1" pid="P5" qty="100"/> <invrec sid="S1" pid="P6" qty="100"/> <invrec sid="S2" pid="P1" qty="300"/> <invrec sid="S2" pid="P2" qty="400"/> <invrec sid="S3" pid="P2" qty="200"/> <invrec sid="S4" pid="P2" qty="200"/> <invrec sid="S4" pid="P4" qty="300"/> <invrec sid="S4" pid="P5" qty="400"/> </inventory> </database>

Нам предстоит выполнить соединение, которое отвечает на вопрос: «Какие поставщики и детали находятся в одном и том же городе?»

К решению этой задачи на XSLT есть два основных подхода. Во-первых, мож­но использовать вложенные циклы for-each:

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

<xsl:for-each select="database/suppliers/*"> <xsl:variable name="supplier" select="."/>

<xsl:for-each select="/database/parts/*[@city=current()/@city]"> <colocated>

<xsl:copy-of select="$supplier"/> <xsl:copy-of select="."/> </colocated> </xsl:for-each> </xsl:for-each> </result> </xsl:template>

Второй способ – применить команду apply-templates:

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

<xsl:apply-templates select="database/suppliers/supplier" /> </result> </xsl:template>

<xsl:template match="supplier">

<xsl:apply-templates select="/database/parts/part[@city = current()/@city]">

<xsl:with-param name="supplier" select="." /> </xsl:apply-templates> </xsl:template>

<xsl:template match="part">

<xsl:param name="supplier" select="/.." /> <colocated>

<xsl:copy-of select="$supplier" /> <xsl:copy-of select="." /> </colocated> </xsl:template>

Если в одном из соединяемых наборов очень много элементов, то для повыше­ния производительности можно воспользоваться командой xsl:key:

<xsl:key name="part-city" match="part" use="@city"/>

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

<xsl:for-each select="database/suppliers/*">

<xsl:variable name="supplier" select="."/> <xsl:for-each select="key(‘part-city’,$supplier/@city)"> <colocated>

<xsl:copy-of select="$supplier"/> <xsl:copy-of select="."/> </colocated> </xsl:for-each> </xsl:for-each> </result> </xsl:template>

Обе таблицы стилей дают один и тот же результат:

<result>

<colocated>

<supplier id="S1" name="Smith" status="20" city="London"/> <part id="P1" name="Nut" color="Red" weight="12" city="London"/> </colocated> <colocated>

<supplier id="S1" name="Smith" status="20" city="London"/> <part id="P4" name="Screw" color="Red" weight="14" city="London"/> </colocated> <colocated>

<supplier id="S1" name="Smith" status="20" city="London"/> <part id="P6" name="Cog" color="Red" weight="19" city="London"/> </colocated> <colocated>

<supplier id="S2" name="Jones" status="10" city="Paris"/> <part id="P2" name="Bult" color="Green" weight="17" city="Paris"/> </colocated>

<colocated>

<supplier id="S2" name="Jones" status="10" city="Paris"/> <part id="P5" name="Cam" color="Blue" weight="12" city="Paris"/> </colocated> <colocated>

<supplier id="S3" name="Blake" status="30" city="Paris"/> <part id="P2" name="Bult" color="Green" weight="17" city="Paris"/> </colocated> <colocated>

<supplier id="S3" name="Blake" status="30" city="Paris"/> <part id="P5" name="Cam" color="Blue" weight="12" city="Paris"/> </colocated> <colocated>

<supplier id="S4" name="Clark" status="20" city="London"/> <part id="P1" name="Nut" color="Red" weight="12" city="London"/> </colocated> <colocated>

<supplier id="S4" name="Clark" status="20" city="London"/> <part id="P4" name="Screw" color="Red" weight="14" city="London"/> </colocated> <colocated>

<supplier id="S4" name="Clark" status="20" city="London"/> <part id="P6" name="Cog" color="Red" weight="19" city="London"/> </colocated>

</result>

Обсуждение

XSLT 1.0

Соединение, которое мы только что выполнили, называется соединением по равенству (equi-join), поскольку элементы сравнивались на равенство. В общем случае можно задавать и другие отношения. Рассмотрим, например, такой запрос: «Отобрать все сочетания поставщиков и деталей, в которых город, где находится поставщик, больше города, в котором производится деталь, в смысле лексикогра­фического порядка».

Хорошо бы написать соответствующую таблицу стилей, как показано ниже, но в XSLT 1.0 не определены операции сравнения строк:

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

<xsl:for-each select="database/suppliers/*"> <xsl:variable name="supplier" select="."/> <!– Это не работает! –> <xsl:for-each select="/database/parts/*[current( )/@city > @city]">

<colocated>

<xsl:copy-of select="$supplier"/> <xsl:copy-of select="."/> </colocated> </xsl:for-each> </xsl:for-each> </result> </xsl:template>

Вместо этого придется создать таблицу с помощью команды xsl:sort, ко­торая отобразит названия городов на целые числа, отражающие порядок следо­вания. Здесь мы пользуемся умением процессора Saxon рассматривать перемен­ные, которые содержат фрагменты результирующего дерева, как наборы узлов, если установлена версия 1.1. Но можно также воспользоваться функцией node- set(), если она поддерживается вашим процессором XSLT 1.0, или обратиться к процессору XSLT 2.0:

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

<xsl:variable name="unique-cities"

select="//@city[not(. = ../preceding::*/@city)]"/>

<xsl:variable name="city-ordering">

<xsl:for-each select="$unique-cities"> <xsl:sort select="."/>

<city name="{.}" order="{position()}"/> </xsl:for-each> </xsl:variable>

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

<xsl:for-each select="database/suppliers/*"> <xsl:variable name="s" select="."/> <xsl:for-each select="/database/parts/*"> <xsl:variable name="p" select="."/> <xsl:if

test="$city-ordering/*[@name = $s/@city]/@order &gt;

$city-ordering/*[@name = $p/@city]/@order"> <supplier-city-follows-part-city> <xsl:copy-of select="$s"/> <xsl:copy-of select="$p"/> </supplier-city-follows-part-city> </xsl:if> </xsl:for-each> </xsl:for-each> </result>

</xsl:template> </xsl:stylesheet>

Этот запрос дает следующий результат:

<result>

<supplier-city-follows-part-city>

<supplier id="S2" name="Jones" status="10" city="Paris"/> <part id="P1" name="Nut" color="Red" weight="12" city="London"/> </supplier-city-follows-part-city> <supplier-city-follows-part-city>

<supplier id="S2" name="Jones" status="10" city="Paris"/> <part id="P4" name="Screw" color="Red" weight="14" city="London"/> </supplier-city-follows-part-city> <supplier-city-follows-part-city>

<supplier id="S2" name="Jones" status="10" city="Paris"/> <part id="P6" name="Cog" color="Red" weight="19" city="London"/> </supplier-city-follows-part-city> <supplier-city-follows-part-city>

<supplier id="S3" name="Blake" status="30" city="Paris"/> <part id="P1" name="Nut" color="Red" weight="12" city="London"/> </supplier-city-follows-part-city> <supplier-city-follows-part-city>

<supplier id="S3" name="Blake" status="30" city="Paris"/> <part id="P4" name="Screw" color="Red" weight="14" city="London"/> </supplier-city-follows-part-city> <supplier-city-follows-part-city>

<supplier id="S3" name="Blake" status="30" city="Paris"/> <part id="P6" name="Cog" color="Red" weight="19" city="London"/> </supplier-city-follows-part-city> </result>

XSLT 2.0

В XSLT 2.0 операторы сравнения корректно работают со строковыми значе­ниями, поэтому годится и более простая таблица стилей:

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

<xsl:for-each select="database/suppliers/*"> <xsl:variable name="supplier" select="."/>

<!– В версии 2.0 это допустимо –> <xsl:for-each select="/database/parts/*[current( )/@city > @city]">

<colocated>

<xsl:copy-of select="$supplier"/> <xsl:copy-of select="."/>

</colocated> </xsl:for-each> </xsl:for-each> </result>

</xsl:template>

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

По теме:

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