PHP DOM XPath

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

Оглавление:
  1. Основные запросы XPath
  2. Преимущество кода и скорость XPath
  3. Функции XPath
  4. XPath и PHP
  5. Заключение

Мы будем использовать DTD и XML, чтобы продемонстрировать функциональность PHP DOM XPath. Напоминаю, что DTD и XML выглядит следующим образом:

<!ELEMENT library (book*)>
<!ELEMENT book (title, author, genre, chapter*)>
<!ATTLIST book isbn ID #REQUIRED>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT genre (#PCDATA)>
<!ELEMENT chapter (chaptitle,text)>
<!ATTLIST chapter position NMTOKEN #REQUIRED>
<!ELEMENT chaptitle (#PCDATA)>
<!ELEMENT text (#PCDATA)>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE library SYSTEM "library.dtd">
<library>
  <book isbn="isbn1234">
    <title>Первая книга</title>
    <author>Роберт Ирвин Говард</author>
    <genre>Фантастика</genre>
    <chapter position="first">
      <chaptitle>Глава первая</chaptitle>
      <text><![CDATA[Вступление...]]></text>
    </chapter>
  </book>
  <book isbn="isbn1235">
    <title>Вторая книга</title>
    <author>Артур Конан Дойл</author>
    <genre>Детектив</genre>
    <chapter position="first">
      <chaptitle>Глава первая</chaptitle>
      <text><![CDATA[<i>Вступление...</i>]]></text>
    </chapter>
  </book>
</library>
К началу

Основные запросы XPath

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

Например, запрос XPath к XML возвращает коллекцию всех элементов книги:

//library/book

Вот и всё. Две косые черты (слэш) указывают на библиотеку, и одной косой чертой на саму книгу.

Но что, если вы хотите указать на конкретного автора в книге. Для этого и нужен XPath:

//library/book/author[text() = "An author"]/..

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

XPath выполняет запросы с одной из двух функций: query() и evaluate(). Разница заключается в типе их возвращения; query() будет всегда возвращать список DOMNodeList в то время как evaluate() возвращаtn типизированный результат, если это возможно.

Например, если запрос XPath возвращает количество книг, написанных определенным автором, а не фактически сами книги, то query() возвращает пустой список DOMNodeList.evaluate() просто возвращает данные из узла.

К началу

Преимущество кода и скорость XPath

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

Без использования XPath:

<?php
public function getNumberOfBooksByAuthor($author) {
    $total = 0;
    $elements = $this->domDocument->getElementsByTagName("author");
    foreach ($elements as $element) {
        if ($element->nodeValue == $author) {
            $total++;
        }
    }
    return $number;
}

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

<?php
public function getNumberOfBooksByAuthor($author)  {
    $query = "//library/book/author1/..";
    $xpath = new DOMXPath($this->domDocument);
    $result = $xpath->query($query);
    return $result->length;
}?>

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

<?php
public function getNumberOfBooksByAuthor($author)  {
    $query = "count(//library/book/author1/..)";
    $xpath = new DOMXPath($this->domDocument);
    return $xpath->evaluate($query);
}

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

Обратите внимание, что в последнем примере использовался evaluate(). Это потому, что функция count() возвращает типизированный результат, и вернет пустой список DOMNodeList.

Я обнаружил, что последний способ на 30% быстрее чем версии 1 и 2. Хотя эти показатели будут варьироваться в зависимости от вашего сервера и запросов. XPath в его чистом виде, как правило дают значительное преимущество скорости, а также делает код проще для чтения.

К началу

Функции XPath

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

Вы уже видели функцию count(). Теперь рассмотрим функцию id() которая возвращает название книг с данного номера ISBN.

Необходимо использовать выражение XPath:

id("isbn1234 isbn1235")/title

Заметьте, что значения, которые вы ищете заключены в кавычки и разделены пробелом, нет необходимости разделять условия запятой.

<?php
public function findBooksByISBNs(array $isbns) {
    $ids = join(" ", $isbns);
    $query = "id('$ids')/title"; 

    $xpath = new DOMXPath($this->domDocument);
    $result = $xpath->query($query); 

    $books = array();
    foreach ($result as $node) {
        $book = array("title" => $booknode->nodeValue);
        $books[] = $book;
    }
    return $books;
}

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

К началу

XPath и PHP

Если нужна большая функциональность стандартные функции XPath вам не помогут. К счастью, PHP DOM позволяет совмещенное использование функций запроса XPath и PHP.

Рассмотрим количество слов в названии книги. Это простейшая функция:

<?php
public function getNumberOfWords($isbn) {
    $query = "//library/book[@isbn = '$isbn']"; 

    $xpath = new DOMXPath($this->domDocument);
    $result = $xpath->query($query); 

    $title = $result->item(0)->getElementsByTagName("title")
        ->item(0)->nodeValue; 

    return str_word_count($title);
}

Но, еще есть функция str_word_count() непосредственно в запросе XPath, и несколько шагов выполнения. Сначала нужно зарегистрировать пространство имен XPath. PHP функции в запросах XPath предшествует PHP: functionString, а затем в круглых скобках имя функции, которую необходимо использовать. Кроме того пространство имен для определения будет http://php.net/xpath. Пространство имен должно быть установлено, любые другие значения приводят к ошибкам. Последнее, нужно вызвать функцию registerPHPFunctions(), которая говорит PHP, что всякий раз когда он сталкивается с пространством имён, PHP должен смериться с этим, и вести себя адекватно.

Фактический синтаксис вызова функции:

php:functionString("nameoffunction", arg, arg...)

Все вместе приводит к следующему переопределению:

<?php
public function getNumberOfWords($isbn) {
    $xpath = new DOMXPath($this->domDocument);

    //register the php namespace
    $xpath->registerNamespace("php", "http://php.net/xpath"); 

    //ensure php functions can be called within xpath
    $xpath->registerPHPFunctions();

    $query = "php:functionString('str_word_count',(//library/book[@isbn = '$isbn']/title))"; 

    return $xpath->evaluate($query);
}

Вам не нужно вызывать функцию XPath text(), чтобы предоставить текст узла. Метод RegisterPHPFunctions() делает это автоматически. Но следующее действие:

php:functionString('str_word_count',(//library/book[@isbn = '$isbn']/title)))

Регистрация функции PHP не ограничиваются функциями, которые поставляются с PHP. Вы можете определить свои собственные функции и обеспечивать в XPath. Единственная разница состоит в том, что при определении функции можно использовать php:function вместо php: functionString. Можно предоставить функции самостоятельно либо статическими методами. Вызов методов экземпляра не поддерживается.

Давайте использовать обычную функцию, которая выходит за рамки класса. Чтобы продемонстрировать основные функции, мы будем использовать функции возврата только для книги Майкл Джон Муркок. Книга должна возвращать true для каждого узла который вы хотите включить в запрос.

<?php
function compare($node) {
    return $node[0]->nodeValue == "Майкл Джон Муркок";
}

Аргумент, передаваемый в функцию массив DOMElement. Это зависит от массива, проходит ли узел испытания, возвращать его в список DOMNodeList. В этом примере, узел обрабатывает /book, и мы используем /author, чтобы сделать определение.

Создать метод getGeorgeOrwellBooks() :

<?php
public function getGeorgeOrwellBooks() {
    $xpath = new DOMXPath($this->domDocument);
    $xpath->registerNamespace("php", "http://php.net/xpath");
    $xpath->registerPHPFunctions(); 

    $query = "//library/book1";
    $result = $xpath->query($query); 

    $books = array();
    foreach($result as $node) {
        $books[] = $node->getElementsByTagName("title")
            ->item(0)->nodeValue;
    } 

    return $books;
}

Если compare() был статический метод, то вам нужно будет изменить запрос XPath:

//library/book[php:function('library::compare', author)]

По правде говоря, все эти функции могут быть легко закодированы просто с XPath, но этот пример показывает как можно расширить запросы XPath.

Вызов метода объекта не представляется возможным в XPath. Если вам нужно получить доступ к некоторым свойствам объекта или метода выполнения запроса XPath, лучшим решением было бы сделать с XPath, а затем работать с полученным списком DOMNodeList с любым объектом методов и свойств по мере необходимости.

К началу

Заключение

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

автор
htmlhook.ru | Скрипты для веб-приложений