xpath()
SimpleXMLElement::xpath() выполняет XPath-запрос к XML-документу, загруженному расширением SimpleXML PHP.
Введение
SimpleXMLElement::xpath() выполняет XPath-запрос к XML-документу, загруженному расширением SimpleXML в PHP, и возвращает совпадающие узлы. Без него можно обходить XML-дерево только по свойствам ($xml->book->title); с его помощью можно перейти сразу к любому узлу — на любой глубине — используя единственное путевое выражение, например //book/title.
На этой странице описано, что возвращает xpath(), наиболее часто используемый синтаксис XPath, как читать атрибуты и работать с пространствами имён, а также типичные подводные камни.
Синтаксис
public SimpleXMLElement::xpath(string $expression): array|false$expression— XPath-выражение для вычисления, относительное к узлу, на котором оно вызывается.- Возвращает — массив объектов
SimpleXMLElementдля каждого совпадающего узла, пустой массив, если ничего не найдено, илиfalseпри некорректном выражении.
Поскольку результат всегда является массивом, его обычно перебирают с помощью foreach, даже если ожидается единственное совпадение.
Первый пример
В приведённых ниже примерах используется simplexml_load_string(), чтобы их можно было запустить без внешнего файла:
<?php
$data = <<<XML
<library>
<book genre="fiction">
<title>The Pragmatic Programmer</title>
<author>Hunt</author>
</book>
<book genre="reference">
<title>PHP Cookbook</title>
<author>Sklar</author>
</book>
</library>
XML;
$xml = simplexml_load_string($data);
// Select every <title> anywhere under the root.
foreach ($xml->xpath('//title') as $title) {
echo $title . "\n";
}Вывод:
The Pragmatic Programmer
PHP Cookbook//title означает «любой элемент title на любой глубине». Цикл выводит каждый результат; приведение SimpleXMLElement к строке (выполняемое неявно через echo) возвращает текстовое содержимое элемента.
Распространённые XPath-выражения
| Выражение | Выбирает |
|---|---|
/library/book | элементы book, являющиеся прямыми дочерними элементами корневого library |
//book | каждый элемент book на любой глубине |
//book/title | дочерний элемент title каждого book |
//book[1] | первый book (индексация в XPath начинается с 1, а не с 0) |
//book[@genre='fiction'] | книги, у которых атрибут genre равен fiction |
//book[author='Sklar'] | книги с дочерним элементом <author> со значением Sklar |
//@genre | каждый узел-атрибут genre |
Фильтрация с помощью предиката
Предикат в квадратных скобках оставляет только узлы, соответствующие условию:
<?php
$data = <<<XML
<library>
<book genre="fiction"><title>Dune</title></book>
<book genre="reference"><title>PHP Cookbook</title></book>
</library>
XML;
$xml = simplexml_load_string($data);
$fiction = $xml->xpath("//book[@genre='fiction']");
echo $fiction[0]->title . "\n"; // Dune
echo count($fiction) . " match\n"; // 1 matchВывод:
Dune
1 matchЧтение атрибута внутри предиката использует @, а чтение атрибута из результирующего узла — синтаксис массива: (string) $book['genre']. Подробнее см. атрибуты.
Работа с пространствами имён XML
Если документ объявляет пространства имён, простой путь вроде //book вернёт ничего — парсеру нужен префикс пространства имён. Сначала зарегистрируйте префикс с помощью registerXPathNamespace(), а затем используйте его в выражении:
<?php
$data = <<<XML
<lib:library xmlns:lib="http://example.com/lib">
<lib:book><lib:title>Clean Code</lib:title></lib:book>
</lib:library>
XML;
$xml = simplexml_load_string($data);
$xml->registerXPathNamespace('l', 'http://example.com/lib');
foreach ($xml->xpath('//l:book/l:title') as $title) {
echo $title . "\n"; // Clean Code
}Вывод:
Clean CodeЗарегистрированный префикс (l) является локальным для вашего запроса — он не обязан совпадать с префиксом, используемым в документе (lib); должен совпадать только URI пространства имён.
Подводные камни
- Всегда проверяйте результат.
xpath()возвращаетfalseпри некорректном выражении и пустой массив при отсутствии совпадений. Конструкцияforeach (($xml->xpath($e) ?: []) as $n)защищает от обоих случаев. - Результаты — это объекты, не строки. Используйте приведение
(string), когда нужен текст:(string) $node. - XPath индексируется с 1.
//book[1]— первая книга;[0]не существует. - Важен контекст. Вызов
xpath('title')на узлеbookвыполняет поиск относительно этого узла, тогда как ведущий/или//выполняет поиск от корня документа независимо от места вызова.
Заключение
SimpleXMLElement::xpath() превращает глубокий, повторяющийся обход дерева в единственный декларативный запрос. В сочетании с предикатами и регистрацией пространств имён он позволяет точно указывать нужные узлы. Используйте его вместе с simplexml_load_string() или расширенным API SimpleXML для чтения и преобразования XML всего в нескольких строках.