Главная » XSLT » Язык XPath

0

XPath – это язык для записи выражений. Он имеет фундаментальное значе­ние для обработки XML-документов. Нельзя овладеть XSLT, не зная XPath, точ­но так же, как нельзя выучить английский язык, не зная алфавита. Некоторые читатели первого издания этой книги пеняли мне за то, что я не включил в нее основ XPath. Эта глава добавлена отчасти, чтобы удовлетворить их, но главным образом потому, что в спецификации XPath 2.0 выразительная мощь этого языка была значительно усилена. Впрочем, многие рецепты будут рабо­тать и с XPath 1.0.

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

В XSLT 2.0 связь с XPath 2.0 сохранена и даже стала прочнее. В нем широ­ко используются новые вычислительные средства, появившиеся в XPath 2.0. Можно даже сказать, что дополнительные возможности XSLT 2.0 – во многом результат нововведений в XPath 2.0, к числу которых относятся последова­тельности, регулярные выражения, условные и итеративные выражения, сис­тема типов, совместимая со спецификацией XML Schema, а также множество новых встроенных функций.

Каждый рецепт в этой главе – это подборка мини-рецептов для решения опре­деленного класса задач, возникающих в XPath в контексте XSLT. Все XPath-выра- жения прокомментированы в соответствии с принятым в XPath 2.0 соглашением (: комментарий :), но пользователям XPath 1.0 следует иметь в виду, что это недопустимая синтаксическая конструкция. Пустой результат вычисления XPath-выражения мы будем обозначать (), именно так в XPath 2.0 записывается пустая последовательность.

Задача

Требуется отобрать узлы XML-дерева с учетом сложных взаимосвязей в иерар­хической структуре.

Решение

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

Дочерняя ось и ось потомков

Дочерняя ось принимается в XPath по умолчанию. Иными словами, явно ука­зывать ось child:: необязательно, но, если вы хотите быть педантом, то можете и указать. Спуститься по XML-дереву глубже, чем на один уровень, позволяют

оси descendant:: и descendant-or-self::. Первая не включает сам кон­текстный узел, вторая – включает.

<Test id="descendants"> <parent>

<X id="1"/> <X id="2"/> <Y id="3">

<X id="3-1"/> <Y id="3-2"/> <X id="3-3"/>

</Y>

<X id="4"/> <Y id="5"/> <Z id="6"/> <X id="7"/> <X id="8"/> <Y id="9"/> </parent>

<Test>

(: Отобрать все дочерние элементы с именем X :)

X (: то же, что child::X)

Результат: <X id="1"/> <X id="2"/> <X id="4"/> <X id="7"/> <X id="8"/>

(: Отобрать первый дочерний элемент с именем X :) X[1]

Результат: <X id="1"/>

(: Отобрать последний дочерний элемент с именем X :) X[last()]

Результат: <X id="8"/>

(: Отобрать первый дочерний элемент при условии, что его имя X. Иначе пусто :) *[1][self::X]

Результат: <X id="1"/>

(: Отобрать последний дочерний элемент при условии, что его имя X. Иначе пусто :)

*[last()][self::X]

Результат: ()

(: Отобрать последний дочерний элемент при условии, что его имя Y. Иначе пусто :)

*[last()][self::Y]

Результат: <Y id="9"/>

(: Отобрать всех потомков с именем X :) descendant::X

Результат: <X id="1"/> <X id="2"/> <X id="3-1"/> <X id="3-3"/> <X id="4"/> <X id="7"/> <X id="8"/>

(: Отобрать контекстный узел, если его имя X, а также всех потомков с именем X :) descendant-or-self::X

Результат: <X id="1"/> <X id="2"/> <X id="3-1"/> <X id="3-3"/> <X id="4"/> <X id="7"/> <X id="8"/>

(: Отобрать контекстный узел и всех его потомков :) descendant-or-self::*

