Не так страшен XPATH как его незнание. Выбор нескольких путей. Решение для общего случая

Не так страшен XPATH как его незнание. Выбор нескольких путей. Решение для общего случая

26.04.2019

XPath используется для навигации по элементам и атрибутам XML-документа. XPath является одним из основных элементов в стандарте XSLT консорциума W3C.

1 Что такое XPath

Выражения XPath

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

Стандартные функции XPath

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

XPath используется в XSLT

XPath является одним из основных элементов в стандарте XSLT. Без знания XPath вы не будете иметь возможность создавать XSLT-документы.

2 Терминология XPath

Узлы

В XPath существует семь видов узлов: элемент, атрибут, текст, пространство имён, инструкции обработки, комментарии и узлы документа. XML-документы обрабатываются в виде деревьев узлов. Верхний элемент дерева называется корневым элементом. Посмотрите на следующий документ XML:

Harry Potter J. K. Rowling 2005 29.99

Пример узлов в документе XML выше:

(корневой элемент) J. K. Rowling (узел) lang="en" (атрибут)

Атомарные значения

Атомарные значения являются узлами, не имеющие детей или родителей. Пример атомарных значений:

J. K. Rowling "en"

Элементы

Элементы - это атомарные значения или узлы.

3 Отношения узлов

Родитель

Каждый элемент и атрибут имеет одного родителя. В следующем примере элемент «книга» (book) является родителем элементов «название» (title), «автор» (author), «год» (year) и «цена» (price):

Harry Potter J K. Rowling 2005 29.99

Потомки

Узлы элементов могут иметь ноль, один или более потомков. В следующем примере элементы «название», «автор», «год» и «цена» - они все потомки элемента книга:

Harry Potter J K. Rowling 2005 29.99

Элементы одного уровня

Это узлы, которые имеют одного и того же родителя. В следующем примере элементы «название», «автор», «год» и «цена» все являются элементами одного уровня:

Harry Potter J K. Rowling 2005 29.99

Предки

Родитель узла, родитель родителя узла и т.д. В следующем примере предки элемента «название» (title) - это элементы «книга» (book) и «книжный магазин» (bookstore):

Harry Potter J K. Rowling 2005 29.99

Потомки

Дети узла, дети детей узла и т.д. В следующем примере потомками элемента «книжный магазин» являются элементы «книга», «название», «автор», «год» и «цена»:

Harry Potter J K. Rowling 2005 29.99

4 Синтаксис XPath

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

Harry Potter 29.99 Learning XML 39.95

Выбор узлов

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

В приведенной ниже таблице перечислены некоторые пути выражения и результат выполнения выражения:

Выражение XPath Результат
bookstore Выбирает все узлы с именем "bookstore"
/bookstore Выбирает корневой элемент книжного магазина

Примечание: Если путь начинается с косой черты (/), он всегда представляет собой абсолютный путь к элементу!

bookstore/book Выбирает все элементы «книга» (book), которые являются потомками элемента «книжный магазин» (bookstore)
//book Выбирает все элементы «книга» независимо от того, где они находятся в документе
bookstore//book Выбирает все элементы «книга», которые являются потомком элемента «книжный магазин», независимо от того, где они находятся под элементом «книжный магазин»
//@lang Выбирает все атрибуты, которые называются "lang"

Предикаты

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

Выражения XPath Результат
/bookstore/book Выбирает первый элемент «книга», который является потомком элемента «книжный магазин».

Примечание: В IE 5,6,7,8,9 первый узел имеет индекс , но в соответствии с рекомендациями W3C, это . Для решения этой проблемы в IE, задаётся опция "SelectionLanguage" для XPath:

На JavaScript: xml .setProperty("SelectionLanguage", "XPath");
/bookstore/book Выбирает последний элемент «книга» (book), который является дочерним элементом элемента «книжный магазин» (bookstore)
/bookstore/book Выбирает предпоследний элемент «книга», который является дочерним элементом элемента «книжный магазин»
/bookstore/book Выбор первых двух элементов «книга», которые являются потомками элемента «книжный магазин»
//title[@lang] Выбирает все элементы «название» (title), которые имеют атрибут с именем "lang"
//title[@lang="en"] Выбирает все элементы «название», которые имеют атрибут «язык» со значением "en"
/bookstore/book Выбирает все элементы «книга» после элемента «книжный магазин», которые имеют элемент «цена» со значением больше, чем 35.00
/bookstore/book/title Выбирает все элементы «название» книги элемента «книжный магазин», которые имеют элемент «цена» со значением больше, чем 35.00

Выбор неизвестных узлов

Специальные символы XPath могут использоваться для выбора неизвестных XML узлов.

В приведённой ниже таблице мы перечислили некоторые пути выражения и результаты выражений:

Выбор нескольких путей

