@highw read-only
Пользователь
25 июля 2009 в 21:01

Разработка → Игры с XPath

XML*


XML

XML — текстовый формат, предназначенный для хранения структурированных данных (взамен существующих файлов баз данных), для обмена информацией между программами, а также для создания на его основе более специализированных языков разметки (например, XHTML), иногда называемых словарями. XML является упрощённым подмножеством языка SGML.

XML это удобная штука хранить файлы в читаемом виде.

Например простой XML файл может быть таким

<?xml version="1.0" encoding="UTF-8"?>
<root>

 <data>

    <row>
         <id>1</id>
         <name company="ibm" status="banned" >Vasilii</name>
         <age>18</age>

    </row>

    <row>
          <id>2</id>
          <name company="ibm">Anton</name>
          <age>20</age>
    </row>
    <row>
          <id>3</id>
          <name company="apple">Petro</name>
          <age>35</age>
     </row>
 </data>

</root>

На данном файле будет показана работа с XPath.

Как видно из примера XML удобен тем, что он легко читаем, структурирован. В данном формате удобно хранить данные, более того, существует множество библиотек по работе с данным типом файла.

Любой элемент имеет свой путь, например путь root-data-row[0] будет указывать на ветку Василия.

Но когда программист начинает использовать данный файл у него возникает проблем с манипуляцией файла. Например необходимо выбрать всех людей у которых возраст < 20.

Что делать?

Решением данной проблемы является XPath.

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

Итак, есть документ.

Напишем РНР код для этого документа. В данном примере используются фукнции SimpleXML для работы.

Есть код:
$xml = simplexml_load_file("db.xml");
var_dump($xml);

Результат будет следующий:
object(SimpleXMLElement)[1]
  public 'data' =>
    object(SimpleXMLElement)[2]
      public 'row' =>
        array
          0 =>
            object(SimpleXMLElement)[3]
              public 'id' => string '1' (length=1)
              public 'name' => string 'Vasilii' (length=7)
              public 'age' => string '18' (length=2)
          1 =>
            object(SimpleXMLElement)[4]
              public 'id' => string '2' (length=1)
              public 'name' => string 'Anton' (length=5)
              public 'age' => string '20' (length=2)
          2 =>
            object(SimpleXMLElement)[5]
              public 'id' => string '3' (length=1)
              public 'name' => string 'Petro' (length=5)
              public 'age' => string '35' (length=2)

Как видно из результата доступ можно делать как: $xml->data->row[0]->name == Valilii.

Теперь собственно XPath.

XPath позволяет использовать условия и свои встроенные функции для выборки веток.

Например / (слеш) — указывает на иерархию веток, например /data,

Основные моменты при использовании:

/ — корневой узел

// — множество узлов удовлетворяющих след условие (детальнее на вики)

* — любые символы

@ — аттрибут

[] — аналог () в sql, задает условия

and, or — И, ИЛИ.

Более подробно написано ru.wikipedia.org/wiki/XPath

Сделаем выборку всех элементов с помощью XPath.

foreach( $xml->xpath("//row") as $res) var_dump($res);

object(SimpleXMLElement)[6]
  public 'id' => string '1' (length=1)
  public 'name' => string 'Vasilii' (length=7)
  public 'age' => string '18' (length=2)

object(SimpleXMLElement)[7]
  public 'id' => string '2' (length=1)
  public 'name' => string 'Anton' (length=5)
  public 'age' => string '20' (length=2)

object(SimpleXMLElement)[8]
  public 'id' => string '3' (length=1)
  public 'name' => string 'Petro' (length=5)
  public 'age' => string '35' (length=2)


Давайте сделаем выборку, где age>18
foreach( $xml->xpath("//row[age>18]") as $res) var_dump($res);

Результат:
object(SimpleXMLElement)[6]
  public 'id' => string '2' (length=1)
  public 'name' => string 'Anton' (length=5)
  public 'age' => string '20' (length=2)