Результат: <parent> <X id="1"/> <X id="2"/> <Y id="3"> <X id="3-1"/> <Y id="3-2"/> <X id="3-3"/> </Y> <X id="4"/> <Y id="5"/> <Z id="6"/> <X id="7"/> <X id="8"/> <Y id="9"/> </parent>

Оси братьев

Оси братьев называются preceding-sibling:: и following-sibling::. Ось preceding-sibling содержит братьев, предшествующих контекстному узлу, а ось following-sibling – следующих за ним. Братьями, естественно, называются дети одного родителя. Почти во всех примерах ниже используется ось preceding-sibling::, но вам не составит труда вычислить результат и для оси following-sibling::.

Имейте в виду, что в позиционном выражении вида preceding- sibling::*[1] вы ссылаетесь на непосредственно предшествующего брата в порядке обратного отсчета от контекстного узла, а не первого брата в порядке доку­мента. Некоторых смущает тот факт, что результирующая последовательность воз­вращается в порядке документа вне зависимости от того, используется ось preceding-sibling:: или following-sibling::. В выражении ../X, строго говоря, никакая ось не указана; это просто способ отобрать предшествующего и пос­ледующего брата с именем X, а также сам контекстный узел, если он называется X. Формально это сокращенная запись выражения parent::node()/X. Отметим, что выражения(preceding-sibling::*)[1] и(following-sibling::*)[1] от­бирают первого предшествующего или последующего брата в порядке документа.

<!– Тестовый документ с выделенным контекстным узлом —> <Test id="preceding-siblings"> <A id="1"/> <A id="2"/> <B id="3"/> <A id="4"/> <B id="5"/> <C id="6"/> <A id="7"/> <A id="8"/> <B id="9"/> </Test>

(: Отобрать всех братьев с именем A, предшествующих контекстному узлу :) preceding-sibling::A

Результат: <A id="1"/> <A id="2"/> <A id="4"/>

(: Отобрать всех братьев с именем A, следующих за контекстным узлом :) following-sibling::A

Результат: <A id="8"/>

(: Отобрать всех братьев, предшествующих контекстному узлу :) preceding-sibling::*

Результат: <A id="1"/> <A id="2"/> <B id="3"/> <A id="4"/> <B id="5"/>

<C id="6"/>

(: Отобрать первого предшествующего брата с именем A в обратном порядке документа :) preceding-sibling::A[1]

Результат: <A id="4"/>

(: Отобрать первого предшествующего брата в обратном порядке документа при условии, что его имя A :) preceding-sibling::*[1][self::A]

Результат: ()

(: Если бы контекстным был узел <A id="8"/>, то в результате мы получили бы <A id="7"/> :)

(: Отобрать всех предшествующих братьев, кроме элементов с именем A :) preceding-sibling::*[not(self::A)]

Результат: <B id="3"/> <B id="5"/> <C id="6"/>

(: В следующих примерах используется такой тестовый документ :)

<Test id="preceding-siblings"> <A id="1"> <A/>

</A>

<A id="2"/> <B id="3"> <A/>

</B>

<A id="4"/> <B id="5"/> <C id="6"/> <A id="7"/>

<A id="8"/> <B id="9"/> </Test>

(: Элемент, непосредственно предшествующий контекстному узлу при усло­вии, что у того есть дочерний элемент с именем A :)

preceding-sibling::*[1][A]

Результат: ()

(: Первый из предшествующих контекстному узлу элементов, у которого есть дочерний элемент с именем A :)

preceding-sibling::*[A][1]

Результат: <B id="3"/> …

(: XPath 2.0 позволяет более гибко выбирать элементы с учетом про­странств имен. В следующих примерах используется такой XML-документ :)

<Test xmlns:NS="http://www.ora.com/xsltckbk/1" xmlns:NS2= "http:// www.ora.com/xsltckbk/2"> <NS:A id="1"/> <NS2:A id="2"/> <NS:B id="3"/> <NS2:B id="3"/> </Test>

