Перебор элементов из DOMDocument :: getElementsByTagName () не работает - PullRequest
1 голос
/ 19 сентября 2019

У меня есть этот крошечный класс, который поможет мне заменить пользовательские теги действительными тегами 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 ]

1 голос
/ 19 сентября 2019

Кажется, проблема в том, что вы изменяете структуру документа, пытаясь выполнить итерации по нему.

Альтернативой является использование 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();
}
0 голосов
/ 19 сентября 2019

DOMNode::getElementsByTagName() возвращает "живой" результат.Элементы и список меняются по мере изменения документа.Вы изменяете документ так, что элементы в списке также меняются.Вот три способа избежать этой проблемы:

  1. Вы можете перебирать список в обратном порядке (используя цикл for).В большинстве случаев это будет означать, что вы изменяете только те части документа, которые не влияют на предыдущие элементы в списке узлов.

  2. Используйте методы, которые возвращают стабильный результат.DOMXpath::evaluate()DOMXpath::query()) возвращают стабильный список.Выражения Xpath также уменьшают количество кода, необходимого для выборки узлов.

  3. Преобразование списка узлов в массив с использованием iterator_to_array().Это создаст копию массива списков узлов с объектами узла в нем.Вы действительно использовали этот метод в своем примере кода.

0 голосов
/ 19 сентября 2019

Если я правильно помню, я имел дело с этим раньше, или вы можете использовать регрессивный цикл:

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