С помощью оператора | в выражениях XPath вы можете выбрать несколько путей. В таблице ниже перечислены несколько выражений путей и результаты их применения:

5 Оси XPath

Мы будем использовать следующий XML документ далее в примере.

Harry Potter 29.99 Learning XML 39.95

Оси определяют наборы узлов, относительно текущего узла.

Название оси Результат
ancestor Выбирает всех предков (родителей, прародителей и т.д.) текущего узла
ancestor-or-self Выбирает всех предков (родителей, прародителей и т.д.) текущего узла и сам текущий узел
attribute
child
descendant Выбирает всех потомков (детей, внуков и т.д.) текущего узла
descendant-or-self Выбирает всех потомков (детей, внуков и т.д.) текущего узла и сам текущий узел
following Выбирает всё в документе после закрытия тэга текущего узла
following-sibling Выбирает все узлы одного уровня после текущего узла
namespace Выбирает все узлы в данном пространстве имён (namespace) текущего узла
parent Выбирает родителя текущего узла
preceding Выбирает все узлы, которые появляются перед текущим узлом в документе, за исключением предков, узлов атрибутов и узлы пространства имён
preceding-sibling Выбирает всех братьев и сестёр до текущего узла
self Выбирает текущий узел

6 Выражения пути выборки

Путь определения местоположения может быть абсолютным или относительным. Абсолютный путь расположения начинается с косой черты (/), а относительный - нет. В обоих случаях путь выборки состоит из одного или нескольких шагов, разделённых косой чертой:

Абсолютный путь расположения:

/step/step/...

Относительный путь выборки расположения:

Step/step/...

Каждый шаг оценивается по узлам в текущем наборе узлов. Шаг состоит из:

  • ось (определяет древовидную связь между выбранными узлами и текущим узлом);
  • проверка узла (идентифицирует узел в пределах оси);
  • ноль или более предикатов (для дальнейшего уточнения выбранного набор узлов)

Синтаксис шага выборки такой:

Axisname::nodetest имяОси::проверкаУзла[предиктор]

Пример Результат
child::book Выбирает все узлы «книга» (book), которые являются потомками текущего узла
attribute::lang Выбирает атрибут «язык» (lang) текущего узла
child::* Выбирает всех потомков текущего узла
attribute::* Выбирает все атрибуты текущего узла
child::text() Выбирает все текстовые узлы текущего узла
child::node() Выбирает всех ближайших потомков текущего узла
descendant::book Выбирает всех потомков текущего узла
ancestor::book Выбирает всех предков «книга» (books) текущего узла
ancestor-or-self::book Выбирает всех предков «книга» (book) текущего узла - и текущий узел, если он также «книга» (book)
child::*/child::price Выбирает все потомки «цена» (price) через один уровень от текущего узла

7 Операторы XPath

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

Оператор Описание Пример
| Вычисляет два набора узлов //book | //cd
+ Сложение 6 + 4
- Вычитание 6 - 4
* Умножение 6 * 4
div Деление 8 div 4
= Равенство price=9.80
!= Неравенство price!=9.80
< Меньше, чем price<9.80
<= Меньше или равно price≤9.80
> Больше, чем price>9.80
>= Больше или равно price≤9.80
or Или price=9.80 or price=9.70
and И price>9.00 and price<9.90
mod Остаток от деления 5 mod 2

8 Примеры XPath

Давайте рассмотрим базовый синтаксис XPath на нескольких примерах. Мы будем использовать следующий XML документ "books.xml" в примерах ниже:

Everyday Italian Giada De Laurentiis 2005 30.00 Harry Potter J K. Rowling 2005 29.99 XQuery Kick Start James McGovern Per Bothner Kurt Cagle James Linn Vaidyanathan Nagarajan 2003 49.99 Learning XML Erik T. Ray 2003 39.95

Загрузка XML документа

Используйте XMLHttpRequest для загрузки XML документов, который поддерживается большинством современных браузеров:

Var xmlhttp=new XMLHttpRequest()

Код для устаревших браузеров Microsoft (IE 5 и 6):

Var xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")

Выбор узлов

К сожалению, работа с XPath в Internet Explorer и в других браузерах может отличаться. В наших примерах мы будем использовать код, который должен работать в большинстве браузеров. Internet Explorer использует метод "selectNodes()" для выбора узлов XML документа:

XmlDoc.selectNodes(xpath);

Firefox, Chrome, Opera и Safari используют метод evaluate() для выбора узлов из XML документа:

XmlDoc.evaluate(xpath, xmlDoc, null, XPathResult.ANY_TYPE, null);

Выбор всех заглавий

Следующий пример выбирает все узлы заголовков:

/bookstore/book/title

Выбор заголовка первой книги

Следующий пример выбирает заголовок первого узла «книга» после элемента «книжный магазин» (bookstore):

/bookstore/book/title

Выбор всех цен

