这个答案可能有点晚了,但我喜欢你的问题!
PHP 没有任何内置功能可以直接解决您的问题,因此没有 XML 转储之类的东西。
但是,PHP 的 RecursiveTreeIteratorDocs 与您的输出非常接近:
\-<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 必须提供的迭代器功能。我之所以这么写,是因为它有助于解决常见问题,并使彼此并不真正相关的组件能够相互协作。例如,RecursiveTreeIteratorDocs 是内置的,它可以与您提供的任何内容一起使用(您甚至可以对其进行配置)。但是,它需要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 函数,它只返回 DOMNode 中的 tagName 和 bracketsWikipedia (<>) 和 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);
}
}
这是一个非常短的类,只有两个函数。我在这里作弊,因为这个类也是从另一个类扩展而来的。但正如所写,这是倒退的,所以这个类实际上负责递归:hasChildren 和 getChildren。显然,即使这两个函数也没有太多代码,它们只是将“问题”(hasChildren?getChildren?)映射到标准的DOMNode。如果一个节点有子节点,好吧,说是或者只是返回它们(这是一个迭代器,以迭代器的形式返回它们,因此是new self())。
因此,由于这很短,因此在窒息之后,只需继续父类DOMIterator(implements RecursiveIteratorDocs 只是为了使其工作):
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 用于输出。所以在它的 constructor 中创建了一个 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 或将它们与您可能正在使用的自动加载器集成。 Full code at once.
如果需要编辑输出方式,可以编辑DOMRecursiveDecoratorStringAsCurrent或者在xmltree_dump里面修改RecursiveTreeIterator的配置。希望这会有所帮助(即使很长,backwards 也很直接)。