Главная » XSLT » Объединение документов с одной и той же схемой

0

Задача

Есть несколько документов с одинаковой структурой, а нужно объединить их в один.

Решение

Если все документы имеют разное содержимое или вы готовы смириться с дубликатами, то решение несложно:

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

<xsl:output method="xml" indent="yes"/>

<xsl:param name="doc2"/>

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

<xsl:copy-of select="* | document($doc2)/*/*"/> </xsl:copy> </xsl:template>

</xsl:stylesheet>

Если во входных документах есть дубликаты, а вы хотите, чтобы в выходном документе их не было, то для удаления дубликатов можно применить рецепт 5.1. Рассмотрим документы, представленные в примерах 8.12 и 8.13.

Пример 8.12. Документ 1

<people which="MeAndMyFriends">

<person firstname="Sal" lastname="Mangano" age="38" height="5.75"/> <person firstname="Mike" lastname="Palmieri" age="28" height="5.10"/> <person firstname="Vito" lastname="Palmieri" age="38" height="6.0"/> <person firstname="Vinny" lastname="Mari" age="37" height="5.8"/> </people>

Пример 8.13. Документ 2

<people which="MeAndMyCoWorkers">

<person firstname="Sal" lastname="Mangano" age="38" height="5.75"/> <person firstname="Al" lastname="Zehtooney" age="33" height="5.3"/> <person firstname="Brad" lastname="York" age="38" height="6.0"/> <person firstname="Charles" lastname="Xavier" age="32" height="5.8"/> </people>

Следующая таблица стилей объединяет оба документа и устраняет повторяю­щийся элемент с помощью команды xsl:sort и расширения exsl:node-set:

<xsl:stylesheet version="1.0"

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

<xsl:import href="exsl.xsl" />

<xsl:param name="doc2"/>

<!— Атрибут ‘key’ введен для того, чтобы упростить удаление дубликатов —> <xsl:variable name="all">

<xsl:for-each select="/*/person | document($doc2)/*/person"> <xsl:sort select="concat(@lastname,@firstname)"/> <person key="{concat(@lastname, @firstname)}">

<xsl:copy-of select="@* | node()" /> </person> </xsl:for-each> </xsl:variable>

<xsl:template match="/">

<people>

<xsl:for-each

select="exsl:node-set($all)/person[not(@key =

preceding-sibling::person[1]/@key)]"> <xsl:copy-of select="."/> </xsl:for-each> </people>

</xsl:template>

У такого способа удаления дубликатов есть три недостатка. Во-первых, изме­няется порядок элементов, а это может быть нежелательно. Во-вторых, требуется функция расширения node-set, которой нет в стандартном XSLT 1.0. В-третьих, эта таблица стилей не является достаточно общей, поскольку ее нужно переписы­вать целиком для каждого случая удаления дубликатов.

Решить эти проблемы можно с помощью элемента xsl:key:

<!– Stylesheet: merge-simple-using-key.xslt –>

<!– Импортировать эту таблицу стилей в другую, где определен ключ –>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:merge="http:www.ora.com/XSLTCookbook/mnamespaces/merge">

<xsl:param name="doc2"/>

<xsl:template match="/*">

<!– Копировать самый внешний элемент исходного документа –> <xsl:copy>

<!– Для каждого дочернего элемента в исходном документе решаем, нужно ли копировать его в выходной, основываясь на том, есть он в другом документе или нет –> <xsl:for-each select="*">

<!– Вызываем шаблон, который определяет уникальный ключ для этого элемента. Он должен быть определен во включающей таблице стилей. –>

<xsl:variable name="key-value">

<xsl:call-template name="merge:key-value"/> </xsl:variable>

<xsl:variable name="element" select="."/> <!– Этот цикл for-each нужен просто для переключения контекста на второй документ. –> <xsl:for-each select="document($doc2)/*">

<!– Применяем механизм ключей для проверки присутствия элемента во втором документе. Ключ должен быть определен во включающей таблице стилей. –>

<xsl:if test="not(key(‘merge:key’, $key-value))">

<xsl:copy-of select="$element"/> </xsl:if> </xsl:for-each>

</xsl:for-each>

