这个答案可能有点晚了,但我喜欢你的问题!
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";
}
(您可以将其包装在一个函数中,因此您只需要调用该函数)
即使这看起来很简单,但有一个警告:它需要一个RecursiveIterator
over the DOMDocument
tree。由于 PHP 无法猜测您需要什么,因此需要将其包装到代码中。正如所写的那样,我发现这个问题很有趣(显然您没有要求 XML 输出),所以我编写了一些小代码来提供所需的递归迭代器。所以我们开始吧。
首先,您可能不熟悉 PHP 中的迭代器。这与使用我将展示的代码无关,因为我将向后执行,但是,每当您考虑自己运行某些代码时,请考虑是否可以利用 PHP 必须提供的迭代器功能. 我之所以这么写,是因为它有助于解决常见问题,并使彼此并不真正相关的组件能够相互协作。例如,RecursiveTreeIterator
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
只返回括号Wikipedia ( )中tagName
的 a和 textnodes 的文本的函数。这就是你的输出所需要的,所以这就是编码所需的一切。DOMNode
<>
实际上,除非您也拥有抽象代码,否则这不起作用,但是为了可视化代码的使用方式(最有趣的部分),让我们查看它:
$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
文档只是为了使其工作):
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
用于树状输出。
DOMRecursiveDecoratorStringAsCurrent
DOMNode
用于树中 a 的可视化
DOMRecursiveIterator
并DOMIterator
递归遍历 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
。希望这会有所帮助(即使很长,倒退也很直接)。