Главная » XSLT » Углубление иерархии XML

0

Задача

Имеется плохо спроектированный документ, которому не помешали бы до­полнительные структурные уровни[2].

Решение

Эта задача противоположна рассмотренной в рецепте 8.7. Здесь нужно доба­вить в документ структурные уровни, возможно, для того чтобы организовать элементы по какому-нибудь дополнительному критерию.

Добавление структуры на основе уже имеющихся данных

Показанное ниже преобразование обратно уплощению, выполненному в рецепте 8.7.

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

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

<xsl:template match="people"> <union>

<xsl:apply-templates select="person[@class = ‘union’]" /> </union> <salaried>

<xsl:apply-templates select="person[@class = ‘salaried’]" /> </salaried> </xsl:template>

</xsl:stylesheet>

Добавление структуры для исправления плохо спроектированного документа

Ошибочно стремясь упростить XML-разметку, некоторые авторы пытаются зако­дировать информацию, вставляя элементы-братья вместо родительских элементов1.

Предположим, например, что кто-то решил следующим образом различать работников, получающих оклад и являющихся членами профсоюза:

<people>

<class name="union"/>

<person>

<firstname>Warren</firstname> <lastname>Rosenbaum</lastname> <age>37</age> <height>5.75</height> </person>

<person>

<firstname>Theresa</firstname> <lastname>Archul</lastname> <age>37</age> <height>5.5</height> </person>

<class name="salaried"/>

<person>

<firstname>Sal</firstname> <lastname>Mangano</lastname> <age>37</age> <height>5.75</height> </person>

<person>

<firstname>James</firstname> <lastname>O’Riely</lastname> <age>33</age> <height>5.5</height> </person> </people>

Обратите внимание, что элементы class, определяющие принадлежность той или другой категории, теперь пусты. Имелось в виду, что все последующие братья элемента class считаются принадлежащими указанному классу, пока не встретится другой элемент class или не останется братьев. Такой способ ко­дирования легко воспринимается на взгляд, но обрабатывать его с помощью XSLT сложнее. Чтобы исправить ситуацию, понадобится таблица стилей, кото­рая вычисляет разность между множеством элементов person, следующих за первым и последующим вхождениями элемента class. В XSLT 1.0 нет функции для явного вычисления разности множеств. Добиться того же эффекта, причем более эффективно, можно, рассмотрев те элементы, следующие за элементом class, позиция которых меньше, чем у элементов, следующих за последующим элементом class:

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

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

<!– Общее число людей –>

<xsl:variable name="num-people" select="count(/*/person)"/> <xsl:template match="class">

<!– Последняя интеpеcyющaя нас позиция. –> <xsl:variable name="pos"

select="$num-people -

count(following-sibling::class/following-sibling::person)"/> <xsl:element name="{@name}">

<!– Cкoпиpoвaть людей, следующих за этим элементом class, позиция

кoтopыx меньше или paBHa $pos.–>

<xsl:copy-of

select="following-sibling::person[position() &lt; = $pos]"/> </xsl:element> </xsl:template>

<!– Игнopиpoвaть элементы person. Они уже cкoпиpoвaны выше. –> <xsl:template match="person"/>

</xsl:stylesheet>

Более тонкое решение получается при использовании ключа:

<xsl:key name="people" match="person"

use="preceding-sibling::class[1]/@name" />

<xsl:template match="people"> <people>

<xsl:apply-templates select="class" /> </people>

</xsl:template>

<xsl:template match="class">

<xsl:element name="{@name}">

<xsl:copy-of select="key(‘people’, @name)" /> </xsl:element>

</xsl:template>

Еще одна альтернатива – пошаговый подход:

<xsl:template match="people"> <people>

<xsl:apply-templates select="class[1]" /> </people>

</xsl:template>

<xsl:template match="class"> <xsl:element name="{@name}">

<xsl:apply-templates select="following-sibling::*[1][self::person]" />

</xsl:element>

<xsl:apply-templates select="following-sibling::class[1]" /> </xsl:template>

<xsl:template match="person"> <xsl:copy-of select="." />

<xsl:apply-templates select="following-sibling::*[1][self::person]" /> </xsl:template>

XSLT 2.0

Добавление структуры на основе уже имеющихся данных

Имеющаяся в XSLT 2.0 команда xsl:for-each-group позволяет написать более общее решение, чем возможно в версии 1.0. Хотя и для 1.0 существуют обоб­щенные решения (см. обсуждение), но все они сложнее:

<xsl:template match="people">

<xsl:for-each-group select="person"

group-by="preceding-sibling::class[1]/@name"> <xsl:element name="{curent-grouping-key()">

<xsl:apply-templates select="current-group()" /> </xsl:element> </xsl:for-each> </xsl:template>

Добавление структуры для исправления плохо спроектированного документа

Можно воспользоваться командой xsl:for-each-group с атрибутом

starting-with:

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

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

<xsl:template match="people"> <xsl:copy>

<xsl:for-each-group select="*" group-starting-with="class"> <xsl:element name="{@name}">

<xsl:apply-templates select="current-group()[not(self::class)]"/> </xsl:element> </xsl:for-each-group> </xsl:copy>

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

Обсуждение

Добавление структуры на основе уже имеющихся данных

Добавляя структуру на основе уже имеющихся данных, мы явно ссылались на критерий выделения представляющих интерес категорий (например, union и salaried). Лучше, если бы таблица стилей умела самостоятельно находить эти категории. Тогда она стала бы более общей, пусть даже ценой усложнения:

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

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

<!– Строим список всех уникальных классов –> <xsl:variable name="classes"

select="/*/*/@class[not(. = ../preceding-sibling::*/@class)]"/> <xsl:template match="/*">

<!– Для каждого класса создаем элемент с именем, которое задано в элементе class. Он будет содержать все элементы, принадлежащие данному классу. –> <xsl:for-each select="$classes">

<xsl:variable name="class-name" select="."/> <xsl:element name="{$class-name}">

<xsl:for-each select="/*/*[@class=$class-name]"> <xsl:copy>

<xsl:apply-templates/> </xsl:copy> </xsl:for-each> </xsl:element> </xsl:for-each> </xsl:template>

</xsl:stylesheet>

Хотя эта таблица стилей и не общая на все 100%, но в ней нет предположе­ний о том, какие классы имеются в документе. Единственная информация, спе­цифичная для приложения, – это тот факт, что категории закодированы в атри­буте @class, и этот атрибут встречается в элементах, отстоящих от корня на два уровня.

Добавление структуры для исправления плохо спроектированного документа

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

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

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

<xsl:template match="class">

<!– Все люди, следующие за элементом class –>

<xsl:variable name="nodes1" select="following-sibling::person"/> <!– Все люди, следующие за последующим элементом class –> <xsl:variable name="nodes2"

select="following-sibling::class/following-sibling::person"/> <xsl:element name="{@name}">

<xsl:copy-of select="$nodes1[count(. | $nodes2) != count($nodes2)]"/> </xsl:element> </xsl:template>

<xsl:template match="person"/>

</xsl:stylesheet>

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

По теме:

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