Применение осей¶
Задача¶
Требуется отобрать узлы XML-дерева с учетом сложных взаимосвязей в иерархической структуре.
Решение¶
Во всех приведенных ниже примерах используются оси. В каждой группе для демонстрации берется некий XML-документ, в котором контекстный узел выделен полужирным шрифтом. Поясняется, что является результатом вычисления пути, при этом показано, какие элементы отбираются относительно выделенного контекста. В некоторых случаях для иллюстрации тонкостей вычисления конкретного выражения рассматриваются и другие узлы, помимо контекстного.
Дочерняя ось и ось потомков¶
Дочерняя ось принимается в XPath по умолчанию. Иными словами, явно указывать ось child:: необязательно, но, если вы хотите быть педантом, то можете и указать. Спуститься по XML-дереву глубже, чем на один уровень, позволяют оси descendant:: и descendant-or-self::. Первая не включает сам контекстный узел, вторая – включает.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Отобрать все дочерние элементы с именем X: X, то же, что child::X
Результат:
1 | |
Отобрать первый дочерний элемент с именем X: X[1]
Результат:
1 | |
Отобрать последний дочерний элемент с именем X: X[last()]
Результат:
1 | |
Отобрать первый дочерний элемент при условии, что его имя X. Иначе пусто: *[1][self::X]
Результат:
1 | |
Отобрать последний дочерний элемент при условии, что его имя X. Иначе пусто: *[last()][self::X]
Результат: пусто
Отобрать последний дочерний элемент при условии, что его имя Y. Иначе пусто: *[last()][self::Y]
Результат:
1 | |
Отобрать всех потомков с именем X: descendant::X
Результат:
1 2 | |
Отобрать контекстный узел, если его имя X, а также всех потомков с именем X: descendant-or-self::X
Результат:
1 2 | |
Отобрать контекстный узел и всех его потомков: descendant-or-self::*
Результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Оси братьев¶
Оси братьев называются 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] отбирают первого предшествующего или последующего брата в порядке документа.
1 2 3 4 5 6 7 8 9 10 11 12 | |
Отобрать всех братьев с именем A, предшествующих контекстному узлу: preceding-sibling::A
Результат:
1 | |
Отобрать всех братьев с именем A, следующих за контекстным узлом: following-sibling::A
Результат:
1 | |
Отобрать всех братьев, предшествующих контекстному узлу: preceding-sibling::*
Результат:
1 2 | |
Отобрать первого предшествующего брата с именем A в обратном порядке документа: preceding-sibling::A[1]
Результат:
1 | |
Отобрать первого предшествующего брата в обратном порядке документа при условии, что его имя A: preceding-sibling::*[1][self::A]
Результат: пусто
Если бы контекстным был узел <A id="8"/>, то в результате мы получили бы <A id="7"/>
Отобрать всех предшествующих братьев, кроме элементов с именем A: preceding-sibling::*[not(self::A)]
Результат:
1 | |
В следующих примерах используется такой тестовый документ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Элемент, непосредственно предшествующий контекстному узлу при условии, что у того есть дочерний элемент с именем A: preceding-sibling::*[1][A]
Результат: пусто
Первый из предшествующих контекстному узлу элементов, у которого есть дочерний элемент с именем A: preceding-sibling::*[A][1]
Результат:
1 | |
XPath 2.0 позволяет более гибко выбирать элементы с учетом пространств имен. В следующих примерах используется такой XML-документ:
1 2 3 4 5 6 7 8 9 | |
Отобрать всех предшествующих братьев контекстного узла, для которых пространство имен ассоциировано с префиксом NS: preceding-sibling::NS:*
Результат:
1 | |
Отобрать всех предшествующих братьев контекстного узла, для которых локальное имя равно A: preceding-sibling::*:A
Результат:
1 | |
Родительская ось и ось предков¶
Родительская ось (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="precedingsibling::X[1]"> </xsl:if>, имея в виду <xsl:if test="precedingsibling::*[1][self::X]"> </xsl:if>. Возможно, это объясняется тем, что последнее выражение – интуитивно менее понятный способ выразить такое условие: «если имя непосредственно предшествующего брата равно X».
Конечно, нет никакой возможности показать все полезные комбинации осей в путевых выражениях. Но, если вы поймете приведенные выше примеры, то сможете разобраться, что означает выражение preceding-sibling::X[1]/descendant::Z[A/B] и еще более сложные.