Технология XSLT — страница 20 из 66

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

Существуют пять основных шаблонных правил, которые применяются процессорами по умолчанию.

Первое из них обеспечивает рекурсивную обработку дочерних элементов документа, которые находятся как в корне, так и в других элементах. Это правило эквивалентно следующему шаблону:

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

В XSLT также определяется встроенное правило для обработки текстовых узлов и атрибутов — это правило просто выводит их текстовые значения. Шаблон такого преобразования может быть записан в виде:

Четвертое правило касается обработки инструкций по обработке и комментариев. Это правило не делает ничего, то есть инструкции и комментарии просто опускаются в выходящем документе. Шаблон такого преобразования будет иметь вид

Последнее, пятое правило определяет обработку узлов пространств имен. Аналогично инструкциям и комментариям, с ними по умолчанию не следует делать ничего, то есть узлы пространств имен просто удаляются.

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

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

Идентичное преобразование

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

bold
в элементы с именами
b
. Однако результатом обработки документа

<а>

 text a

  text b

  text c 

преобразованием

 version="1.0"

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



будет документ вида

text a

 text b

text c

Как можно заметить, в выходящем документе элементы

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

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

Листинг 5.11. Идентичное преобразование

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


Единственный шаблон этого преобразования при помощи элемента

xsl:copy
рекурсивно создает в выходящем документе копии узлов и атрибутов. На практике идентичное преобразование используется очень часто, и потому мы настоятельно рекомендуем сохранить его в отдельном файле и импортировать при потребности.

Поясним, что это преобразование выполняет не просто копирование документа (для этого было бы достаточно элемента

xsl:copy-of
). Идентичное преобразование переопределяет встроенные шаблоны; теперь, если для обработки какого-либо узла в преобразовании не определено подходящего шаблона, он вместе со всеми своими потомками будет скопирован в выходящий документ без изменений.

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

bold
. Искомое преобразование, импортирующее идентичное преобразование из файла
identity.xsl
, будет записано следующим образом.

Листинг 5.12

 version="1.0"

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

Результат будет иметь вид:

 text a

  text b

  text c

Другим примером использования идентичного преобразования может послужить случай, когда нужно просто удалить из документа некоторые узлы. Например, нам необходимо избавиться от комментариев (быстро и без шума).

 version="1.0"

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

Разрешение конфликтов в шаблонах

Как правило, каждое преобразование в XSLT определяет, включает или импортирует множество шаблонов, которые обрабатывают указанные части документов. При этом один и тот же узел документа может соответствовать нескольким шаблонным правилам. К примеру, элемент

content
может быть обработан любым из следующих трех шаблонов.

Листинг 5.14. Конфликтующие шаблоны



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

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

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

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

Во втором из этих двух правил, мы встретились с понятием приоритета шаблона. Приоритет шаблона это не что иное, как численное значение, которое может быть указано в атрибуте

priority
элемента
xsl:template
. В том случае, если значение этого атрибута не определено, приоритет шаблонного правила вычисляется следующим образом.

□ Прежде всего, шаблон, который обрабатывает несколько альтернатив, перечисленных через знак "

|
", будет рассматриваться как множество шаблонов, обрабатывающих каждую из возможностей. Например, шаблон с атрибутом
match="b|bold|B"
будет рассматриваться как три одинаковых шаблона с атрибутами
match="b"
,
match="bold"
и
match="B"
соответственно.

□ Если паттерн состоит из имени (

QName
) или конструкции
processing-instruction(литерал)
, которым предшествует дескриптор оси дочернего узла или атрибута (
ChildOrAttributeAxisSpecifier
), приоритет шаблона равен
0
. Такие паттерны могут иметь следующий вид:

 • 

QName
или
child::QName
— выбор дочерних элементов;

 • 

@QName
или
attribute::QName
— выбор атрибутов;

 • 

processing-instruction(литерал)
или
child::processing-instruction(литерал)
— именной выбор дочерних инструкций по обработке.

Примеры паттернов с приоритетом, равным

0
:

 • 

content
— выбор дочернего элемента
content
;

 • 

fo:content
— выбор дочернего элемента
content
с префиксом пространств имен
fo
;

 • 

child::processing-instruction('арр')
— выбор дочерних инструкций по обработке, которые имеют вид
;

 • 

@xsd:name
— выбор атрибута
xsd:name
текущего узла;

 • 

