21

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

DoM文档:

$dom = 新的 DOMDocument();
$dom->loadHTML("<html><body><p>Hello World</p></body></html>");
var_dump($dom); //或类似的东西

这输出

DOMDocument 对象 ( )

而我希望它输出

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

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

4

6 回答 6

36

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

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

但是,PHP 的RecursiveTreeIterator文档与您的输出非常接近:

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

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

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

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

(您可以将其包装在一个函数中,因此您只需要调用该函数)

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

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

RecursiveIterator所以让我们给它一个标签(元素),<tag>如果它们是文本节点:DOMNodestext

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只返回括号Wikipedia ( )中tagName的 a和 textnodes 的文本的函数。这就是你的输出所需要的,所以这就是编码所需的一切。DOMNode<>

实际上,除非您也拥有抽象代码,否则这不起作用,但是为了可视化代码的使用方式(最有趣的部分),让我们查看它:

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

由于它是向后完成的,目前我们已经指定了输出,基于哪个输出DOMNode将由RecursiveTreeIterator. 到目前为止很好,很容易得到。但是缺少的肉在抽象代码中,以及如何RecursiveIteratorDOMElement. 只需预览整个代码是如何被调用的(如前所述,您可以将其放入一个函数中,以便在您的代码中轻松访问它以进行调试。可能是一个名为 的函数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);
    }
}

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

因此,由于这很短,因此在扼杀它之后,只需继续父类DOMIteratorimplements RecursiveIterator文档只是为了使其工作):

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

  • RecursiveTreeIterator用于树状输出。
  • DOMRecursiveDecoratorStringAsCurrentDOMNode用于树中 a 的可视化
  • DOMRecursiveIteratorDOMIterator递归遍历 a 中的所有节点DOMDocument

就定义而言,这就是所需要的,但是我称之为抽象的代码仍然缺失。它只是某种简单的代理代码,它将相同的方法委托给另一个对象。一个相关的模式称为装饰器。但是,这只是代码,首先是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。希望这会有所帮助(即使很长,倒退也很直接)。

于 2011-12-25T22:50:17.870 回答
15

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

$dom->formatOutput = true;
echo $dom->saveXML();
于 2010-03-17T16:04:15.287 回答
10

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

print_r(simplexml_import_dom($entry)->asXML());
于 2010-12-16T22:18:29.083 回答
0

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

于 2009-03-26T05:02:08.017 回答
-1

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

于 2009-03-26T16:59:11.837 回答
-1

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

print_r(json_decode(json_encode($node), true));
于 2012-05-17T19:20:57.500 回答