迭代 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" 结果。项目和列表随着文档的变化而变化。您修改了文档,因此列表中的项目也发生了变化。以下是避免该问题的树方法。
您可以反向迭代列表(使用 for 循环)。大多数情况下,这意味着您只更改不影响节点列表中先前元素的文档部分。
使用 return 稳定结果的方法。 DOMXpath::evaluate()
(和 DOMXpath::query()
)return 一个稳定列表。 Xpath 表达式也减少了获取节点所需的代码量。
使用iterator_to_array()
将节点列表转换为数组。这将创建节点列表的数组副本,其中包含节点对象。您实际上在示例代码中使用了该方法。
我有这个小 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" 结果。项目和列表随着文档的变化而变化。您修改了文档,因此列表中的项目也发生了变化。以下是避免该问题的树方法。
您可以反向迭代列表(使用 for 循环)。大多数情况下,这意味着您只更改不影响节点列表中先前元素的文档部分。
使用 return 稳定结果的方法。
DOMXpath::evaluate()
(和DOMXpath::query()
)return 一个稳定列表。 Xpath 表达式也减少了获取节点所需的代码量。使用
iterator_to_array()
将节点列表转换为数组。这将创建节点列表的数组副本,其中包含节点对象。您实际上在示例代码中使用了该方法。