Zvýraznění výsledků vyhledávání

Školení, která pořádám

Když už si na stránkách dáme práci s implementací vyhledávání, je užitečné nalezená slova vhodně zvýraznit. Kdokoliv k tomu sice může použít vyhledávání dostupné přímo v prohlížeči, ale trocha pohodlí návštěvníky jistě potěší. Zvýrazňování se dá zajistit buď u každého vypisovaného textu zvlášť (na což je ale jednoduché zapomenout, znepřehledňuje to kód a nedá se to použít u statických textů vypisovaných mimo PHP) nebo společně pro celou stránku s využitím funkce ob_start.

<?php
function odzvyraznit($matches) {
    return preg_replace('~<span class="search-result">([^<]*)</span>~i', '\\1', $matches[0]);
}

function zvyraznit($text) {
    $search = preg_quote(htmlspecialchars($_GET["search"]), '~');
    if ($search) {
        $text = preg_replace("~$search~i", '<span class="search-result">\\0</span>', $text);
        // odstranění zvýrazňování z obsahu <option> a <textarea> a zevnitř značek a entit
        $span = '<span class="search-result">[^<]*</span>';
        $pattern = "~<(option|textarea)[\\s>]([^<]*$span)+|<([^>]*$span)+|&([^;]*$span)+~i";
        $text = preg_replace_callback($pattern, 'odzvyraznit', $text);
    }
    return $text;
}

// zvýrazňování pro každý vypisovaný text zvlášť
echo "<h1>" . zvyraznit($row["nadpis"]) . "</h1>\n";
echo zvyraznit($row["clanek"]);
// obdobně diskuse a spol., statické texty je nutné převést do PHP

// zvýrazňování pro celou stránku
ob_start('zvyraznit'); // umístit před začátek zvýrazňovaného textu
echo "<h1>$row[nadpis]</h1>\n";
echo $row["clanek"];
// diskuse, statické texty, ...
ob_end_flush(); // umístit za konec zvýrazňovaného textu - pokud odpovídá konci dokumentu, lze vynechat
?>

Pokud nepoužíváte zalomení konce řádků uvnitř značek (tedy např. <a\nhref="">), dá se funkci ob_start předat druhý parametr, který zajistí plynulé vypisování výstupu na straně serveru. Zvýrazňování si nerozumí s kódem uvnitř značky <script>. Ale vzhledem k tomu, že při tomto použití značky nejde HTML kód napsat zároveň zpětně a dopředně kompatibilní, je stejně lepší se mu vyhnout v jakémkoliv dokumentu a skripty vkládat vždy externě (důvod je ten, že HTML doporučuje kód uzavírat do HTML komentářů, ale XML parser používaný v XHTML může text uvnitř těchto komentářů bez varování odstranit).

Nejlepší na tom všem je, že tento způsob se dá použít nejen pro zvýraznění výsledků interního vyhledávání, ale dá se použít i pro zvýraznění hledání externím vyhledávačem (např. Googlem). Stačí využít proměnnou $_SERVER["HTTP_REFERER"] a proměnnou $search naplnit např. takto:

<?php
if (preg_match('~^http://www\\.google\\..*[?&]q=([^&]+)~', $_SERVER["HTTP_REFERER"], $matches)) {
    $search = urldecode($matches[1]);
    //~ $search = iconv('utf-8', 'windows-1250', $search); // převod kódování
    $search = preg_replace('~all[^ ]+:.*~', '', $search); // odstranění operátorů all*
    $search = preg_replace('~([^ ]+:|-)[^ ]*~', '', $search); // odstranění operátorů a nechtěných slov
    $search = preg_replace('~[\\~+()]|\\bOR\\b~i', '', $search); // odstranění +~() a OR
    $search = preg_quote(htmlspecialchars($search), '~'); // escapování pro preg
    $search = preg_replace('~ +~', '|', $search); // nahrazení mezer znakem pro nebo
    $search = preg_replace('~"([^"]*)"~e', 'str_replace("|", "\\\\\\\\s+", "\\1")', $search); // zpracování uvozovek
    $search = "\\b($search)\\b"; // hledají se pouze celá slova
} else {
    $search = preg_quote(htmlspecialchars($_GET["search"]), '~');
}
?>

Krapánek se to zesložitilo, ale mělo by to postihovat většinu funkcí, které Google pro vyhledávání nabízí. Obdobně by se zvýraznění výsledků vyhledávání dalo samozřejmě udělat i pro další vyhledávače.

Jakub Vrána, Řešení problému, 16.3.2005, diskuse: 15 (nové: 0)

Diskuse

Pachollini:

