Я пытаюсь написать функцию 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
никогда не будет соответствовать.
Что касается этой проблемы, я весьма озадачен и не знаю, как ее решить.
Я чувствую, что мне нужно как-то выполнить все операции поиска по исходной строке отдельно, а затем как-то объединить их в конце, но опять же, я заблудился, как это сделать.
Возможно, есть и лучшее решение, но я не знаю, что это будет.
Будем весьма благодарны за любые советы или указания о том, как решить одну или обе эти проблемы.
Спасибо.
В этом случае я думаю, что проще использовать 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
собирается потерпеть неудачу, так как есть разметка в пути: \
Я не могу придумать, как обойтись, боюсь.
Не изменяйте исходную строку и не сохраняйте совпадения в отдельном массиве, либо начинайте с нечетных и заканчивая четными элементами, либо сохраняйте их в записях (массивах из двух элементов).
После поиска по нескольким ключевым словам, вы получите несколько массивов с совпадениями. Итак, теперь задача состоит в том, как объединить два списка сегментов, создавая сегменты, которые покрывают области. Поскольку списки отсортированы, это тривиальная задача, которую можно решить за O (n) раз.
Затем просто вставьте маркеры выделения в позиции, записанные в результирующем массиве.