Следующий пример выбирает текст всех узлов «цена» (price):

/bookstore/book/price

Выбирает узлы с ценой >35

Следующий пример выбирает все узлы с ценами выше 35:

/bookstore/book/price

Выбор узлов заголовков с ценой >35

Следующий пример выбирает все узлы заголовков с ценой выше 35:

/bookstore/book/title

Стандартное решение через xsl:choose и его недостатки

Подобные задачи в XSLT традиционно решают так:

< xsl : choose > < xsl : when test = " $cond " > < xsl : copy-of select = " $one " /> < xsl : otherwise > < xsl : copy-of select = " $two " />

Однако это решение, помимо очевидной громоздкости, обладает еще одним, весьма существенным недостатком: то, что получается в результате — RTF. Т.е. результат такого выражения приемлем в выходном потоке, но никак не может быть использован (по крайней мере в рамках "чистого" XSLT 1.0, без расширений) для последующих преобразований.

Конечно, с помощью exsl:node-set (или ее аналога), мы можем превратить этот RTF в полноценное множество узлов, но останется одна проблема: это будет новое множество узлов, никак не связанное с исходными документами. Узел, полученый в результате преобразования, будет его корневым узлом — применить к нему, к примеру, ось ancestor мы уже не сможем.

Попробуем преодолеть этот недостаток.

Решение на чистом XPath 1.0

Решение

Воспользуемся тем фактом, что при задании осей выборки мы можем использовать предикаты , налагающие на них ограничивающие условия: $one[$cond] | $two .

Его недостатки

Это выражение лишено недостатков "классического" варианта, но у него есть свои, «дополняющие» их:

  1. Если xsl:choose нельзя (кросс-процессорным способом) использовать для получения node-set"ов, то этот вариант, напротив, может работать только с node-sets в качестве как параметров, так и результата.

Т.е. приведенный пример будет работать только если $one и $two — множества узлов. Запихнуть в это выражение строку или число уже не получится.

  1. Если в примере вместо переменной $cond использовать некоторое сложное выражение, результат его вычисления может зависеть от содержимого $one и $two .

Например $one | $two — значение count(.) будет разным в обоих случаях, и совсем не тем, которое, очевидно, имел бы ввиду гипотетический горе-автор такого выражения...

  1. Исходное выражение достаточно сложно для понимания. Когда я привел пример его употребления в форуме, мне приходилось слышать замечания, что «вообще-то | в xslt — это не or , а объединение нодесетов...»

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

  1. При обработке этого выражения вычисляются обе ветки и лишь затем проверяется их соответствие условию. Хотя это почти никогда не вызывает проблем (выражения XSLT не должны иметь побочных действий) — иногда это все же имеет значение.

Утверждение «никакие функции в XSLT не имеют побочных эффектов» неверно . Помимо функций расширений (а наличие у них побочных эффектов вполне возможно), есть по крайней мере одна такая функция, входящая в спецификацию XSLT 1.0. Это функция document : действительно, вряд ли кто-нибудь станет утверждать, что вызов document("http://example.com/cgi-bin/counter.pl") побочных эффектов иметь не будет...

Заметим сразу, что преодолеть эти недостатки этого выражения в рамках «чистого» XSLT 1.0 (т.е. без расширений) нельзя. В этом случае остается лишь предложить использовать этот вариант, если нам нужен на выходе node-set, и "классический" — во всех остальных случаях.

Использование расширений EXSLT

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

Для создания функций расширений EXSLT предоставляет элементы func:function и func:result , где префикс func: соответствует xmlns http://exslt.org/functions .
Первый из них практически точно соответствует элементу XSLT 2.0 xsl:function . Аналога же result там нету, для создания возвращаемых значений используется стандартный синтаксис коструктора последовательности .

Решение для общего случая

Итак, определим собственную функцию расширения (lib: — префикс нашего собственного пространства имен, допустим — urn:xslt:library):

< func : function name = " lib:if" > < xsl : choose > < xsl : when test = " $cond " > < func : result select = " $then" /> < xsl : otherwise > < func : result select = " $else" />

Наш пример с ее помощью запишется так: lib:if($cond, $one, $two) .

Если есть побочные эффекты

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

В большинстве случаев этим можно пренебречь. Но если наши аргументы имеют побочные эффекты — это недопустимо. Потому воспользуемся для решения этой задачи еще одной функцией EXSLT — dyn:evaluate , где xmlns:dyn = "http://exslt.org/dynamic" . Она, как нетрудно догадаться, получает в качестве аргумента строку и вычисляет ее как выражение XPath непосредственно в том контексте , в котором вызвана:

< func : function name = " lib:if-dyn" > < xsl : choose > < xsl : when test = " $cond " > < func : result select = " dyn:evaluate($then)" /> < xsl : otherwise > < func : result select = " dyn:evaluate($else)" />