<!– Копируем все элементы из второго документа. –> <xsl:copy-of select="document($doc2)/*/*"/>

</xsl:copy> </xsl:template>

</xsl:stylesheet>

Следующая таблица стилей импортирует предыдущую, в ней определен ключ и шаблон для получения значения ключа:

<!– Эта таблица стилей определяет уникальность элементов в терминах ключей. –>

<xsl:stylesheet version="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:merge="http:www.ora.com/XSLTCookbook/mnamespaces/merge">

<xsl:include href="merge-simple-using-key.xslt"/>

<!– Человек уникально определяется конкатенацией имени и фамилии –> <xsl:key name="merge:key" match="person"

use="concat(@lastname,@firstname)"/>

<!– Этот шаблон возвращает значение ключа элемента –> <xsl:template name="merge:key-value">

<xsl:value-of select="concat(@lastname,@firstname)"/> </xsl:template>

</xsl:stylesheet>

Другой способ объединить документы с устранением дубликатов основан на теоретико-множественных операциях над значениями, которые обсуждаются в рецепте 9.2. Решение мы приводим здесь, но отсылаем читателя к упомянутому рецепту за дополнительной информацией. Нужные таблицы стилей приведены в примерах 8.14 и 8.15.

Пример 8.14. Повторно используемая таблица стилей, реализующая объединение документов в терминах теоретико-множественной опера­ции объединения

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset">

<xsl:import href="../query/vset.ops.xslt"/>

<xsl:output method="xml" indent="yes"/>

<xsl:param name="doc2"/>

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

<xsl:call-template name="vset:union">

<xsl:with-param name="nodes1" select="*"/>

<xsl:with-param name="nodes2" select="document($doc2)/*/*"/> </xsl:call-template> </xsl:copy> </xsl:template>

</xsl:stylesheet>

Пример 8.15. Таблица стилей, определяющая, что означает равенство элементов

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:vset="http:/www.ora.com/XSLTCookbook/namespaces/vset">

<xsl:import href="merge-using-vset-union.xslt"/>

<xsl:param name="other"/>

<xsl:if test="concat(@lastname,@firstname) =

concat($other/@lastname,$other/@firstname)"> <xsl:value-of select="true()"/> </xsl:if> </xsl:template>

</xsl:stylesheet>

Решение на основе шаблона vset:union требует меньше нового кода, чем основанное на ключах, однако для больших документов решение на базе xsl:key, скорее всего, окажется быстрее.

Обсуждение

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

В примерах из этого раздела рассмотрен простой случай, когда объединяют­ся только два документа. Если нужно объединить произвольное число докумен­тов, то необходим механизм передачи списка документов в таблицу стилей. Можно, например, передать параметр, содержащий все имена файлов, перечис­ленные через пробел, а затем вычленить отдельные имена, воспользовавшись простым шаблоном для выделения лексем (рецепт 2.9). Другой способ заключа­ется в передаче имен всех файлов в отдельном документе, как показано в приме­рах 8.16 и 8.17.

Пример 8.16. XML-файл, содержащий имена объединяемых документов

<mergeDocs>

<doc path="people1.xml"/> <doc path="people2.xml"/> <doc path="people3.xml"/> <doc path="people4.xml"/> </mergeDocs>

Пример 8.17. Таблица стилей для объединения документов (в предполо­жении, что содержимое не дублируется)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:variable name="docs" select="/*/doc"/> <xsl:template match="mergeDocs">

<xsl:apply-templates select="doc[1]"/> </xsl:template>

<!– Сопоставляем с первым документом, чтобы создать элемент верхнего уровня –>

<xsl:template match="doc">

<xsl:variable name="path" select="@path"/> <xsl:for-each select="document($path)/*"> <xsl:copy>

<!– Включаем в объединение дочерние элементы первого документа –> <xsl:copy-of select="@* | *"/>

<!– Цикл по остальным документам для объединения их дочерних элементов –>

<xsl:for-each select="$docs[position() > 1]"> <xsl:copy-of select="document(@path)/*/*"/> </xsl:for-each> </xsl:copy> </xsl:for-each> </xsl:template>

</xsl:stylesheet>

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

По теме:

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