@select
— выбор атрибута
select
текущего узла.

□ Если паттерн состоит из конструкции

NCName:*
, которой предшествует
ChildOrAxisSpecifier
, приоритет шаблона будет равен
-0.25
. Такие паттерны могут иметь следующий вид:

 • 

префикс:*
или
child::префикс:*
— выбор всех дочерних элементов в определенном пространстве имен;

 • 

@префикс:*
или
attribute::префикс:*
— выбор всех атрибутов в определенном пространстве имен.

Примеры паттернов с приоритетом, равным

-0.25
:

 • 

fo:*
— выбор всех дочерних элементов в пространстве имен с префиксом
fo
;

 • 

attribute::xsl:*
— выбор всех атрибутов текущего элемента, которые находятся в пространстве имен с префиксом
xsl
.

□ Если паттерн состоит из проверки узла (

NodeTest
), которой предшествует
ChildOrAttributeAxisSpecifier
, приоритет шаблона будет равен
-0.5
. Паттерны такого рода будут выглядеть как:

 • 

NodeTest
или
child::NodeTest
— выбор всех дочерних узлов, соответствующих данной проверке;

 • 

QNodeTest
или
attribute::NodeTest
— выбор всех атрибутов, соответствующих данной проверке.

□ Примеры паттернов с приоритетом, равным

-0.5
:

 • 

text()
— выбор дочерних текстовых узлов;

 • 

child::comment()
— выбор дочерних комментариев;

 • 

@*
— выбор всех атрибутов данного шаблона.

□ Если ни одно из предыдущих условий не выполняется, приоритет шаблона равен

0.5
.

Для удобства использования составим таблицу (табл. 5.1) с приоритетами тех или иных паттернов.


Таблица 5.1. Приоритет паттернов

Вид паттернаПриоритет
QName
0
child::QName
@QName
attribute::QName
processing-instruction(литерал)
child::processing-instruction(литерал)
префикс:*
-0.25
child::префикс:*
@префикс:*
attribute::префикс:*
NodeTest
-0.5
child::NodeTest
@NodeTest
attribute::NodeTest
Другие паттерны
0.5

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

Пример

Вычислим в качестве упражнения приоритеты шаблонов для следующего примера.

Листинг 5.15. Преобразование

 version="1.0"

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

 xmlns:a="a">



1


2


3


4


5


 template matched 

.

Пять шаблонов этого преобразования могут соответствовать одним и тем же узлам, а значит, создавать множество конфликтов, которые будут разрешаться при помощи механизма приоритетов.

Приоритет первого шаблона, паттерн которого соответствует продукции QName, будет равен

0
. Приоритет второго шаблона будет равен
0.5
, поскольку его паттерн не удовлетворяет другим условиям. Паттерн третьего шаблона имеет вид
NCName:*
, а значит, его приоритет равен
-0.25
. Приоритет четвертого шаблона равен
-0.5
, поскольку его паттерн является проверкой узла (
NodeTest
). Приоритет последнего, пятого шаблона будет равен 0, поскольку паттерн
b
соответствует продукции
QName
.

Попробуем применить это преобразование к следующему документу:

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

□ Инструкции по обработке

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

□ Корневому элементу

b
соответствуют два шаблона — четвертый и пятый, однако приоритет пятого шаблона выше, и поэтому применен будет именно он.

□ Следующему элементу,

a
, соответствуют третий и четвертый шаблоны. Здесь процессор должен применить третий шаблон, так как его приоритет выше, чем приоритет четвертого шаблона.

□ Элемент

b
, включенный в элемент
а
, соответствует первому, второму, третьему и четвертому шаблонам. Наивысший приоритет из них имеет второй шаблон.

□ Следующему элементу

b
соответствуют первый, третий и четвертый шаблоны. В этом случае процессор выберет первый шаблон.

□ Элемент

с
соответствует третьему и четвертому шаблонному правилу. В этом случае процессор должен будет использовать третий шаблон.

Сравнивая этот анализ с сообщениями процессора, можно убедиться в верности прогноза:

4 template matched ORA.

5 template matched b.

3 template matched a.

2 template matched b.

1 template matched b.

3 template matched c.

Напомним, что приоритет преобразований может быть также явно указан в атрибуте

priority
элемента
xsl:template
. Например, если бы в предыдущем преобразовании четвертый шаблон был определен в виде