Таким образом, любое выражение, которое мы передадим этой функции — в виде строки, не следует забывать об этом — будет вычислено лишь в том случае, если оно действительно используется. Но следует отметить, что lib:if , вообще говоря, должна выполняться быстрее (и не требовать еще одного модуля расширений) — поэтому, если нам не нужна эта дополительная функциональность, всегда следует использовать lib:if .

Само собой разумеется и во всех книгах проговаривают, что для нахождения элемента лучше всего и быстрее использовать локаторы id и name и, что характерно, основные примеры по использованию локаторов и по работе Selenium показывают именно с ними. Но в реальной жизни часто бывает так, что id элементов формируется динамически, а потому все привязки к нему бесполезны, class может иметь десятки представителей на странице, а name может отсутствовать. Как вы уже догадываетесь в этом случае нужно применять локаторы xpath и css. В данной статье я не собираюсь говорить о каком то превосходстве над css или сравнивать быстродействие, я лишь расскажу почему я использую именно xpath и как это нужно делать. Букв будет много, так как в свое время мне пришлось достаточно порыться в интернет, чтобы получить нужную мне информацию, я выкладываю все самое полезное, в надежде, что кому это поможет в использовании xpath-локаторов. Важно, что у тебя, мой читатель должно быть хоть небольшой представление о xpath, если его нет, то можешь .

Сначала о том, почему новички (и не только) не любят xpath:

  1. Со времен далекой, далекой Галактики, существует миф о том, что xpath во много раз медленнее css, что на данный момент времени не является правдой. Не знаю как обстояло дело раньше, но в наши дни я лично написал несколько тестов с использованием xpath и css и сравнивая их могу сказать, что никакого значительного преимущества нет, а порой даже xpath работает быстрее. Не собираюсь вступать в длительные баталии по поводу скорости, просто разница в несколько миллисекунд нельзя считать значительной, особенно при общей длительности УИ-тестов.
  2. Xpath неверно используют, во многом из-за того, что стандартные панели разработчика и плагины выдергивают xpath из страницы в совершенно непотребном виде, который неудобен и нечитаем. Потому у многих сложилось мнение, что xpath это тяжеловесная и непонятная ерунда.
  3. Нет или по меньшей мере мне не попался какой-нибудь вменяемый мануал по xpath, в основном предлагают ссылки на pdf файл где локаторы приведены всей кучей вместе с css, этакая выжимка, в которой я уверен мало кто разбирается просто из-за обилия информации.

А теперь о том, как обстоят дела на самом деле и в чем преимущества xpath, если его правильно использовать:

— он не уступает (или незначительно уступает) в скорости css

— он понятен и легко читаем, по нему можно понять о каком элементе идет речь

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

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

Итак, несколько правил использования xpath :

  1. Никогда не используй плагины или копирование xpath из кода страницы средствами браузера или веб-разработчика. Вот например как показывает одну ссылку плагин к Файрфокс: //header/div/ul/li/a . Разве из этой ссылки понятно, о каком элементе речь, что мы ищем? Ведь порой бывает, что взглянув на локатор в коде или в тексте исключения мы должны понять о каком элементе речь. Как это можно понять из такой строки? Я уже не говорю о том, что любой код, основанный на таких локаторах упадет при любом дуновении ветерка. Каждый раз, когда ты пишешь локатор подобный // div / div / ul / li (продолжать можно долго) в мире умирает что-то хорошее!!! Это, если хотите, говнокод тестировщика, который нужно выжигать каленым железом.
  2. Старайся написать xpath как можно короче и понятнее, используй его возможности и схожесть с языком программирования, чтобы и через месяц ты сам мог понять о каком элементе речь и что нужно поправить в случае изменения верстки
  3. Xpath’у время и место! Если есть возможность использовать id, name или внести в код id то сделай это!
  4. Вместо длинной цепочки слешей, как указано выше, используй отношения элементов: предок, потомок, сестринский элемент
  5. Можно и нужно использовать логические операции and, not , or
  6. Нормальный xpath всегда начинается с // и не использует фильтры с номером элемента в стиле (например // div )