(: Отобрать всех предшествующих братьев контекстного узла, для которых пространство имен ассоциировано с префиксом NS :) preceding-sibling::NS:*

Результат: <NS:A id="1"/>

(: Отобрать всех предшествующих братьев контекстного узла, для которых локальное имя равно A :) preceding-sibling::*:A

Результат: <NS:A id="1"/> <NS2:A id="2"/>

Родительская ось и ось предков

Родительская ось (parent:: ) относится к родителю контекстного узла. Не путайте выражение parent::X с ../X. Первое порождает последовательность, ко­торая содержит в точности один элемент, если имя родителя контекстного узла – X, и пуста в противном случае. Второе – это сокращенная запись выражения parent::node()/X, которое отбирает всех братьев контекстного узла с именем X, в том числе и сам этот узел, если он называется X.

С помощью осей ancestor:: и ancestor-or-self:: можно перемещать­ся вверх по XML-дереву (переходя к родителю, деду, прадеду и т.д.). В первом случае сам контекстный узел исключается, во втором – включается.

(: Возвращает родителя контекстного узла, при условии, что он называется X, в противном случае — пустую последовательность. :) parent::X

(: Возвращает родителя контекстного узла. Результат может быть пустым, только если контекстный узел является элементом верхнего уровня. :) parent::*

(: Возвращает родителя, если его пространство имен ассоциировано с префиксом X. Префикс должен быть определен, иначе произойдет ошибка. :)

parent::NS:*

(: Возвращает родителя независимо от его пространства имен при условии, что локальное имя равно X.:) parent::*:X

(: Возвращает всех предков (включая родителя) с именем X. :) ancestor::X

(: Возвращает контекстный узел, если он называется X, а также всех предков с именем X. :) ancestor-or-self::X

Оси предшественников и последователей

Оси preceding:: и following:: могут отбирать очень много узлов, по­скольку учитываются все узлы, предшествующие контекстному (или следующие за ним) в порядке документа, исключая предков в случае оси following:: или потомков для оси preceding::. Не забывайте также, что обе оси не включают узлы, соответствующие пространствам имен и атрибутам.

(: Все предшествующие узлы с именем X. :) preceding::X

(: Ближайший предшественник с именем X. :) preceding::X[1]

(: Самый дальний последователь с именем X. :) following::X[last()]

Обсуждение

В языке XPath понятие оси служит для того, чтобы выделить в дереве доку­мента различные подмножества узлов относительно некоторого узла, называемо­го контекстным. В общем случае эти подмножества пересекаются, но оси ancestor, descendant, following, preceding и self разбивают документ на непересекающиеся части, в совокупности содержащие все узлы (за исключением тех, что соответ­ствуют пространствам имен и атрибутам). Контекстный узел устанавливается языком, в который погружен XPath. В случае XSLT контекстный узел устанавли­вают следующие конструкции:

?      сопоставление с шаблоном (<xsl:template match="x">…</ xsl:template>);

?      xsl:for-each;

?      xsl:apply-templates.

Свободное владение языком написания путевых выражений – необходимое условие для выполнения как простых, так и сложных преобразований. Опыт про­граммирования на традиционных языках часто служит причиной путаницы и ошибок при работе с XPath. Например, я часто ловил себя на том, что писал что- то типа<xsl:if test="preceding-sibling: :X[1]"> </xsl:if>, имея в виду <xsl:if test="preceding-sibling::*[1][self::X]"> </xsl:if>. Возможно, это объясняется тем, что последнее выражение – интуитивно менее понятный способ выразить такое условие: «если имя непосредственно предше­ствующего брата равно X».

Конечно, нет никакой возможности показать все полезные комбинации осей в путевых выражениях. Но, если вы поймете приведенные выше примеры, то смо­жете разобраться, что означает выражение preceding-sibling::X[1]/ descendant::Z[A/B] и еще более сложные.

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

По теме:

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