object(SimpleXMLElement)[7]
  public 'id' => string '3' (length=1)
  public 'name' => string 'Petro' (length=5)
  public 'age' => string '35' (length=2)


Сделаем выборку у всех у которых аттрибут company='ibm'
foreach( $xml->xpath("//row[name[@company='ibm']]") as $res) var_dump($res);

Результат:
object(SimpleXMLElement)[6]
  public 'id' => string '1' (length=1)
  public 'name' => string 'Vasilii' (length=7)
  public 'age' => string '18' (length=2)

object(SimpleXMLElement)[7]
  public 'id' => string '2' (length=1)
  public 'name' => string 'Anton' (length=5)
  public 'age' => string '20' (length=2)

Следует обратить внимание, что атрибут @company является частью name, по этому просто написать row[ @company=..] — нельзя.

Сделаем выборку людей, у которых возраст больше 18 и работает в IBM
foreach($xml->xpath("//row[name[@company='ibm'] and age>18]") as $res) var_dump($res);

Результат:
object(SimpleXMLElement)[6]
  public 'id' => string '2' (length=1)
  public 'name' => string 'Anton' (length=5)
  public 'age' => string '20' (length=2)


Можно увидеть что комбинировать очень просто.
Следующий пример покажет как можно использовать встроенные функции, например
last() — получим последнего человека списка.
foreach($xml->xpath("//row[last()]") as $res) var_dump($res);

Результат:
object(SimpleXMLElement)[6]
  public 'id' => string '3' (length=1)
  public 'name' => string 'Petro' (length=5)
  public 'age' => string '35' (length=2)


А теперь найдем людей которые НЕ работают в IBM:
foreach( $xml->xpath("//row[name[not(@company='ibm')]]") as $res) var_dump($res);

Результат:
object(SimpleXMLElement)[6]
  public 'id' => string '3' (length=1)
  public 'name' => string 'Petro' (length=5)
  public 'age' => string '35' (length=2)


Все очень просто и удобно!

Результаты


Xpath — хороший инструмент для работы с документом XML. Базовые функции выполняет.
Для более сложных есть XQuery, но речь сейчас не об этом.

XML+XPath достойная замена в тех местах, где неудобно использовать БД по каким либо причинам.

Спасибо.
Продолжение следует.


