Как игнорировать теги 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'))