【问题标题】:Debug a DOMDocument Object in PHP在 PHP 中调试 DOMDocument 对象
【发布时间】:2010-10-15 14:46:06
【问题描述】:

我正在尝试在 php 中调试一个大而复杂的 DOMDocument 对象。理想情况下,如果我能让 DOMDocument 以类似数组的格式输出,那就太好了。

DoM文档:

$dom = new DOMDocument();
$dom->loadHTML("

Hello World

"); var_dump($dom); //或类似的东西

这个输出

DOMDocument 对象 ( ) 

而我希望它输出

DOM文档:
html
=>身体
==>p
===>你好世界

或者类似的东西。为什么没有方便的调试或输出?!?

【问题讨论】:

    标签: php domdocument


    【解决方案1】:

    这个答案可能有点晚了,但我喜欢你的问题!

    PHP 没有任何内置功能可以直接解决您的问题,因此没有 XML 转储之类的东西。

    但是,PHP 的 RecursiveTreeIterator­Docs 与您的输出非常接近:

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

    (如果您的 X(HT)ML 结构看起来更复杂,它会更好看。)

    它的使用非常简单(与大多数迭代器一样)foreach:

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

    (你可以把这个包裹在一个函数里面,所以你只需要调用这个函数)

    即使这看起来很简单,但有一个警告:它需要在 DOMDocument 树之上的 RecursiveIterator。由于 PHP 无法猜测您需要什么,因此需要将其包装到代码中。正如所写的那样,我发现这个问题很有趣(显然您没有要求 XML 输出),所以我编写了一些小代码来提供所需的递归迭代器。所以我们开始吧。

    首先,您可能不熟悉 PHP 中的迭代器。这不是使用我将展示的代码的交易,因为我会这样做向后,但是,每当您考虑自己运行一些代码时,请考虑您是否可以使用PHP 必须提供的迭代器功能。我之所以这么写,是因为它有助于解决常见问题,并使彼此并不真正相关的组件能够相互协作。例如,RecursiveTreeIterator­Docs 是内置的,它可以与您提供的任何内容一起使用(您甚至可以对其进行配置)。但是,它需要RecursiveIterator 才能进行操作。

    所以让我们给它一个RecursiveIterator,它为DOMNodes 提供&lt;tag&gt;,它们是标签(元素),如果它们是文本节点,则只是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 函数,它只返回 DOMNode 中的 tagNamebracketsWikipedia (&lt;&gt;) 和 textnodes 的文本。这就是您的输出所需要的,所以这就是编码所需的一切。

    实际上这在你也有抽象代码之前是行不通的,但是为了可视化代码的使用方式(最有趣的部分),让我们来看看它:

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

    由于它是向后完成的,目前我们已经根据DOMNode 将由RecursiveTreeIterator 显示指定的输出。到目前为止很好,很容易得到。但是缺少的肉在抽象代码中,以及如何在DOMElement 内的所有节点上创建RecursiveIterator。只需预览整个代码是如何被调用的(如前所述,您可以将其放入一个函数中,以便在代码中轻松访问它以进行调试。可能是一个名为 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。在RecursiveTreeIterator 中最终需要的是所需的RecursiveIterator。它得到了装饰,因此树的转储实际上在括号中打印标记名并按原样打印文本。

    现在可能值得分享它的代码:

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

    这是一个非常短的类,只有两个函数。我在这里作弊,因为这个类也是从另一个类扩展而来的。但正如所写,这是倒退的,所以这个类实际上负责递归:hasChildrengetChildren。显然,即使这两个函数也没有太多代码,它们只是将“问题”(hasChildren?getChildren?)映射到标准的DOMNode。如果一个节点有子节点,好吧,说是或者只是返回它们(这是一个迭代器,以迭代器的形式返回它们,因此是new self())。

    因此,由于这很短,因此在窒息之后,只需继续父类DOMIteratorimplements RecursiveIterator­Docs 只是为了使其工作):

    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 的基本迭代器,它只需要DOMNodeDOMNodeList 来迭代。这听起来可能有点多余,因为 DOM 已经支持 DOMNodeList 的这种类型,但它不支持 RecursiveIterator 并且我们已经知道我们需要一个 RecursiveTreeIterator 用于输出。所以在它的 constructor 中创建了一个 Iterator 并将其传递给父类,这又是抽象代码。当然,我会在一分钟内揭示这段代码。由于这是倒退,让我们回顾一下到目前为止所做的事情:

    • RecursiveTreeIterator 用于树状输出。
    • DOMRecursiveDecoratorStringAsCurrent 用于在树中可视化 DOMNode
    • DOMRecursiveIteratorDOMIterator 以递归方式遍历 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 或将它们与您可能正在使用的自动加载器集成。 Full code at once.

    如果需要编辑输出方式,可以编辑DOMRecursiveDecoratorStringAsCurrent或者在xmltree_dump里面修改RecursiveTreeIterator­的配置。希望这会有所帮助(即使很长,backwards 也很直接)。

    【讨论】:

    • +1 ....你也在圣诞节写了这一切。成就“永远孤独”解锁。
    • 我收到Catchable fatal error: Argument 1 passed to IteratorIterator::__construct() must implement interface Traversable, instance of DOMNodeList given - 我做错了什么?我从要点中获取了代码,并在底部的usage 块中使用了最后一个示例...
    • 当然,它应该可以工作,你到底在哪一行得到错误,?是为了gist line 67上的$nodeOrNodes = new IteratorIterator($nodeOrNodes);吗?
    • 这很有用!这对我帮助很大。谢谢:)
    • @hakre,你是最棒的!
    【解决方案2】:

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

    $dom->formatOutput = true;
    echo $dom->saveXML();
    

    【讨论】:

    • 如果想要调试不是文档本身而是文档一部分的 DOMElement:$element-&gt;ownerDocument-&gt;saveXML($element));
    【解决方案3】:

    对于 dom 节点,只需使用以下内容:

    print_r(simplexml_import_dom($entry)->asXML());
    

    【讨论】:

      【解决方案4】:

      虽然我自己没有尝试过,但请查看Zend_Dom,这是Zend Framework 的一部分。大多数 Zend Framework 组件的文档和示例非常详尽。

      【讨论】:

        【解决方案5】:

        我刚刚使用了 DOMDocument::save。它必须写入文件是很糟糕的,但无论如何。

        【讨论】:

        • 如果你这样做了,你可以只用 saveHTML 代替并将它转到一个字符串。
        【解决方案6】:

        您可以作弊并使用 JSON 通过将其转换为数组来检查结构。

        print_r(json_decode(json_encode($node), true));
        

        【讨论】:

          猜你喜欢
          • 2020-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-27
          • 2012-01-10
          • 2013-06-16
          • 2011-02-20
          相关资源
          最近更新 更多