XPath + PHP – примеры использования запросов и функций


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

В начале, предоставлю вам два типа документов: DTD и XML, на примере которых мы рассмотрим функционирование PHP DOM XPath. Вот как они выглядят:

<!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>A Book</title>
    <author>An Author</author>
    <genre>Horror</genre>
    <chapter position="first">
      <chaptitle>chapter one</chaptitle>
      <text><![CDATA[Lorem Ipsum...]]></text>
    </chapter>
  </book>
  <book isbn="isbn1235">
    <title>Another Book</title>
    <author>Another Author</author>
    <genre>Science Fiction</genre>
    <chapter position="first">
      <chaptitle>chapter one</chaptitle>
      <text><![CDATA[<i>Sit Dolor Amet...</i>]]></text>
    </chapter>
  </book>
</library>

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

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

//library/book

Вот так! Два слеша впереди определяют корневой элемент документа, а один слеш производит переход к дочернему элементу book. Это просто и быстро, не так ли?

Но что, если вы хотите выбрать определенный элемент book из множества? Давайте предположим, что вам нужны книги «Определенного автора». XPath запрос для этого будет следующим:

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

Вы можете использовать text() в квадратных скобках для сравнения значения узла. Также «/..» означает, что мы хотим использовать родительский элемент (т. е. возвращаемся на один узел выше).

XPath запросы осуществляются с помощью одной или двух функций: query() и evaluate(). Обе формируют запрос, но разница в возвращаемом результате. query() всегда будет возвращать DOMNodeList, в отличии evaluate() будет возвращать текстовый результат, если это возможно. Для примера, если ваш 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;
}

Заметьте, нам не нужно повторно проверять значение каждого элемента, чтобы определить, каким автором написана каждая книга. Но мы можем более упростить код, используя 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() возвращает текстовый результат. Используя query(), возвратиться DOMNodeList, но он будет пустым.

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

XPath Функции

Вот несколько функций, которые могут использоваться с XPath. Также вы найдете множество ресурсов, которые детально рассматривают каждую доступную функцию. Если вам нужно вычислять DOMNodeList или сравнивать nodeValue (значение узла), можно найти подходящую 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 невероятно проста.

Использование PHP функций совместно с XPath

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

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

<?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 запрос. Это можно сделать с помощью нескольких шагов. Прежде всего, нам нужно зарегистрировать namespase с XPath объектом. PHP функции в XPath запросах вызываются с помощью строки «php:functionString», после чего прописывается имя желаемой функции. Также, namespace более подробно рассматривается на http://php.net/xpath. Другие значения namespace будут выдавать ошибку. После этого нам нужно вызвать registerPHPFunctions(). Эта функция сообщает PHP, что когда идет обращение через namespace «php:», этот вызов будет обрабатывать именно PHP.

Примерный синтаксис вызова функций будет следующим:

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

Давайте совместим все это вместе в следующем примере функции getNumberOfWords():

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

    //регистрируем php namespace
    $xpath->registerNamespace("php", "http://php.net/xpath"); 

    //теперь php функции могут вызываться в 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[text()]))

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

Давайте напишем функцию, которая будет за пределами класса, для демонстрации базовой функциональности. Функция, которую мы будем использовать, возвращает книги автора «George Orwell». Она должна возвращать true для каждого узла, который вы хотите включить в запрос.

<?php
function compare($node) {
    return $node[0]->nodeValue == "George Orwell";
}

Аргумент, который передается в функцию, является массивом DOM элементов. Эта функция проходит по массиву и определяет нужные элементы, после чего включает их в 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 – это отличный способ сократить количество кода и повысить его обработку, при работе с XML. Дополнительная функциональность PHP DOM позволяет вам расширить XPath функции. Это реально полезная штука, если вы будете ее использовать и углубляться в специфику, вам придется меньше и меньше писать кода.


Источник материала ...

Дальше: Рисование на JavaScript с помощью Paper.js, Processing.js, Raphael.js


Дискуссия по теме     0 Комментариев
Добавить комментарий
Просмотров: 26010