遍历DOMDocument :: getElementsByTagName()中的元素不起作用

时间:2019-09-19 08:38:52

标签: php xml dom domdocument

我有一个很小的类,它将帮助我用有效的HTML标签替换自定义标签。我的问题是,无论出于何种原因,它仅替换第一个自定义标签。我的猜测是我在某处打破了参考,但是我不知道在哪里...向下滚动到这篇文章的底部以查看实际结果和预期的输出。

<?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();
    }
}

运行该类的示例代码:

<?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>

3 个答案:

答案 0 :(得分:1)

问题似乎是您在尝试遍历文档结构时正在更改文档结构。

另一种选择是使用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();
}

答案 1 :(得分:0)

如果我没记错的话,我之前已经处理过,或者可以使用回归循环:

public function replace() {

    foreach ($this->tags as $name => $callable) {
        $elements = $this->document->getElementsByTagName($name);
        $i = $elements->length - 1;
        while ($i > -1) {
            $element = $elements->item($i);
            $callable($element, $this->document);
            $i--;
        }
    }

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

答案 2 :(得分:0)

DOMNode::getElementsByTagName()返回“实时”结果。项目和列表随文档的更改而更改。您修改文档,以便列表中的项目也更改。这是避免问题的树方法。

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

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

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