4

то его приоритет был бы выше, чем у всех остальных шаблонов, а поскольку он соответствует всем узлам в обрабатываемом документе, то во всех случаях применялся бы только он один. Сообщения процессора имели бы вид

4 template matched ORA.

4 template matched b.

4 template matched a.

4 template matched b.

4 template matched b.

4 template matched c.

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

Пример
Листинг 5.16. Основное преобразование

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

 version="1.0"

 xmlns:a="a">


1


2


3


5

Листинг 5.17. Преобразование b.xsl

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

 version="1.0"

 xmlns:a="a">


4


 template matched 

.

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

4 template matched ORA.

5 template matched b.

3 template matched a.

2 template matched b.

1 template matched b.

3 template matched c.

Кстати сказать, XSLT предоставляет возможность выполнять в преобразованиях импортированные шаблоны вместо тех, которые, по мнению процессора, лучше подходят. Подобно тому, как для применения шаблонных правил мы использовали элемент

xsl:apply-templates
, импортированные шаблоны могут быть вызваны элементом
xsl:apply-imports
.

Элемент xsl:apply-imports

Синтаксис этого элемента:

Элемент

xsl:apply-imports
можно использовать в шаблонах для применения правил, которые были импортированы во внешних модулях, но затем переопределены шаблонами основного преобразования.

Пример

Предположим, что в преобразованиях часто используется шаблон, который заменяет элементы

home
ссылками на сайт http://www.xsltdev.ru:

<а href="http://www.xsltdev.ru">www.xsltdev.ru

При необходимости этот шаблон может быть переопределен. К примеру, ссылка может выглядеть как

Visit <а href="http://www.xsltdev.ru">www.xsltdev.ru

Соответственно, шаблон будет иметь вид

Visit 

www.xsltdev.ru

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

home.xsl
.

Листинг 5.18. Преобразование home.xml

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

www.xsltdev.ru

Для того чтобы использовать внешний шаблон, основное преобразование должно импортировать его при помощи

xsl:import
и применять посредством xsl:apply-imports.

Листинг 5.19. Основное преобразование base.xsl

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

 version="1.0">

Visit 

Элемент

xsl:apply-imports
нельзя использовать в блоках
xsl:for-each
и при вычислении глобальных переменных. Дело в том, что при обработке
xsl:apply-imports
процессор применяет импортируемые правила в соответствии с текущим шаблоном. Текущий шаблон — это то самое правило, которое процессор выполняет при обработке элемента
xsl:apply-templates
. При вычислении глобальных переменных и обработке блоков
xsl:for-each
текущее правило становится пустым, и, соответственно, вызов
xsl:apply-imports
вызовет ошибку.

Элемент

xsl:apply-imports
применяет шаблоны точно так же, как и элемент
xsl:apply-templates
, но при этом он имеет две особенности.

□ Шаблоны, определенные в основном преобразовании, применяться не будут, поскольку

xsl:apply-imports
применяет только импортированные правила.

□ Элемент

xsl:apply-imports
применяет только те правила, режим (
mode
) которых совпадает с режимом текущего шаблона.

В текущей версии XSLT

xsl:apply-imports
не может вызывать импортированные именованные шаблоны.

Для того чтобы лучше понять, зачем нужна такая сложная схема импорта, проведем аналогию с объектно-ориентированным программированием. Если рассматривать правила преобразований как методы классов, то импорт преобразований будет ни чем иным, как наследованием — все методы (шаблоны) класса-потомка (импортируемого преобразования) будут доступны в классе-наследнике (импортирующем преобразовании). При этом класс-наследник может переопределить методы класса потомка (шаблоны основного преобразования имеют порядок импорта старше, чем шаблоны импортированного преобразования). В этой схеме использование элемента

xsl:apply-imports
будет равносильно вызову метода родительского класса вместо переопределенного метода класса потомка.

Приведем пример Java-классов, которые будут аналогичны преобразованиям

home.xsl
и
base.xsl
.

Листинг 5.20. Класс home

public class home {

 public String home() {

  return "www.xsltdev.ru";

 }

}

Листинг 5.21. Класс base

public class base extends home {

 public String home() {

  return ("Visit " + super.home());

 }

}

В этом примере вызов родительского метода

super.home()
соответствует применению элементом
xsl:apply-imports
импортированного шаблона.

Тело шаблона