Regex / DOMDocument – сопоставление и замена текста не в ссылке

Мне нужно найти и заменить все текстовые совпадения нечувствительным к регистру образом, если текст не находится в теге привязки – например:

<p>Match this text and replace it</p> <p>Don't <a href="/">match this text</a></p> <p>We still need to match this text and replace it</p> 

Поиск «соответствия этому тексту» заменяет только первый экземпляр и последний экземпляр.

[Изменить] Согласно комментарию Гордона, в этом случае может быть предпочтено использование DOMDocument. Я не совсем знаком с расширением DOMDocument и очень ценю некоторые базовые примеры для этой функциональности.

Вот безопасное решение UTF-8, которое работает не только с правильно отформатированными документами, но и с фрагментами документов.

Требуется mb_convert_encoding, так как loadHtml (), похоже, имеет ошибку с кодировкой UTF-8 (см. Здесь и здесь ).

Mb_substr обрезает тег тела с выхода, таким образом вы возвращаете исходный контент без дополнительной разметки.

 <?php $html = '<p>Match this text and replace it</p> <p>Don\'t <a href="/">match this text</a></p> <p>We still need to match this text and replace itŐŰ</p> <p>This is <a href="#">a link <span>with <strong>don\'t match this text</strong> content</span></a></p>'; $dom = new DOMDocument(); // loadXml needs properly formatted documents, so it's better to use loadHtml, but it needs a hack to properly handle UTF-8 encoding $dom->loadHtml(mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8")); $xpath = new DOMXPath($dom); foreach($xpath->query('//text()[not(ancestor::a)]') as $node) { $replaced = str_ireplace('match this text', 'MATCH', $node->wholeText); $newNode = $dom->createDocumentFragment(); $newNode->appendXML($replaced); $node->parentNode->replaceChild($newNode, $node); } // get only the body tag with its contents, then trim the body tag itself to get only the original content echo mb_substr($dom->saveXML($xpath->query('//body')->item(0)), 6, -7, "UTF-8"); 

Рекомендации:
1. найти и заменить ключевые слова гиперссылками в html-фрагменте, через php dom
2. Regex / DOMDocument – сопоставление и замена текста не по ссылке
3. Проблема php с русским языком
4. Почему DOM меняет кодировку?

Я читал десятки ответов в теме, поэтому мне жаль, если я забыл кого-нибудь (просьба прокомментировать это, и я добавлю ваше и в этом случае).

Спасибо за Гордона и по-прежнему за то, что он комментировал мой другой ответ .

Попробуй это:

 $dom = new DOMDocument; $dom->loadHTML($html_content); function preg_replace_dom($regex, $replacement, DOMNode $dom, array $excludeParents = array()) { if (!empty($dom->childNodes)) { foreach ($dom->childNodes as $node) { if ($node instanceof DOMText && !in_array($node->parentNode->nodeName, $excludeParents)) { $node->nodeValue = preg_replace($regex, $replacement, $node->nodeValue); } else { preg_replace_dom($regex, $replacement, $node, $excludeParents); } } } } preg_replace_dom('/match this text/i', 'IT WORKS', $dom->documentElement, array('a')); 

Это неуправляемый нерекурсивный подход, использующий предварительный обход дерева DOM.

  libxml_use_internal_errors(TRUE); $dom=new DOMDocument('1.0','UTF-8'); $dom->substituteEntities=FALSE; $dom->recover=TRUE; $dom->strictErrorChecking=FALSE; $dom->loadHTMLFile($file); $root=$dom->documentElement; $node=$root; $flag=FALSE; for (;;) { if (!$flag) { if ($node->nodeType==XML_TEXT_NODE && $node->parentNode->tagName!='a') { $node->nodeValue=preg_replace( '/match this text/is', $replacement, $node->nodeValue ); } if ($node->firstChild) { $node=$node->firstChild; continue; } } if ($node->isSameNode($root)) break; if ($flag=$node->nextSibling) $node=$node->nextSibling; else $node=$node->parentNode; } echo $dom->saveHTML(); 

libxml_use_internal_errors(TRUE); и 3 строки кода после $dom=new DOMDocument; должен иметь возможность обрабатывать любые искаженные HTML.

 $a='<p>Match this text and replace it</p> <p>Don\'t <a href="/">match this text</a></p> <p>We still need to match this text and replace it</p>'; echo preg_replace('~match this text(?![^<]*</a>)~i','replacement',$a); 

Отрицательный взгляд обеспечивает замену, только если следующий тег не является закрывающей ссылкой. Он отлично работает с вашим примером, хотя он не будет работать, если вы используете другие теги внутри своих ссылок.

Вы можете использовать PHP Simple HTML DOM Parser . Он похож на DOMDocument, но, на мой взгляд, его проще использовать. Вот альтернатива параллельно с решением Domodocument Netcoder :

 function replaceWithSimpleHtmlDom($html_content, $search, $replace, $excludedParents = array()) { require_once('simple_html_dom.php'); $html = str_get_html($html_content); foreach ($html->find('text') as $element) { if (!in_array($element->parent()->tag, $excludedParents)) $element->innertext = str_ireplace($search, $replace, $element->innertext); } return (string)$html; } 

Я только что профилировал этот код против моего решения DomDocument (ведьма печатает тот же самый результат), а DomDocument (не удивительно) быстрее (~ 4 мс против ~ 77 мс).

 <?php $a = '<p>Match this text and replace it</p> <p>Don\'t <a href="/">match this text</a></p> <p>We still need to match this text and replace it</p> '; $res = preg_replace("#[^<a.*>]match this text#",'replacement',$a); echo $res; ?> 

Этот способ работает. Надеюсь, вы хотите, чтобы на самом деле был чувствителен к регистру, поэтому нужно совместить с маленькой буквой.

Разбор HTML с регулярными выражениями представляет собой огромную проблему, и они могут очень легко стать слишком сложными и занимать массу памяти. Я бы сказал, что лучший способ сделать это:

 preg_replace('/match this text/i','replacement text'); preg_replace('/(<a[^>]*>[^(<\/a)]*)replacement text(.*?<\/a)/is',"$1match this text$3"); 

Если ваш replacement text – это то, что может возникнуть в противном случае, вы можете добавить промежуточный шаг с каким-то уникальным идентификатором.