Отладка объекта DOMDocument в PHP - PullRequest
17 голосов
/ 26 марта 2009

Я пытаюсь отладить большой и сложный объект DOMDocument в php. В идеале было бы неплохо, если бы я мог получить DOMDocument для вывода в виде массива

DomDocument:

$dom = new DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
var_dump($dom); //or something equivalent

Это выводит

DOMDocument Object ( ) 

тогда как я бы хотел вывести

DOMDocument:
html
=>body
==>p
===>Hello World

Или что-то в этом роде. Почему для этого нет удобной отладки или вывода?!?

Ответы [ 6 ]

33 голосов
/ 26 декабря 2011

Возможно, этот ответ немного запоздал, но мне понравился ваш вопрос!

PHP не имеет встроенных средств для решения вашей проблемы, поэтому здесь нет дампа XML или чего-то еще.

Однако в PHP есть RecursiveTreeIterator & shy; Документы , которые очень близки к вашему выводу:

\-<html>
  \-<body>
    \-<p>
      \-Hello World

(это будет выглядеть лучше, если ваша структура X (HT) ML выглядит более сложной.)

Используется довольно просто (как большинство итераторов) с foreach:

$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
    echo $value . "\n";
}

(Вы можете обернуть это внутри функции, поэтому вам нужно только вызвать функцию)

Даже если это выглядит просто, есть одна оговорка: ему нужно RecursiveIterator над DOMDocument деревом. Поскольку PHP не может угадать, что вам нужно, его нужно обернуть в код. Как я уже писал, этот вопрос показался мне интересным (и, очевидно, вы не запрашивали вывод XML), поэтому я написал небольшой код, предлагающий рекурсивный итератор. Итак, поехали.

Прежде всего, вы, возможно, не знакомы с итераторами в PHP. Нет ничего сложного в использовании кода, который я покажу, поскольку я сделаю это задом наперед , однако, всякий раз, когда вы решаете запустить какой-то код самостоятельно, подумайте, можете ли вы использовать Возможности итератора PHP может предложить. Я пишу это потому, что это помогает решить общие проблемы и заставить компоненты, которые на самом деле не связаны друг с другом, работать вместе. Например, RecursiveTreeIterator & shy; Docs встроен, и он будет работать со всем, чем вы его кормите (и вы даже можете его настроить ). Однако для работы требуется RecursiveIterator.

Итак, давайте дадим ему RecursiveIterator, который предлагает <tag> для DOMNodes, которые являются тегами (элементами), и просто text, если они являются текстовыми узлами:

class DOMRecursiveDecoratorStringAsCurrent extends RecursiveIteratorDecoratorStub
{
    public function current()
    {
        $node = parent::current();
        $nodeType = $node->nodeType;

        switch($nodeType)
        {
            case XML_ELEMENT_NODE:
                return "<$node->tagName>";

            case XML_TEXT_NODE:
                return $node->nodeValue;

            default:
                return sprintf('(%d) %s', $nodeType, $node->nodeValue);
        }
    }
}

Этот класс DOMRecursiveDecoratorStringAsCurrent (имя приведено только в качестве примера) использует некоторый абстрактный код в RecursiveIteratorDecoratorStub. Однако важной частью является функция ::current, которая просто возвращает tagName DOMNode в скобках Wikipedia (<>) и текст текстовых узлов как есть. Это то, что нужно вашему выводу, так что это все, что нужно для кодирования.

На самом деле это не работает, пока у вас также не будет абстрактного кода, но для визуализации кода, как он используется (самая интересная часть), давайте рассмотрим его:

$iterator = new DOMRecursiveDecoratorStringAsCurrent($iterator);
$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
    echo $value . "\n";
}

Как это сделано в обратном направлении, на данный момент у нас есть заданный вывод, на основе которого DOMNode должен отображаться RecursiveTreeIterator. Хорошо пока, легко получить. Но недостающее мясо находится внутри абстрактного кода и как создать RecursiveIterator для всех узлов внутри DOMElement. Просто просмотрите весь код, как он вызывается (как написано ранее, вы можете поместить это в функцию, чтобы сделать его легко доступным в вашем коде для целей отладки. Вероятно, функция с именем xmltree_dump):

$dom = new DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
$iterator = new DOMRecursiveIterator($dom->documentElement);
$iterator = new DOMRecursiveDecoratorStringAsCurrent($iterator);
$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
    echo $value . "\n";
}

Так что же мы получили здесь в дополнение к уже рассмотренному коду? Сначала есть DOMRecursiveIterator - и все тут. Остальной код является стандартным DOMDocument кодом.

Итак, давайте напишем о DOMRecursiveIterator. Это необходимый RecursiveIterator, который наконец необходим в RecursiveTreeIterator. Он получает оформленный , так что дамп дерева на самом деле печатает тэги в скобках и текст как есть.

Возможно, стоит поделиться этим кодом сейчас:

class DOMRecursiveIterator extends DOMIterator implements RecursiveIterator
{
    public function hasChildren()
    {
        return $this->current()->hasChildNodes();
    }
    public function getChildren()
    {
        $children = $this->current()->childNodes;
        return new self($children);
    }
}

