Основная мощь языка XPath заключается в осях, позволяющих добраться до любого элемента в исходном документе. Рассмотрим применение таких редкоиспользуемых осей, как ancestor, descendant и self.
Задача: получить атрибут id элемента-«прадеда» foo.
Обычно в таких случаях начинают рисовать лестницы:
Такая запись плоха тем, что малопонятна без знания исходного xml. Автор рекомендует в подобных случаях использовать более информативное выражение:
Эта запись не только даёт представление об искомом элементе, но и продолжает работать даже в том случае, когда текущий элемент изменил своё положение в дереве.
Задача: найти первого потомка foo в текущем элементе.
Когда похожий вопрос был задан на сайте StackOverflow.com, два человека тут же ответили
Запись
Это различие описано в документации, но почему-то многие читают её невнимательно. Во избежание ошибок автор рекомендует всегда писать
Казалось бы, зачем нужна self, если есть более короткий вариант:
Задача: получить следующий элемент, если он называется foo.
Очевидное решение:
Более элегантное, на взгляд автора, выражение:
Комментарии и дополнения приветствуются.
ancestor
Задача: получить атрибут id элемента-«прадеда» foo.
Обычно в таких случаях начинают рисовать лестницы:
../../../@id
Такая запись плоха тем, что малопонятна без знания исходного xml. Автор рекомендует в подобных случаях использовать более информативное выражение:
ancestor::foo[1]/@id
Эта запись не только даёт представление об искомом элементе, но и продолжает работать даже в том случае, когда текущий элемент изменил своё положение в дереве.
descendant
Задача: найти первого потомка foo в текущем элементе.
Когда похожий вопрос был задан на сайте StackOverflow.com, два человека тут же ответили
.//foo[1]
и были поддержаны другими участниками. Автору пришлось вмешаться и предупредить о неправильности данного выражения. Правильный ответ: descendant::foo[1]
, и вот почему..//foo
является короткой формой следующего выражения:self::node()/descendant-or-self::node()/child::foo
Запись
.//foo[1]
означает все потомки foo, каждый из которых первый foo у своего родителя. Такое выражение вернёт два элемента в следующем документе:<list> <item><foo/></item> <item><foo/></item> </list>
descendant::foo[1]
вернёт ровно один элемент.Это различие описано в документации, но почему-то многие читают её невнимательно. Во избежание ошибок автор рекомендует всегда писать
descendant::foo
вместо .//foo
, поскольку в подавляющем большинстве случаев именно это и имеется в виду.self
Казалось бы, зачем нужна self, если есть более короткий вариант:
.
(точка). Однако и у этой оси нашлось своё применение.Задача: получить следующий элемент, если он называется foo.
Очевидное решение:
following-sibling::*[1][name() = 'foo']
Более элегантное, на взгляд автора, выражение:
following-sibling::*[1]/self::foo
Комментарии и дополнения приветствуются.