Pěkné, ale nemá to náhodou Google patentované? Co když po mně pak budou chtít 2$ za každou zobrazenou stránku s takovým zvýrazněním ;-)

lukas:

Jsme v CR, ne v USA.

Daniel.Peder@Iinfoset.cz:

to samé ale univerzálně použitelné s JS je tady:
http://marker.infoset.com

Tomáš:

Kdyby to fungovalo ještě v Opeře a v Mozille, bylo by to naprosto super. A ono to určitě jde, jelikož to samotný google při vyhledávání v gmailu dělá.

Hakuna:

No a nevíte náhodou o někám skriptu, který mi rozparsuje nějakou stránku s výsledky, např. vyhledávání, a potom pomoci REGEXP mi to vyhazi vysledky na moji stranku...? Napriklad pomoci preg_match_all ... prosím o radu a pomoc.

Rozi:

už je to dávno ale v diskuzi na php.net sem našel:

preg_replace("'(?!<.*?)$search(?![^<>]*?>)'si", '<span class="search-result">$0</span>', $text);

zvýrazní text všade krom html tagu ... odpadá odzvýraznění

ikona Jakub Vrána OpenID:

Řekl bych, že (?!<.*?) ve výrazu nedává smysl - znamená to, že $search nesmí začínat <, což ani nemůže, protože je ošetřený funkcí htmlspecialchars(). Možná měl původní autor na mysli lookbehind aserci, ale ta musí mít konstantní délku, navíc i tak by to bylo zbytečné. Nutnost ošetřit entity a vnitřky značek <option> a <textarea> zůstává.

Nicméně obrat (?![^<>]*?>) je pěkný.

Jakub Rychlý:

Jen pro úplnost.

Z php manuálu:
<?php
$pattern
= '(>[^<]*)('. quotemeta($_GET['search']) .')';
$replacement = '\\1<span class="search">\\2</span>';
$body = eregi_replace($pattern, $replacement, $body);
?>

Zvýrazní řetězce jen mezi jednotlivými tagy. Tedy nevkládá zvýraznění doprostřed tagu.

ikona Jakub Vrána OpenID:

Tento kód trpí několika neduhy:

1. Nezvýrazňuje text před první značkou (což může být potřeba, když nezvýrazňujeme celý dokument, ale např. jen jeho obsahovou část.

2. Zvýraznění dovoluje umístit i do značek <option> a <textarea>, kde je nepřípustné.

3. Zvýraznění dovoluje umístit i do entit, kde je také nepřípustné.

Všechny tyto problémy použitá funkce odzvyraznit() řeší.

Nechapu :-D:

jste blazni lidi... tohle pochopit tak si piskam =-O :-D jdu sprtat no

ikona kriznik:

zdravim, sice se to primo netyka tohohle problemu, ale nenasel jsem lepsi misto kde se zeptat

resim replace ve vztahu k cestine a ne a ne ;(
napr. hledam "VST" a nahrazuju ho <span class>VST</span> (treba)
v message mam "vstřikování, vstrikovani, vst, vstš, vsts"

bohuzel se nahradi prvni, treti a ctvrte slovo > a melo by se resit pouze treti
jde tomu nejak zabranit, aby php nevidelo diakritiku jako dve ruzny?
vsechno je UTF-8, diky za tipy

<?php
$message
= str_replace('\"', '"', mb_substr(@preg_replace('#(\>(((?>([^><]+|(?R)))*)\<))#se', "@preg_replace(\$forbiden, \$allowed, '\\0')", '>' . $message . '<'), 1, -1));
?>

lolek57:

Ahoj, použil jsem váš příklad a je to opravdu super, děkuji moc, jen bych se chtěl zeptat, když používám GET ve vyhledávání a zadám k vyhledání dvě slova (pes kočka), tak to udělá toto: pes+kočka. Ale poté už to nezvýrazní nic, když zadám jen jedno slovo tak ano.

Děkuji předem za odpověď!

Online hry:

Jak použít fci preg_replace v případě, že chci nahrazovat např. ",< apod.? Vypisuje mi chybu No ending delimiter. Děkuji.

ikona Karel Dytrych:

preg_replace('/</', 'cim nahradit', kde);

ale pokud je to jednoducha nahrada znak za znak, tak zkuste radsi str_replace();

Paja:

Jak je prosím možné, uvedený kód výše, upravit, aby zvýraznil všechny vyhledávané výrazy.

Např.: Dnes byl krásný den.

Uvedený příklady zvýrazní pouze např. "krásný den". Ale potřeboval bych zvýraznit př.: "dnes den". Prostě všechna slova z vyhledávání.

Děkuji.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.