Это довольно короткий класс только с двумя функциями. Я обманываю здесь, поскольку этот класс также идет от другого класса. Но, как написано, это задом наперед, поэтому этот класс на самом деле заботится о рекурсии: hasChildren и getChildren. Очевидно, что даже эти две функции не имеют большого количества кода, они просто отображают «вопрос» (hasChildren? getChildren?) На стандарт DOMNode. Если у узла есть дочерние элементы, скажите «да» или просто верните их (а это итератор, верните их в форме итератора, следовательно, new self()).

Так как это довольно коротко, после удушения просто продолжайте с родительским классом DOMIterator (implements RecursiveIterator & shy; Документы - это просто чтобы это заработало):

class DOMIterator extends IteratorDecoratorStub
{
    public function __construct($nodeOrNodes)
    {
        if ($nodeOrNodes instanceof DOMNode)
        {
            $nodeOrNodes = array($nodeOrNodes);
        }
        elseif ($nodeOrNodes instanceof DOMNodeList)
        {
            $nodeOrNodes = new IteratorIterator($nodeOrNodes);
        }
        if (is_array($nodeOrNodes))
        {
            $nodeOrNodes = new ArrayIterator($nodeOrNodes);
        }

        if (! $nodeOrNodes instanceof Iterator)
        {
            throw new InvalidArgumentException('Not an array, DOMNode or DOMNodeList given.');
        }

        parent::__construct($nodeOrNodes);
    }
}

Это базовый итератор для DOMPHP, просто требуется DOMNode или DOMNodeList для итерации. Возможно, это звучит немного излишне, поскольку DOM уже поддерживает этот тип с DOMNodeList, но он не поддерживает RecursiveIterator, и мы уже знаем, что нам нужен один для RecursiveTreeIterator для вывода. Таким образом, в конструкторе создается Iterator и передается родительскому классу, который снова является абстрактным кодом. Конечно, я раскрою этот код всего за минуту. Поскольку это задом наперед, давайте рассмотрим, что было сделано до сих пор:

  • RecursiveTreeIterator для древовидного вывода.
  • DOMRecursiveDecoratorStringAsCurrent для визуализации DOMNode в дереве
  • DOMRecursiveIterator и DOMIterator для рекурсивной итерации по всем узлам в DOMDocument.

Это с точки зрения определения как все, что нужно, однако код, который я назвал абстрактным, все еще отсутствует. Это просто какой-то простой прокси-код, он делегирует тот же метод другому объекту. Связанный шаблон называется Decorator . Тем не менее, это всего лишь код, сначала Iterator, а затем RecursiveIterator друг:

abstract class IteratorDecoratorStub implements OuterIterator
{
    private $iterator;
    public function __construct(Iterator $iterator)
    {
        $this->iterator = $iterator;
    }
    public function getInnerIterator()
    {
        return $this->iterator;
    }
    public function rewind()
    {
        $this->iterator->rewind();
    }
    public function valid()
    {
        return $this->iterator->valid();
    }
    public function current()
    {
        return $this->iterator->current();
    }
    public function key()
    {
        return $this->iterator->key();
    }
    public function next()
    {
        $this->iterator->next(); 
    }
}

abstract class RecursiveIteratorDecoratorStub extends IteratorDecoratorStub implements RecursiveIterator
{
    public function __construct(RecursiveIterator $iterator)
    {
        parent::__construct($iterator);
    }
    public function hasChildren()
    {
        return $this->getInnerIterator()->hasChildren();
    }
public function getChildren()
{
    return new static($this->getInnerIterator()->getChildren());
}
}

Ничего особенного, просто делегировать вызовы методов к унаследованному объекту $iterator. Похоже, повторение и хорошо итераторы о повторении. Я поместил это в абстрактные классы, поэтому мне нужно написать этот очень простой код только один раз. Так что, по крайней мере, мне самому не нужно повторяться.

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

Что ж, пока здесь много чего почитать, но самое интересное, вот и все.

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

function xmltree_dump(DOMNode $node)
{
    $iterator = new DOMRecursiveIterator($node);
    $decorated = new DOMRecursiveDecoratorStringAsCurrent($iterator);
    $tree = new RecursiveTreeIterator($decorated);
    foreach($tree as $key => $value)
    {
        echo $value . "\n";
    }
}

Использование:

$dom = new DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
xmltree_dump($dom->documentElement);

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

Если вам нужно отредактировать способ вывода, вы можете отредактировать DOMRecursiveDecoratorStringAsCurrent или изменить конфигурацию RecursiveTreeIterator­ внутри xmltree_dump. Надеюсь, что это полезно (даже довольно долго, в обратном направлении довольно прямолинейно).

14 голосов
/ 17 марта 2010

http://usphp.com/manual/en/function.dom-domdocument-savexml.php

$dom->formatOutput = true;
echo $dom->saveXML();
10 голосов
/ 17 декабря 2010

для узла dom, просто используйте следующее:

print_r(simplexml_import_dom($entry)->asXML());
0 голосов
/ 17 мая 2012

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

print_r(json_decode(json_encode($node), true));
0 голосов
/ 26 марта 2009

Я только что использовал DOMDocument :: save. Жаль, что он должен записывать в файл, но неважно.

0 голосов
/ 26 марта 2009

Хотя я сам не пробовал, посмотрите Zend_Dom , часть Zend Framework . Документация и примеры для большинства компонентов Zend Framework очень подробны.

...