Переходим к делу и практике, тот xpath, что указан выше (//header/div/ul/li/a ) на самом деле можно указать в виде //a . Согласись, что есть разница и в длине текста и в понимании его, ведь тут видно по тегу, что это ссылка и ее текст –Pricing. То есть ты можешь и сам найти этот элемент на странице визуально и в случае исключения с таким локатором сразу знаешь, что и где искать!

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

Как видим id явно сгенерирован и привязаться к нему нельзя, класс тоже не внушает доверия, кроме того Selenium не разрешает использовать сложносоставные имена в локаторе className, но тут есть текст, который решает проблему: // a

  • contains(параметр, искомое ) –возвращает элемент если он содержит искомое, знакомая команда не так ли? Ты ее видишь постоянно в языке программирования. Очень удобно использовать в связке с text() если составляем сложный локатор и не знаем точно всего текста, например: // div[@ class=’ buttons’ and contains(text(),’ Save’)] – как видишь, это некоторый элемент, который относится к кнопкам и на нем есть текст Save. Представь, что в твоем тестируемом веб-приложении есть несколько страниц, на которых есть кнопка сохранения, но с разными текстами –сохранить файл, сохранить диаграмму, сохранить отчет и так далее. Тебе не придется писать локаторы для них всех, хватит одного для всех кнопок сохранения и он описан выше. Обрати внимание на использовании в фильтре сразу двух условий!

Кроме того, очень полезная возможность – это искать элемент по одному из слов в названии класса.Пример:

Все решается вот так: //div , то есть мы ищем элемент, у которого в классе есть какое-то уникальное сочетание слов. Данная возможность contains очень помогает в самых разных ситуациях! Обрати внимание, что параметр и искомое идут через запятую, нельзя писать contains(text()=’smth’)

  • starts- with(параметр, искомое) –все аналогично contains, только эта команда возвращает элементы начинающиеся с искомого. Если возвращаться к примеру с кнопками сохранения, то локатор можно переписать вот так // div[@ class=’ buttons’ and starts- with(text(),’ Save’)] у нас ничего не упадет, так как слово save обычно первое на кнопке и значит локатор опять же будет работать для всех кнопок сохранения. В общем это более узкая форма contains

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

Формат использования //начальный элемент/отношение::тег(фильтр) конечного элемента. Обрати внимание на два двоеточия после отношения и не забывай после двоеточий указать тег, а лучше и фильтр искомого элемента, так как потомков может быть и много, а нам нужен какой-то конкретный.

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

// div[ text()=’Тема’]/ preceding- sibling:: input — мы сначала находим уникальный элемент с текстом, а потом от него ищем предшествующий сестринский элемент, делая фильтр-уточнение, что ищем именно input. Еще пример:

Нам нужно кликнуть кнопку, на которой нет текста, только иконка, но как видишь у нее все те же проблемы с id плюс есть куча одноименных классов. Нас спасает то, что у предшествующего элемента есть уникальное название класса, вот от него и будем плясать: //div/following-sibling::div – находим элемент у которого есть уникальное слово в названии класса и от него уже ищем следующий сестринский элемент, с тегом div. Учитывай, что даже если сестринских последующих элементов с тегом div будет много вернется только самый первый!

То представим, что нам нужен непосредственно элемент с id=__vz4019, для всех на данной картинке он является родителем (parent) и поэтому его можно вытянуть через любой из них, например // div[ text()=’Тема’]/ parent:: div

Кстати, обращение к родительскому элементу, можно заменить двумя точками и без тега, вот так // div[ text()=’Тема’]/ ..

Так как все элементы в примере — дети, то можно любого из них найти от родителя вот так:

//div/child::input – находим родителя, а от него ищем ребенка с тегом input.


Нам нужна папка именно с определенным именем, но верстка организована так, что сам текст не содержится именно в элементе класса папка, поэтому нам надо найти сначала класс, а потом отфильтровать ту, у которой в потомках есть нужный текст:

// div[@ class=’ listitem Folder’]/ descendant:: span[ text()=’ Folder name’] – сначала находим класс папки, потом среди его потомков ищем тег span и нужный нам текст. Вы можете спросить –а почему просто по тексту не искать? Дело в том, что элементов с таким текстом на странице может быть больше одного, а нам нужна именно папка.

Кстати вместо descendant можно использовать двойной слеш // это означает -любой вложенный элемент. Пример выше превращается в

// div[@ class=’ listitem Folder’]/ / span[ text()=’ Folder name’]

  • ancestor – предок, опять же отличающийся от parent тем, что может быть любой удаленности, то есть прадедушкой. Если возвращаться к предыдущему примеру, то найти элемент папки по тексту можно так // span[ text()=’ Folder name’]/ ancestor:: div[@ class=’ listitem Folder’]

Важно понимать, что можно, но крайне нежелательно использовать в одном локаторе несколько отношений, например:

// div[@ class=’ One]/ child :: div[@ class=’ Two’]/ descendant :: input[@ class=’ Three] . Такой локатор работать будет, но он уже сложно читается и скорее всего есть возможность подобрать другой, не нужно использовать такое без необходимости, помним правило номер 2. Совсем недопустимо использовать в одном локаторе обратные отношения то есть сначала искать потомка, потом его предка или наоборот.

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

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

Примеры использования xpath из практики парсинга информации с сайтов. Приведены участки кода xpath.

Получить текст заголовока h1

//h1/text()

Получить текст заголовока с классом produnctName

//h1[@class="produnctName"]/text()

Получить значение определенного span по классу

//span[@class="price"]

Получить значение атрибута title у кнопки с классом addtocart_button

//input[@class="addtocart_button"]/@title

//a/text()

//a/@href

Изображение src

//img/@src

Изображение сразу за определенным элементом в DOM, ось following

//h1[@class="produnctName"]//following::div/img/@src

Изображение в 4 div по счету

//div/img/@src

XPath (XML Path Language) — язык запросов к элементам XML-документа. Разработан для организации доступа к частям документа XML в файлах трансформации XSLT и является стандартом консорциума W3C. XPath призван реализовать навигацию по DOM в XML.

XML имеет древовидную структуру. У элемента дерева всегда существуют потомки и предки, кроме корневого элемента, у которого предков нет, а также тупиковых элементов (листьев дерева), у которых нет потомков.

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

Функции над множествами узлов

  • * — обозначает любое имя или набор символов по указанной оси, например: * — любой дочерний узел; @* — любой атрибут.
  • $name — обращение к переменной, где name — имя переменной или параметра.
  • дополнительные условия выборки или, что то же самое, предикат шага адресации. Должен содержать логическое значение. Если содержит числовое, считается что это порядковый номер узла, что эквивалентно приписыванию перед этим числом выражения «position()=»
  • {} — если применяется внутри тега другого языка (например HTML), то XSLT процессор рассматривает содержимое фигурных скобок как XPath.
  • / — определяет уровень дерева, то есть разделяет шаги адресации
  • | — объединяет результат. То есть, можно написать несколько путей разбора через знак | и в результат такого выражения войдёт всё, что будет найдено любым из этих путей.
  • node-set node ()

Возвращает все узлы. Вместо этой функции часто используют заменитель "*", но, в отличие от звездочки, функция node() возвращает и текстовые узлы.

  • string text ()

Возвращает набор текстовых узлов;

  • node-set current ()

Возвращает множество из одного элемента, который является текущим. Если мы делаем обработку множества с условиями, то единственным способом дотянуться из этого условия до текущего элемента будет данная функция.

  • number position ()

Возвращает позицию элемента в множестве. Корректно работает только в цикле

  • number last ()

Возвращает номер последнего элемента в множестве. Корректно работает только в цикле

  • number count (node-set)

Возвращает количество элементов в node-set.

  • string name (node-set?)

Возвращает полное имя первого тега в множестве.

  • string namespace-uri (node-set?)
  • string local-name (node-set?)

Возвращает имя первого тега в множестве, без пространства имён.

  • node-set id (object)

Находит элемент с уникальным идентификатором

Оси — это база языка XPath. Для некоторых осей существуют сокращённые обозначения.

  • ancestor:: — Возвращает множество предков.
  • ancestor-or-self:: — Возвращает множество предков и текущий элемент.
  • attribute:: — Возвращает множество атрибутов текущего элемента. Это обращение можно заменить на «@»
  • child:: — Возвращает множество потомков на один уровень ниже. Это название сокращается полностью, то есть его можно вовсе опускать.
  • descendant:: — Возвращает полное множество потомков (то есть, как ближайших потомков, так и всех их потомков).
  • descendant-or-self:: — Возвращает полное множество потомков и текущий элемент. Выражение «/descendant-or-self::node()/» можно сокращать до «//» . С помощью этой оси, например, можно вторым шагом организовать отбор элементов с любого узла, а не только с корневого: достаточно первым шагом взять всех потомков корневого. Например, путь «//span» отберёт все узлы span документа, независимо от их положения в иерархии, взглянув как на имя корневого, так и на имена всех его дочерних элементов, на всю глубину их вложенности.
  • following:: — Возвращает необработанное множество, ниже текущего элемента.
  • following-sibling:: — Возвращает множество элементов на том же уровне, следующих за текущим.
  • namespace:: — Возвращает множество, имеющее пространство имён (то есть присутствует атрибут xmlns).
  • parent:: — Возвращает предка на один уровень назад. Это обращение можно заменить на «..»
  • preceding:: — Возвращает множество обработанных элементов исключая множество предков.
  • preceding-sibling:: — Возвращает множество элементов на том же уровне, предшествующих текущему.
  • self:: — Возвращает текущий элемент. Это обращение можно заменить на «.»

Основы

XML имеет древовидную структуру. В документе всегда имеется корневой элемент (инструкция к дереву отношения не имеет). У элемента дерева всегда существуют потомки и предки, кроме корневого элемента, у которого предков нет, а также тупиковых элементов (листьев дерева), у которых нет потомков. Каждый элемент дерева находится на определенном уровне вложенности (далее - «уровень»). У элементов на одном уровне бывают предыдущие и следующие элементы.

Это очень похоже на организацию каталогов в файловой системе, и строки XPath, фактически, - пути к «файлам» - элементам.

Например, рассмотрим XHTML документ:

<html > <body > <div > Первый слой <span > блок текста в первом слое</ span > </ div > <div > Второй слой</ div > <div > Третий слой <span class = "text" > первый блок в третьем слое</ span > <span class = "text" > второй блок в третьем слое</ span > <span > третий блок в третьем слое</ span > </ div > <img / > </ body > </ html >

XPath-путь /html/body/*/span[@class] (полный синтаксис имеет вид /child::html/child::body/child::*/child::span ) будет соответствовать в нём двум элементам исходного документа - первый блок в третьем слое и второй блок в третьем слое .

