解决PHP DOMDocument修改导致后续修改错误的问题

解决PHP DOMDocument修改导致后续修改错误的问题

本文旨在解决在使用phpdomDocument和XPath修改DOM时,由于首次修改导致后续修改出现Call to a member function splitText() on bool错误的问题。通过分析问题原因,提出了一种通过反向处理匹配项来避免修改位置偏移的有效解决方案,并提供了相应的代码示例。

在使用PHP的DOMDocument和DOMXPath处理html内容时,经常需要查找并修改特定的文本节点。一个常见的需求是将某些关键词或短语用<span>标签包裹起来,以便进行样式控制或特殊处理。然而,如果在循环中修改DOM结构,可能会遇到一个棘手的问题:首次修改后,后续的查找和修改操作可能会因为DOM结构的改变而出现错误。具体表现为Call to a member function splitText() on bool。

问题分析

该错误通常发生在尝试对已经因为之前的DOM操作而失效的节点执行splitText()方法时。splitText()方法用于将一个文本节点分割成两个节点。当DOM结构发生变化时,原有的节点对象可能不再有效,或者其在DOM树中的位置已经改变,导致splitText()方法无法正确执行。

立即学习PHP免费学习笔记(深入)”;

解决方案:反向处理匹配项

一个有效的解决方案是反向处理匹配项。这意味着,不要从前往后依次替换找到的匹配项,而是从后往前进行替换。这样,每次修改只会影响当前位置之前的DOM结构,而不会影响尚未处理的匹配项的位置和偏移量。

代码示例

下面是修改后的代码示例,展示了如何反向处理匹配项来避免错误:

/**  * Automatically wrap various forms of CCJM in a class for branding purposes  *  * @param string $content  * @return string  */ function ccjm_branding_filter(string $content): string {     if (! (is_admin() && ! wp_doing_ajax()) && $content) {         $DOM = new DOMDocument();          /**          * Use internal errors to get around HTML5 warnings          */         libxml_use_internal_errors(true);          /**          * Load in the content, with proper encoding and an `<html>` wrapper required for parsing          */         $DOM->loadHTML("<?xml encoding='utf-8' ?><html>{$content}</html>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);          /**          * Clear errors to get around HTML5 warnings          */         libxml_clear_errors();          /**          * Initialize XPath          */         $XPath = new DOMXPath($DOM);          /**          * Retrieve all text nodes, except those within scripts          */         $text = $XPath->query("//text()[not(parent::script)]");          foreach ($text as $node) {             /**              * Find all matches, including offset              */             preg_match_all("/(C.? ?C.?(?:JM| Johnson (?:&|&|&|and) Malhotra)(?: Engineers, LTD.?|, P.?C.?)?)/i", $node->textContent, $matches, PREG_OFFSET_CAPTURE);              /**              * Wrap each match in appropriate span              */             $group = array_reverse($matches[0]); // 反转匹配项数组             foreach ($group as $key => $match) {                 /**                  * Determine the offset and the length of the match                  */                 $offset = $match[1];                 $length = strlen($match[0]);                  /**                  * Isolate the match and what comes after it                  */                 $word  = $node->splitText($offset);                 $after = $word->splitText($length);                  /**                  * Create the wrapping span                  */                 $span = $DOM->createElement("span");                 $span->setAttribute("class", "__brand");                  /**                  * Replace the word with the span, and then re-insert the word within it                  */                 $word->parentNode->replaceChild($span, $word);                 $span->appendChild($word);             }         }          /**          * Save changes, remove unneeded tags          */         $content = implode(array_map([$DOM->documentElement->ownerDocument, "saveHTML"], iterator_to_array($DOM->documentElement->childNodes)));     }      return $content; } 

关键的修改在于以下这行代码:

$group = array_reverse($matches[0]); // 反转匹配项数组

通过使用array_reverse()函数,我们将匹配项数组的顺序反转,从而确保从后往前处理匹配项。

注意事项

  • 编码问题: 在加载HTML内容时,务必指定正确的编码,避免出现乱码问题。
  • HTML结构: 确保加载的HTML内容具有良好的结构,例如包含<html>和<body>标签。
  • 性能优化: 对于大型HTML文档,频繁的DOM操作可能会影响性能。可以考虑使用更高效的字符串处理方法,或者使用缓存机制来减少DOM操作的次数。

总结

在使用PHP的DOMDocument和DOMXPath修改DOM结构时,需要特别注意DOM结构的变化对后续操作的影响。通过反向处理匹配项,可以有效地避免因DOM结构改变而导致的错误,确保代码的稳定性和可靠性。此外,还需要关注编码问题、HTML结构和性能优化等方面,以提高代码的质量和效率。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享