Игры с XPath



    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 достойная замена в тех местах, где неудобно использовать БД по каким либо причинам.

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


    Первоисточник
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 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
                            В свете наличия хорошего перевода спецификации на русский язык ценность данного материала сомнительна. А уж при таком количестве неточностей да еще кросспостингом и вовсе входит в минус.

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