Путь делится на шаги адресации, которые разделяются символом «косая черта» / . Каждый шаг адресации состоит из трех частей:

  • ось (в данном примере child::), это обязательная часть;
  • условие проверки узлов (в данном примере это имена элементов документа html, body, span, а символ * означает элемент с любым именем), также обязательная часть;
  • предикат (в данном примере attribute::class), необязательная часть, заключаемая в квадратные скобки, в которой могут содержаться оси, условия проверки, функции, операторы (+, -, <, > и пр.).

Анализ ведется слева направо. Если первый символ это / , то путь адресации считается абсолютным (то есть от корня документа). При этом за узел контекста на первом шаге берется корневой элемент (html). Контекст - это некая точка отсчета, относительно которой рассчитывается следующий шаг адресации. Поэтому на каждом шаге адресации мы получаем новый набор узлов документа, и этот набор становится контекстом для следующего шага адресации.

На втором шаге адресации (child::body) контекстом становится html элемент. Ось child:: говорит о том, что необходимо найти все непосредственные потомки элемента html, а условие проверки body говорит о том, что в формируемый набор элементов нужно включить все узлы с именем body. В ходе второго шага адресации получаем набор узлов, состоящий всего из одного элемента body, который и становится элементом контекста для третьего шага.

Третий шаг адресации: child::* . Ось child:: собирает все непосредственные потомки элемента body, а условие проверки * говорит о том, что в формируемый набор нужно включить элементы основного типа с любым именем. В ходе этого шага получаем набор узлов, состоящий из трех элементов div и одного элемента img.

