这个回答可能晚了一些,但我很喜欢你的问题!
PHP没有内置直接解决您的问题的方法,因此没有XML dump或类似的东西。
但是,PHP有
RecursiveTreeIterator
Docs,它非常接近您的输出:
\-<html>
\-<body>
\-<p>
\-Hello World
(it将会更加美观,如果你的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
提供 <tag>
(如果它们是标签(元素))并且只提供 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
函数,它只返回
bracketsWikipedia(
<>
)中一个
DOMNode
的
tagName
和textnodes的文本,就是输出所需的内容,所以编码只需要这些。
实际上,除非您也拥有抽象代码,否则这个程序无法正常工作,但为了可视化代码如何使用(最有趣的部分),让我们来看一下:
$iterator = new DOMRecursiveDecoratorStringAsCurrent($iterator);
$tree = new RecursiveTreeIterator($iterator);
foreach($tree as $key => $value)
{
echo $value . "\n";
}
由于是倒序操作,因此我们目前已经根据要由RecursiveTreeIterator
显示的DOMNode
指定了输出。目前为止还好,很容易理解。但缺失的关键在于抽象代码内部以及如何创建一个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);
}
}
这是一个非常简短的类,只有两个函数。在这里我有点作弊,因为这个类也继承自另一个类。但是按照原文写的话,这是不正确的,所以这个类实际上负责递归:
hasChildren
和
getChildren
。显然,即使这两个函数没有太多的代码,它们也只是将“问题”(
hasChildren
?
getChildren
?)映射到标准的
DOMNode
上。如果一个节点有子节点,那么就回答“是”或者返回它们(而且这是一个迭代器,在迭代器的形式下返回它们,因此使用
new self()
)。
因为这很简短,所以在处理完后,可以继续处理父类
DOMIterator
(
implements 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
的基本迭代器,它只需要一个
DOMNode
或
DOMNodeList
来进行迭代。也许这听起来有点多余,因为DOM已经支持了
DOMNodeList
,但它不支持
RecursiveIterator
,而我们已经知道我们需要一个
RecursiveTreeIterator
来输出。所以在它的
构造函数中,创建了一个
Iterator
并传递给父类,这个父类又是抽象代码。当然,我马上就会揭示这段代码。由于这是反向的,让我们回顾一下到目前为止做了什么:
- 使用
RecursiveTreeIterator
实现树形输出。
- 使用
DOMRecursiveDecoratorStringAsCurrent
将DOMNode
可视化为树形结构。
- 使用
DOMRecursiveIterator
和DOMIterator
递归迭代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
或更改
xmltree_dump
内部的
RecursiveTreeIterator
配置。希望这对您有所帮助(即使相当冗长,
反着说也是相当间接的)。
Catchable fatal error: Argument 1 passed to IteratorIterator::__construct() must implement interface Traversable, instance of DOMNodeList given
的错误,我做错了什么?我从gist中获取了代码,并在底部的usage
块中使用了最终示例... - cwd$nodeOrNodes = new IteratorIterator($nodeOrNodes);
吗? - hakre