Игнорировать теги html в preg_replace

Как игнорировать теги html в этом preg_replace. У меня есть функция foreach для поиска, поэтому, если кто-то ищет «span apple», preg_replace также применяет span к span и html breaks:

preg_replace("/($keyword)/i","<span class=\"search_hightlight\">$1</span>",$str); 

Заранее спасибо!

Я предполагаю, что вы должны сделать свою функцию на основе DOMDocument и DOMXPath, а не использовать регулярные выражения. Даже те, которые являются достаточно мощными, вы сталкиваетесь с проблемами, подобными тем, которые вы описываете, которые не всегда (всегда) легко и надежно решать с помощью регулярных выражений.

Общее высказывание: Не разбирайте HTML с регулярными выражениями.

Правильное правило – иметь в виду и хотя и с любым правилом, это не всегда применяется, стоит подумать об этом.

XPath позволяет вам находить все тексты, содержащие условия поиска только в текстах, игнорируя все элементы XML.

Затем вам нужно только обернуть эти тексты в <span> и все готово.

Изменить: Наконец-то некоторый код;)

Сначала он использует xpath для поиска элементов, содержащих текст поиска. Мой запрос выглядит так, это может быть написано лучше, я не супер xpath pro:

 '//*[contains(., "'.$search.'")]/*[FALSE = contains(., "'.$search.'")]/..' 

$search содержит текст для поиска, не содержащий никакого символа " (цитата)» (это сломает его, см. « Очистка / дезинфекция атрибутов xpath для обходного пути, если вам нужны кавычки»).

В этом запросе будут возвращены все родители, которые содержат текстовые поля, которые собираются вместе, будут строкой, содержащей ваш поисковый запрос.

Поскольку такой список нелегко обрабатывать далее как-есть, я создал класс TextRange который представляет список узлов DOMText . Полезно выполнять строковые операции в списке текстовых полей, как если бы они были одной строкой.

Это базовый скелет рутины:

 $str = '...'; # some XML $search = 'text that span'; printf("Searching for: (%d) '%s'\n", strlen($search), $search); $doc = new DOMDocument; $doc->loadXML($str); $xp = new DOMXPath($doc); $anchor = $doc->getElementsByTagName('body')->item(0); if (!$anchor) { throw new Exception('Anchor element not found.'); } // search elements that contain the search-text $r = $xp->query('//*[contains(., "'.$search.'")]/*[FALSE = contains(., "'.$search.'")]/..', $anchor); if (!$r) { throw new Exception('XPath failed.'); } // process search results foreach($r as $i => $node) { $textNodes = $xp->query('.//child::text()', $node); // extract $search textnode ranges, create fitting nodes if necessary $range = new TextRange($textNodes); $ranges = array(); while(FALSE !== $start = strpos($range, $search)) { $base = $range->split($start); $range = $base->split(strlen($search)); $ranges[] = $base; }; // wrap every each matching textnode foreach($ranges as $range) { foreach($range->getNodes() as $node) { $span = $doc->createElement('span'); $span->setAttribute('class', 'search_hightlight'); $node = $node->parentNode->replaceChild($span, $node); $span->appendChild($node); } } } 

Для моего примера XML:

 <html> <body> This is some <span>text</span> that span across a page to search in. and more text that span</body> </html> 

Он дает следующий результат:

 <html> <body> This is some <span><span class="search_hightlight">text</span></span><span class="search_hightlight"> that span</span> across a page to search in. and more <span class="search_hightlight">text that span</span></body> </html> 

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

Здесь вы найдете полный код: http://codepad.viper-7.com/U4bxbe (включая класс TextRange который я взял из примера ответов).

Он не работает должным образом в кодексе viper из-за более старой версии LIBXML, которую использует сайт. Он отлично подходит для моей LIBXML-версии 20707. Я создал связанный с этим вопрос по этой проблеме: порядок результата запроса XPath .

Примечание предупреждения. В этом примере используется двоичный поиск строк ( strpos ) и соответствующие смещения для разделения DOMText::splitText функции DOMText::splitText . Это может привести к неправильным смещениям, поскольку для функций требуется смещение символов UTF-8. Правильный метод – использовать mb_strpos для получения значения на основе UTF-8 .

В любом случае этот пример работает, потому что он использует только US-ASCII который имеет одинаковые смещения как UTF-8 для данных примера.

Для реальной ситуации строка $search должна быть кодирована в кодировке UTF-8, а вместо strpos следует использовать strpos :

  while(FALSE !== $start = mb_strpos($range, $search, 0, 'UTF-8'))