Четвёртый шаг адресации: child::span . Теперь контекстом является набор из четырёх элементов. И следующий набор узлов создается в четыре прохода (за четыре итерации). При первой итерации узлом контекста становится первый div. Согласно заданной оси child:: и правилу проверки span, в набор включаются непосредственные потомки div-а, имя которых равно span. При второй итерации в набор ничего добавлено не будет, так как у второго div нет потомков. Третья итерация добавит в набор сразу три элемента span, а четвёртая ничего не добавит, так как у элемента img нет потомков. Итак, в ходе проверки получен набор узлов, состоящий из четырёх элементов span. Это и будет контекстом для последующей обработки.

Следующего шага нет, поэтому будет производиться фильтрация отобранного набора. В этом и состоит отличие предикатов от шагов адресации. На каждом шаге адресации получаем новый набор, отталкиваясь от контекста, полученного на предыдущем шаге. В ходе же обработки предиката новый набор получается из текущего методом фильтрации, когда из набора исключаются узлы, не прошедшие условие проверки. В данном случае ось attribute:: говорит о необходимости проверить, если ли у узлов контекста атрибуты, а условие class требует оставить лишь те узлы, у которых задан атрибут с именем class. Фильтрация происходит за четыре итерации, но в окончательный набор попадают только два элемента span.

Оси

Оси - это база языка XPath.

  • ancestor:: - Возвращает множество предков.
  • ancestor-or-self:: - Возвращает множество предков и текущий элемент.
  • attribute:: - Возвращает множество атрибутов текущего элемента.
  • child:: - Возвращает множество потомков на один уровень ниже.
  • descendant:: - Возвращает полное множество потомков.
  • descendant-or-self:: - Возвращает полное множество потомков и текущий элемент.
  • following:: - Возвращает необработанное множество, ниже текущего элемента.
  • following-sibling:: - Возвращает множество элементов на том же уровне, следующих за текущим.
  • namespace:: - Возвращает множество, имеющее пространство имён (то есть присутствует атрибут xmlns).
  • parent:: - Возвращает предка на один уровень назад.
  • preceding:: - Возвращает множество обработанных элементов исключая множество предков.
  • preceding-sibling:: - Возвращает множество элементов на том же уровне, предшествующих текущему.
  • self:: - Возвращает текущий элемент.

Существуют сокращения для некоторых осей, например:

  • attribute:: - можно заменить на «@»
  • child:: - часто просто опускают
  • descendant:: - можно заменить на «.//»
  • parent:: - можно заменить на «..»
  • self:: - можно заменить на «.»

Дополнением к базе является набор функций, которые делятся на 5 групп:

Системные функции

