迭代 DOMDocument::getElementsByTagName() 中的元素不起作用

Iterating over elements from DOMDocument::getElementsByTagName() doesn't work

我有这个小 class 可以帮助我用有效的 HTML 标签替换自定义标签。我的问题是,无论出于何种原因,它都只替换了第一个自定义标签。我的 猜测 是我在某处破坏了引用,但我不知道在哪里...向下滚动到此 post 的底部以查看实际结果和预期的输出。

<?php
class DomParser {

    protected $tags = [];
    protected $document;

    public function __construct($html) {
        $this->document = new DOMDocument();
        $this->document->loadXML($html);
    }

    public function addTag(string $name, callable $callable) {
        $this->tags[$name] = $callable;
    }

    public function replace() {
        foreach ($this->tags as $name => $callable) {
            $elements = $this->document->getElementsByTagName($name);

            foreach ($elements as $element) {
                $callable($element, $this->document);
            }
        }

        return $this->document->saveHTML();
    }
}

示例代码 运行 class:

<?php
require_once 'DomParser.php';
//require_once 'RenameTag.php';
//require_once 'Container.php';

$html = '<html>
    <container>
        <col>
            <p>
                <test attribute="test" attribute2="this">test<br />test2</test>
            </p>
        </col>
        <col>
            test col
        </col>
    </container>
    <container fluid="test"><test>dsdshsh</test></container>
</html>';

$parser = new DomParser($html);

//$parser->addTag('test', RenameTag::create('othertag'));
//$parser->addTag('container', Container::create());

$parser->addTag('col', function($oldTag) {
    $document = $oldTag->ownerDocument;

    $newTag = $document->createElement('div');
    $oldTag->parentNode->replaceChild($newTag, $oldTag);

    foreach (iterator_to_array($oldTag->childNodes) as $child) {
        $newTag->appendChild($oldTag->removeChild($child));
    }

    $newTag->setAttribute('class', 'col');
});

echo $parser->replace();

我得到这个结果:

<html>
        <container>
                <div class="col">
                        <p>
                                <test attribute="test" attribute2="this">test<br>test2</test>
                        </p>
                </div>
                <col>
        </container>
        <container fluid="true"><test>dsdshsh</test></container>
</html>

预期的输出应该是:

<html>
        <container>
                <div class="col">
                        <p>
                                <test attribute="test" attribute2="this">test<br>test2</test>
                        </p>
                </div>
                <div class="col">
                    test col
                </div>
        </container>
        <container fluid="test"><test>dsdshsh</test></container>
</html>

问题似乎是您在尝试迭代时更改了文档结构。

另一种方法是使用 XPath,它会获取自己的节点副本供您循环,更改相当小,但会给出您之后的输出...

public function replace() {
    $xp = new DOMXPath($this->document);

    foreach ($this->tags as $name => $callable) {
        $elements = $xp->query("//".$name);
        foreach ($elements as $element) {
            $callable($element, $this->document);
        }
    }

    return $this->document->saveHTML();
}

DOMNode::getElementsByTagName() return 是 "Live" 结果。项目和列表随着文档的变化而变化。您修改了文档,因此列表中的项目也发生了变化。以下是避免该问题的树方法。

  1. 您可以反向迭代列表(使用 for 循环)。大多数情况下,这意味着您只更改不影响节点列表中先前元素的文档部分。

  2. 使用 return 稳定结果的方法。 DOMXpath::evaluate()(和 DOMXpath::query())return 一个稳定列表。 Xpath 表达式也减少了获取节点所需的代码量。

  3. 使用iterator_to_array()将节点列表转换为数组。这将创建节点列表的数组副本,其中包含节点对象。您实际上在示例代码中使用了该方法。