Если он содержит HTML (обратите внимание, что это довольно надежное решение):
$string = '<p>foo<b>bar</b></p>';
$keyword = 'foo';
$dom = new DomDocument();
$dom->loadHtml($string);
$xpath = new DomXpath($dom);
$elements = $xpath->query('//*[contains(.,"'.$keyword.'")]');
foreach ($elements as $element) {
foreach ($element->childNodes as $child) {
if (!$child instanceof DomText) continue;
$fragment = $dom->createDocumentFragment();
$text = $child->textContent;
$stubs = array();
while (($pos = stripos($text, $keyword)) !== false) {
$fragment->appendChild(new DomText(substr($text, 0, $pos)));
$word = substr($text, $pos, strlen($keyword));
$highlight = $dom->createElement('span');
$highlight->appendChild(new DomText($word));
$highlight->setAttribute('class', 'highlight');
$fragment->appendChild($highlight);
$text = substr($text, $pos + strlen($keyword));
}
if (!empty($text)) $fragment->appendChild(new DomText($text));
$element->replaceChild($fragment, $child);
}
}
$string = $dom->saveXml($dom->getElementsByTagName('body')->item(0)->firstChild);
Результат:
<p><span class="highlight">foo</span><b>bar</b></p>
А с:
$string = '<body><p>foobarbaz<b>bar</b></p></body>';
$keyword = 'bar';
Вы получаете (разбито на несколько строк для удобства чтения):
<p>foo
<span class="highlight">bar</span>
baz
<b>
<span class="highlight">bar</span>
</b>
</p>
Остерегайтесь решений не-dom (например, regex
или str_replace
), поскольку выделение чего-то вроде "div" имеет тенденцию полностью разрушать ваш HTML ... Это только когда-либо "выделяет" строки в теле, никогда внутри тега ...
Редактировать Поскольку вам нужны результаты в стиле Google, вот один из способов сделать это:
function getKeywordStubs($string, array $keywords, $maxStubSize = 10) {
$dom = new DomDocument();
$dom->loadHtml($string);
$xpath = new DomXpath($dom);
$results = array();
$maxStubHalf = ceil($maxStubSize / 2);
foreach ($keywords as $keyword) {
$elements = $xpath->query('//*[contains(.,"'.$keyword.'")]');
$replace = '<span class="highlight">'.$keyword.'</span>';
foreach ($elements as $element) {
$stub = $element->textContent;
$regex = '#^.*?((\w*\W*){'.
$maxStubHalf.'})('.
preg_quote($keyword, '#').
')((\w*\W*){'.
$maxStubHalf.'}).*?$#ims';
preg_match($regex, $stub, $match);
var_dump($regex, $match);
$stub = preg_replace($regex, '\\1\\3\\4', $stub);
$stub = str_ireplace($keyword, $replace, $stub);
$results[] = $stub;
}
}
$results = array_unique($results);
return $results;
}
Хорошо, так что же это возвращает массив совпадений с $maxStubSize
словами вокруг него (а именно, до половины этого числа до и наполовину после) ...
Итак, с учетом строки:
<p>a whole
<b>bunch of</b> text
<a>here for</a>
us to foo bar baz replace out from this string
<b>bar</b>
</p>
Вызов getKeywordStubs($string, array('bar', 'bunch'))
приведет к:
array(4) {
[0]=>
string(75) "here for us to foo <span class="highlight">bar</span> baz replace out from "
[3]=>
string(34) "<span class="highlight">bar</span>"
[4]=>
string(62) "a whole <span class="highlight">bunch</span> of text here for "
[7]=>
string(39) "<span class="highlight">bunch</span> of"
}
Итак, тогда вы могли бы создать свой всплеск результатов, отсортировав список по strlen
и затем выбрав два самых длинных совпадения ... (при условии php 5.3 +):
usort($results, function($str1, $str2) {
return strlen($str2) - strlen($str1);
});
$description = implode('...', array_slice($results, 0, 2));
Что приводит к:
here for us to foo <span class="highlight">bar</span> baz replace out...a whole <span class="highlight">bunch</span> of text here for
Надеюсь, это поможет ... (я чувствую, что это немного ... раздутый ... Я уверен, что есть лучшие способы сделать это, но вот один из способов) ...