PHP Подсветка текста Функция поиска - PullRequest
3 голосов
/ 19 декабря 2011

У меня есть функция подсветки PHP, которая выделяет некоторые слова жирным шрифтом.

Ниже приведена функция, и она прекрасно работает, за исключением случаев, когда массив: $ words содержит одно значение: b

Например, кто-то ищет: jessie j price tag feat bob

Это будет иметь следующие записи в массиве $ words: jessie, j, цена, тег, feat, b, o, b

Когда появляется «b», вся моя функция работает неправильно, и она отображает целую кучу неправильных HTML-тегов.Конечно, я могу удалить любые значения 'b' из массива, но это не идеально, так как выделение не работает должным образом с определенными запросами.

Этот пример сценария:

    function highlightWords2($text, $words)
    {
        $text =  ($text);
        foreach ($words as $word)
        {       
            $word = preg_quote($word);

            $text = preg_replace("/\b($word)\b/i", '<b>$1</b>', $text);

        }
        return $text;
    }


$string = 'jessie j price tag feat b o b';

$words = array('jessie','tag','b','o','b');

echo highlightWords2($string, $words);

Выводит:

<<<b>b</b>><b>b</b></<b>b</b>>>jessie</<<b>b</b>><b>b</b></<b>b</b>>> j price <<<b>b</b>><b>b</b></<b>b</b>>>tag</<<b>b</b>><b>b</b></<b>b</b>>> feat <<b>b</b>><b>b</b></<b>b</b>> <<b>b</b>>o</<b>b</b>> <<b>b</b>><b>b</b></<b>b</b>>

И это происходит только потому, что в массиве есть "b".

Ребята, можете ли вы увидеть что-нибудь, что я мог бы изменить, чтобы заставить его работатьправильно?

Ответы [ 3 ]

5 голосов
/ 19 декабря 2011

Ваша проблема в том, что когда ваша функция проходит и ищет все b, выделенные жирным шрифтом, она видит жирные теги, а также пытается выделить их жирным шрифтом.

@ symcbean был близок, но забыл одну вещь.

$string = 'jessie j price tag feat b o b';
$words = array('jessie','tag','b','o','b');

print hl($string, $words);

function hl($inp, $words)
{
  $replace=array_flip(array_flip($words)); // remove duplicates
  $pattern=array();
  foreach ($replace as $k=>$fword) {
     $pattern[]='/\b(' . $fword . ')(?!>)\b/i';
     $replace[$k]='<b>$1</b>';
  }
  return preg_replace($pattern, $replace, $inp);
}

Видите ли вы это добавленное "(?!>)", Которое является отрицательным утверждением в перспективе, в основном оно говорит, что совпадать только еслиза строкой не следует ">", что означает, что открывающий жирный шрифт и закрывающий жирный тэг.Обратите внимание, что я проверяю только «>» после строки, чтобы исключить открывающий и закрывающий жирный тег, так как поиск его в начале строки не поймает закрывающий жирный тег.Приведенный выше код работает точно так, как ожидалось.

2 голосов
/ 19 декабря 2011

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

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

Для таких вещей регулярные выражения весьма ограничены.Вместо этого используйте анализатор HTML, в PHP это, например, DOMDocument.С помощью парсера HTML можно искать только внутри текстовых элементов HTML (а не других вещей, таких как теги, атрибуты и комментарии).

Вы найдете подсветку для текста в моем предыдущем ответе с подробным описанием, как это работает.Вопрос Игнорировать теги html в preg_replace , и он очень похож на ваш вопрос, так что, вероятно, этот фрагмент полезен, он использует <span> вместо <b> тегов:

$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);
        }
    }
}

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

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

0 голосов
/ 19 декабря 2011

Если бы это был я, я бы использовал javascript.

Но используя PHP, поскольку проблема заключается только в дублировании записей в поиске, просто удалите их, также вы можете запустить preg_replace только один раз, а ненесколько раз ....

$string = 'jessie j price tag feat b o b';
$words = array('jessie','tag','b','o','b');

print hl($string, $words);

function hl($inp, $words)
{
  $replace=array_flip(array_flip($words)); // remove duplicates
  $pattern=array();
  foreach ($replace as $k=>$fword) {
     $pattern[]='/\b(' . $fword . ')\b/i';
     $replace[$k]='<b>$1<b>';
  }
  return preg_replace($pattern, $replace, $inp);
}
...