Как выделить текст, соответствующий поисковому запросу, на веб-странице

Я пытаюсь написать функцию PHP, которая принимает текст для отображения на веб-странице, а затем на основе введенных поисковых терминов выделяет соответствующие части текста. К сожалению, у меня есть пара вопросов.
Чтобы лучше объяснить две проблемы, которые у меня возникли, давайте представим, что следующая безобидная строка находится в поиске и будет отображаться на веб-странице:

My daughter was born on January 11, 2011.

Моя первая проблема заключается в том, что если введено более одного поискового термина, любой текст-заполнитель, который я использую для обозначения начала и конца любых совпадений для первого термина, может затем соответствовать второму.
Например, в настоящее время я использую следующие разделительные строки, чтобы отметить начало и конец совпадения (на котором я использую preg_replace функция в конце функции, чтобы превратить разделители в HTML span теги):

'#####highlightStart#####'
'#####highlightEnd#####'

Проблема в том, если я делаю поиск, как 2011 light, затем 2011 будет соответствовать первым, давая мне:

My daughter was born on January 11, #####highlightStart#####2011#####highlightEnd#####.

На котором когда light ищется, оно будет соответствовать слову light в обоих #####highlightStart##### а также #####highlightEnd#####что я не хочу.

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

Мой второй вопрос касается того, как обрабатывать перекрывающиеся совпадения.
Например, с той же строкой My daughter was born on January 11, 2011., если введенный поиск Jan anuar, затем Jan будет соответствовать первым, давая мне:

My daughter was born on #####highlightStart#####Jan#####highlightEnd#####uary 11, 2011.

И поскольку текст-разделитель теперь является частью строки, второй поисковый термин, anuar никогда не будет соответствовать.

Что касается этой проблемы, я весьма озадачен и не знаю, как ее решить.
Я чувствую, что мне нужно как-то выполнить все операции поиска по исходной строке отдельно, а затем как-то объединить их в конце, но опять же, я заблудился, как это сделать.
Возможно, есть и лучшее решение, но я не знаю, что это будет.

Будем весьма благодарны за любые советы или указания о том, как решить одну или обе эти проблемы.
Спасибо.

0

Решение

В этом случае я думаю, что проще использовать str_replace (хотя это не будет идеально).

Предполагая, что у вас есть массив терминов, которые вы хотите выделить, я назову это $aSearchTerms ради аргумента … и что упаковка выделенных терминов в HTML5 <mark> тег приемлем (для разборчивости вы указали, что он идет на веб-странице, и его легко strip_tags() из ваших условий поиска):

$aSearchTerms = ['Jan', 'anu', 'Feb', '11'];
$sinContent = "My daughter was born on January 11, 2011.";

foreach($aSearchTerms as $sinTerm) {
$sinContent = str_replace($sinTerm, "<mark>{$sinTerm}</mark>", $sinContent);
}

echo $sinContent;
// outputs: My d<mark>au</mark>ghter was born on <mark>Jan</mark>uary <mark>11</mark>, 20<mark>11</mark>.

Это не идеально, так как при использовании данных в этом массиве первый проход изменится January в <mark>Jan</mark>uary что значит anu больше не будет совпадать в Jанаичных — что-то вроде этого, однако, покроет самый Потребности в использовании.


РЕДАКТИРОВАТЬ

Оки — я не уверен на 100%, что это нормально, но я использовал совершенно другой подход, посмотрев на ссылку @AlexAtNet:

https://stackoverflow.com/a/3631016/886824

Что я сделал, так это посмотрел на точки в строке, где поисковый термин был найден численно (индексы), и построил массив начальных и конечных индексов, где <mark> а также </mark> теги будут введены.

Затем, используя ответ выше, объедините эти начальный и конечный индексы вместе — это покрывает проблему совпадения совпадений.

Затем я зациклил этот массив, разрезал исходную строку на подстроки и склеил ее обратно, вставив <mark> а также </mark> теги в соответствующих точках (на основе индексов). Это должно охватить вашу вторую проблему, поэтому у вас нет замены строк, заменяющих замены строк.

Полный код выглядит так:

<?php
$sContent = "Captain's log, January 11, 2711 - Uranus";
$ainSearchTerms = array('Jan', 'asduih', 'anu', '11');

//lower-case it for substr_count
$sContentForSearching = strtolower($sContent);

//array of first and last positions of the terms within the string
$aTermPositions = array();

//loop through your search terms and build a multi-dimensional array
//of start and end indexes for each term
foreach($ainSearchTerms as $sinTerm) {

//lower-case the search term
$sinTermLower = strtolower($sinTerm);

$iTermPosition = 0;
$iTermLength = strlen($sinTermLower);
$iTermOccursCount = substr_count($sContentForSearching, $sinTermLower);

for($i=0; $i<$iTermOccursCount; $i++) {

//find the start and end positions for this term
$iStartIndex = strpos($sContentForSearching, $sinTermLower, $iTermPosition);
$iEndIndex = $iStartIndex + $iTermLength;
$aTermPositions[] = array($iStartIndex, $iEndIndex);

//update the term position
$iTermPosition = $iEndIndex + $i;
}
}

//taken directly from this answer https://stackoverflow.com/a/3631016/886824
//just replaced $data with $aTermPositions
//this sorts out the overlaps so that 'Jan' and 'anu' will merge into 'Janu'
//in January - whilst still matching 'anu' in Uranus
//
//This conveniently sorts all your start and end indexes in ascending order
usort($aTermPositions, function($a, $b)
{
return $a[0] - $b[0];
});

$n = 0; $len = count($aTermPositions);
for ($i = 1; $i < $len; ++$i)
{
if ($aTermPositions[$i][0] > $aTermPositions[$n][1] + 1)
$n = $i;
else
{
if ($aTermPositions[$n][1] < $aTermPositions[$i][1])
$aTermPositions[$n][1] = $aTermPositions[$i][1];
unset($aTermPositions[$i]);
}
}

$aTermPositions = array_values($aTermPositions);

//finally chop your original string into the bits
//where you want to insert <mark> and </mark>
if($aTermPositions) {
$iLastContentChunkIndex = 0;
$soutContent = "";

foreach($aTermPositions as $aChunkIndex) {
$soutContent .= substr($sContent, $iLastContentChunkIndex, $aChunkIndex[0] - $iLastContentChunkIndex)
. "<mark>" . substr($sContent, $aChunkIndex[0], $aChunkIndex[1] - $aChunkIndex[0]) . "</mark>";

$iLastContentChunkIndex = $aChunkIndex[1];
}

//... and the bit on the end
$soutContent .= substr($sContent, $iLastContentChunkIndex);
}

//this *should* output the following:
//Captain's log, <mark>Janu</mark>ary <mark>11</mark>, 27<mark>11</mark> - Ur<mark>anu</mark>s
echo $soutContent;

Неизбежный гоча!
Использование этого в контенте, который уже является HTML, может ужасно потерпеть неудачу.

Учитывая строку.

In <a href="#">January</a> this year...

Поиск / знак Jan вставит <mark>/</mark> вокруг «Ян», что хорошо. Однако поисковая марка чего-то вроде In Jan собирается потерпеть неудачу, так как есть разметка в пути: \

Я не могу придумать, как обойтись, боюсь.

1

Другие решения

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

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

Затем просто вставьте маркеры выделения в позиции, записанные в результирующем массиве.

1

По вопросам рекламы [email protected]