Первоисточник
@highw
карма
0,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (22)

  • –5
    >> Первоисточник
    Это не первоисточник, это копи-cпи$динг
    • –4
      лолик. это мой сайт:)
      • +3
        Т.е. вы все свои статью сюда собираетесь копи-пастить?

        Я к тому, что в вашей «оригинальной» статье есть ссылка «Первоисточник», которая ведет на саму себя. Вы когда копипастите сюда или отсюда в блог, хоть смотрите, как копируете.
        • –2
          Сколько злости то…
          Подготовил статью для публикаций, в чем проблема?

          Забыл в оригинале убрать — сейчас уберу.
          • +1
            Злость — это, кажется, у читателей: вот и коммент заминусовали; я просто обратил ваше внимание на досадную оплошность.

            P.S. Правила Хабра в явном виде запрещают кросс-постинг; надеюсь, у вас не будет проблем.
  • +7
    / — это не узел, это указатель на сам корень. Т.е. используя / вы переводите XPath на поиск от корня документа

    // — тоже не совсем верно — это не множество узлов, это указание на произвольную глубину поиска указанной ноды. Т.е. //row найдет вам и /item/row и /item/mynest/row и /item/mynest/subtree/subnest/pod/row.

    Ну еще дополню — [] это не аналог (), ну не то что бы не аналог, просто для людей не знающих синтаксиса РНР совсем непонятно.
    [] — это предикат, который проверяется на истинность ( т.е. проверяемая строка попадет в результат если все ее предикаты истинны. Добавлю что можно писать несколько предикатов подряд, чтобы легче читалось.

    И еще, насколько я вижу, ваше выражение людей не работающих в IBM — неверное, banned у василия говорит от том что он уже тоже НЕ работает в IBM
    //row[name[not(@company='ibm')]]

    правильнее будет еще добавить проверку статуса
    //row[name[@company !='ibm' or @status !='banned']]

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

    Как пример — буквально в пятницу в небольшой утилите надо было разбирать 15 метров xml фрагментов. Оказалось ГОРАЗДО выгоднее переложить все 15000 записей об объектах в память, в большую хэш-таблицу, чем каждый раз гонять xpath и извлекать только конкретный объект для текущей итерации.
    • +8
      Добавлю еще — на мой взгляд, статья слабовата для того чтобы быть достойной быть написанной :). Подобные базовые знания легко можно почерпнуть в любом источнике. А вот допущенные неточности и ошибки — могут ввести в заблуждение неопытных товарищей.
    • 0
      // неверное, banned у василия говорит от том что он уже тоже НЕ работает в IBM
      Я хотел больше показать работу, а не нагружать логикой пример)

      спасибо за поправки
      • 0
        welcome to nitpicker corner :)
  • +3
    Эмоциональная реакция: скучно.

    По существу: ни заголовок, ни текст до хабраката не дают понять, что игры будут не только XPath, но и с PHP. Так же информация достаточно поверхностная. Проверили бы хотя бы работу библиотеки с юникодом (UTF-8, UTF-16).
  • 0
    Как при помощи XPath можно выбрать элемент с максимальным значением атрибута?
    • 0
      Простой ответ — никак. Эта задача выходит за рамки задач XPath.

      Сложный ответ — задать условие, когда значение атрибута найденного элемента будет выше предыдущего найденного значения атрибута. Но учтите — это будет работать весьма медленно. Намного лучше будет просто найти все нужные элементы и уже непосредственно средствами своего языка программирования выполнить поиск наибольшего.
    • +2
      item[not(../item/@attr > @attr)]
      item[not((preceding-sibling::item | following-sibling::item)/@attr > @attr)]
    • 0
      имхо — отсортировать и взять первый. Но это уже действительно — уже xslt
    • 0
      В XPath2.0 есть функция max. Правда XSLT2.0 процессоров мало. К примеру: saxon.sourceforge.net/
  • 0
    Понял, в необходимо окунуться в более сложные схемы и показать как это.
    Взял на заметку.
  • +1
    Есть ощущение, что автор изучал XPath по примерам, не читая документацию. Пожалуйста, не вводите читателей в заблуждение:

    / не просто узел, а корневой узел;
    // не множество узлов, а сокращение для /descendant-or-self::node()/;
    * выбирает не любые символы, а только дочерние узлы-элементы (например, сюда не попадают текстовые узлы).

    Вместо //row[name[@company='ibm']] лучше писать //row[name/@company='ibm'].
    • –2
      Это я и имел ввиду, нечетко написал.
    • +1
      > Вместо //row[name[@company='ibm']] лучше писать //row[name/@company='ibm']
      Это не одно и то же. В первом случае мы перебираем все теги name и проверяем их атрибут. Во втором сравниваем *последовательность* name/@company со строкой. Xslt2.0 процессоры будут сильно ругаться на такое.
      • 0
        Насколько я знаю, в XPath 1.0 сравнивается набор узлов. Не могли бы вы объяснить, на что именно ругаются процессоры XSLT 2.0? В документации ничего подозрительного не нашёл.
        • 0
          Ругаются на ошибку преобразования типов. В документации, если поискать «compatibility mode», всякие «raise error» очень часто встречается рядом.

          Вполне возможно конкретно данное выражение сработает. Вполне возможно, сработает корректно. На часах 2:20 проверять лень, извините…

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

          ЗЫ: Безусловно, есть XPath 1.0 compatibility mode, но и он не всегда спасает.
          ЗЗЫ: www.ozon.ru/context/detail/id/3964217/ вот тут об этом написано на странице 38.
  • +1
    В свете наличия хорошего перевода спецификации на русский язык ценность данного материала сомнительна. А уж при таком количестве неточностей да еще кросспостингом и вовсе входит в минус.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.