Пишу, по большей части, про историю, свою жизнь и немного про программирование.

PHP4 без XML-парсера, плюс XPath

Я недавно написал простенький парсер XML для PHP 4.3+, для случаев, когда никакого встроенного парсера XML на хостинге не оказалось.

А вчера мне подумалось, что парсить XML в дерево это, конечно, замечательно, но пользоваться этим всё равно неудобно. Было бы неплохо дать возможность получать произвольные узлы так же просто, как это делается при помощи XPath.

Весь XPath я, конечно, реализовывать не буду, да мне столько и не надо. В общем, написал класс, который используется в паре VerySimple_XMLParser. Реализованы получение потомков тега, «*», «node()» и «text()». Это всё, но мне хватит.

// Simple XPath for VerySimple_XMLParser
// /
class VerySimple_XMLParser_Tree
{
    var $path;
    var $counter;
    var $xpath;
    
    function VerySimple_XMLParser_Tree()
    {
        $this->path = array('/');
        $this->counter = 0;
        $this->xpath = array();
    }


    function collect($type, $args)
    {
        $path = implode($this->path);

        switch ($type) {
            case 'TEXT':
                $this->xpath[$path . $this->counter . '/text()/'] = $args;
                $this->counter++;
                break;

            case 'TAGOPEN':
                list($name, $attributes) = $args;

                $tag = $this->counter . '/' . $name . '/';

                $this->xpath[$path . $tag] = $attributes;
                $this->counter = 0;

                $this->path[] = $tag;
                break;

            case 'TAGCLOSE':
                $this->counter = 1 + (int) array_pop($this->path);
                break;
        }
    }

    function halfxpath($path)
    {
        $keys = array_keys($this->xpath);
        $trans = array(
            '\/'       => '\/\d+\/',
            '\*'       => '[^\/()]+',
            'node\(\)' => '[^\/]+',
        );

        $filter   = strtr(preg_quote($path, '/'), $trans);
        $filtered = preg_grep("/^$filter\/$/su", $keys);

        $nodes = array();

        foreach ($filtered as $key) {
            $nodes[$key] = $this->xpath[$key];
        }

        return $nodes;
    }
}

// а вот так это можно использовать:
$xml =<<<XML
<root>
    <a>
        <b><c>Xa!</c></b>
        <b>Hello</b>
    </a>
    <a name="value"/>
</root>
XML;

$tree = &new VerySimple_XMLParser_Tree();

$sax = &new VerySimple_XMLParser(array(&$tree, 'collect'));
$sax->parse($xml);

echo '<plaintext>';
var_dump($tree->halfxpath('/*/a/b/text()'));
var_dump($tree->halfxpath('/*/a/b/node()'));
var_dump($tree->halfxpath('/*/a/b/*'));
var_dump($tree->halfxpath('/root/a'));

Метод для запросов я назвал «halfxpath», намекая, что это не настоящий XPath, а лишь неполноценная эмуляция.

Основная идея, как видно из кода — не парсить XML в дерево, а помещать каждый элемент по специального вида пути, который потом будет проверен на совпадение по регулярному выражению, приготовленному из запроса.

5 комментариев
Дмитрий Фантазеров (Смирнов) (fantaseour.ya.ru) 2010

Полдела сделано :) теперь еще написать лексический парсер для выражений xsl:select и xsl:if и будет XSLT :)

Евгений Степанищев (bolknote.ru) 2010

Комментарий для fantaseour.ya.ru:

Да нафига он мне :)

Я же свои задачи решаю, а не пишу сферического коня в вакууме.

Дмитрий Фантазеров (Смирнов) (fantaseour.ya.ru) 2010

да я так, больше даже с самоиронией. я для своего урезанного xslt делал естественно и XPath -​-​ перед выполнением строил дерево узлов исходного xml, и тоже искал по пути. И еще кешировал индексы узлов, которые уже были найдены.

Та еще городушка была конечно.

Евгений Степанищев (bolknote.ru) 2010

Комментарий для fantaseour.ya.ru:

У меня цели попроще и производительность можно не оптимизировать.

А что за проект такой, где понадобилось свой XSLT-процессор писать?

Дмитрий Фантазеров (Смирнов) (fantaseour.ya.ru) 2010

Комментарий для Евгения Степанищева:

Да банальная cms. Казалось что xml-xslt решит сразу кучу проблем. Оказалось однако, что sablotron встает с таким скрипом и не всегда, что решил подстраховаться -​-​ сделать усеченный парсер.