node-set document (object, node-set?) Возвращает документ, указанный в параметре object. string format-number (number, string, string?) Форматирует число согласно образцу, указанному во втором параметре, третий параметр указывает именованный формат числа, который должен быть учтён. string generate-id (node-set?) Возвращает строку, являющуюся уникальным идентификатором. node-set key (string, object) Возвращает множество с указанным ключом (аналогично функции id для идентификаторов). string unparsed-entity-uri (string) Возвращает непроанализированный URI, если такового нет, возвращает пустую строку. boolean element-available (string) Проверяет, доступен ли элемент или множество, указанное в параметре. Параметр рассматривается как XPath. boolean function-available (string) Проверяет, доступна ли функция, указанная в параметре. Параметр рассматривается как XPath. object system-property (string) Параметры, возвращающие системные переменные, могут быть: * xsl: version - возвращает версию XSLT процессора. * xsl: vendor - возвращает производителя XSLT процессора. * xsl: vendor-url - возвращает URL, идентифицирующий производителя. Если используется неизвестный параметр, функция возвращает пустую строку. boolean lang (string) Возвращает истину, если у текущего тега имеется атрибут xml: lang, либо родитель тега имеет атрибут xml: lang и в нем указан совпадающий строке символ.

Функции с множествами

  • * - обозначает любое имя или набор символов, @* - любой атрибут
  • $name - обращение к переменной, где name - имя переменной или параметра.
  • - дополнительные условия выборки
  • {} - если применяется внутри тега другого языка (например HTML), то XSLT процессор рассматривает содержимое фигурных скобок как XPath.
  • / - определяет уровень дерева
node-set node () Возвращает все узлы. Для этой функции часто используют заменитель "*", но в отличие от звездочки - node() возвращает и текстовые узлы. string text () Возвращает набор текстовых узлов; node-set current () Возвращает множество из одного элемента, который является текущим. Если мы делаем обработку множества с условиями, то единственным способом дотянуться из этого условия до текущего элемента будет данная функция. number position () Возвращает позицию элемента в множестве. Корректно работает только в цикле number last () Возвращает номер последнего элемента в множестве. Корректно работает только в цикле number count (node-set) Возвращает количество элементов в node-set. string name (node-set?) Возвращает полное имя первого тега в множестве. string namespace-uri (node-set?) Возвращает ссылку на url определяющий пространство имён. string local-name (node-set?) Возвращает имя первого тега в множестве, без пространства имён. node-set id (object) Находит элемент с уникальным идентификатором

Строковые функции

string string (object?) Возвращает текстовое содержимое элемента. По сути возвращает объединенное множество текстовых элементов на один уровень ниже. string concat (string, string, string*) Объединяет две или более строк number string-length (string?) Возвращает длину строки. boolean contains (string, string) Возвращает истину, если первая строка содержит вторую, иначе возвращает ложь. string substring (string, number, number?) Возвращает строку вырезанную из строки начиная с указанного номера, и если указан второй номер - количество символов. string substring-before (string, string) Если найдена вторая строка в первой, возвращает строку до первого вхождения второй строки. string substring-after (string, string) Если найдена вторая строка в первой, возвращает строку после первого вхождения второй строки. boolean starts-with (string, string) Возвращает истину если вторая строка входит в начало первой, иначе возвращает ложь. boolean ends-with (string, string) Возвращает истину если вторая строка входит в конец первой, иначе возвращает ложь. string normalize-space (string?) Убирает лишние и повторные пробелы, а также управляющие символы, заменяя их пробелами. string translate (string, string, string) Заменяет символы первой строки, которые встречаются во второй строке, на соответствующие позиции символам из второй строки символы из третьей строки. translate(«bar», «abc», «ABC») вернет BAr.

Логические функции

  • or - логическое «или»
  • and - логическое «и»
  • = - логическое «равно»
  • < (<) - логическое «меньше»
  • > (>) - логическое «больше»
  • <= (<=) - логическое «меньше либо равно»
  • >= (>=) - логическое «больше либо равно»
boolean boolean (object) Приводит объект к логическому типу; boolean true () Возвращает истину. boolean false () Возвращает ложь. boolean not (boolean) Отрицание, возвращает истину если аргумент ложь и наоборот.

Числовые функции

  • + - сложение
  • − - вычитание
  • * - умножение
  • div - обычное деление (не деление нацело!)
  • mod - остаток от деления
number number (object?) Переводит объект в число. number sum (node-set) Вернёт сумму множества, каждый тег множества будет преобразован в строку и из него получено число. number floor (number) Возвращает наибольшее целое число, не большее, чем аргумент. number ceiling (number) Возвращает наименьшее целое число, не меньшее, чем аргумент. number round (number) Округляет число по математическим правилам.

Ссылки

  • XPath 1.0. Рекомендация W3C. (англ.)
  • XPath 2.0. Рекомендация W3C. (англ.)
  • XPath 2.1. Рекомендация W3C. (англ.)
  • XPath учебник (нем.)


© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows