Hlavní navigace

PHP okénko: Zkrácení textu s XHTML značkami

Jakub Vrána

Dnešní PHP okénko ukazuje, jak se dá v PHP zkrátit řetězec obsahující XHTML značky s využitím rozšíření Tidy i bez něj, a naznačuje, jak by se kód musel upravit pro zpracování tolerantnějšího HTML kódu.

Pokud chceme vypsat pouze začátek nějakého textu, není v PHP nic jednoduššího:

<?php
echo substr($text, 0, $limit);
?> 

Pokud text nechceme ukončovat uprostřed slova, také to není dvakrát velká věda:

<?php
if (strlen($text) <= $limit) {
    echo $text;
} else {
    $text = substr($text, 0, $limit+1);
    $pos = strrpos($text, " "); // v PHP 5 by se dal použít parametr offset
    echo substr($text, 0, ($pos ? $pos : -1)) . "...";
}
?> 

Funkce strrpos vrátí v případě nenalezení podřetězce false a v případě nalezení na samém začátku řetězce 0, mezi čímž je obvykle potřeba pomocí operátoru === rozlišovat, my ale oba případy ošetříme stejně – vypsáním řetězce zkráceného přesně na požadovanou délku.

Co ale dělat v případě, kdy $text obsahuje HTML značky? Pokud nám na nich nezáleží, dá se jednoduše použít funkce strip_tags. Pokud je ale v textu chceme zachovat, musíme si napsat funkci vlastní. Mohla by být založena na regulárních výrazech, nejjednodušší ale bude použít staré dobré procházení po znacích:

<?php
/** Zkrácení textu s XHTML značkami
@param string $s zkracovaný řetězec bez komentářů a bloků skriptu
@param int $limit požadovaný počet vrácených znaků
@return string zkrácený řetězec se správně uzavřenými značkami
*/
function xhtml_cut_tidy($s, $limit)
{
    $length = 0;
    for ($i=0; $i < strlen($s) && $length < $limit; $i++) {
        switch ($s{$i}) {
        case '<':
            while ($i < strlen($s) && $s{$i} != '>') {
                $i++;
            }
            break;
        case '&':
            $length++;
            while ($i < strlen($s) && $s{$i} != ';') {
                $i++;
            }
            break;
        default:
            $length++;
        }
    }
    $config = array('output-xhtml' => true, 'show-body-only' => true);
    return tidy_repair_string(substr($s, 0, $i), $config, 'raw');
}
?> 

Funkce prochází řetězec, značky a entity přeskakuje a započítává pouze zobrazované znaky. Zkrácený řetězec ošetří funkcí tidy_repair_strin­g, která doplní chybějící uzavírací značky. Pokud bychom ošetřovali HTML řetězec, museli bychom kromě nepředání parametru output-xhtml funkci tidy_repair_string upravit také zpracování entit, které v HTML není tak přísné.

Funkce tidy_repair_string nám ušetřila spoustu práce, jak ale postupovat v případě, že není k dispozici? Bude potřeba ukládat otevírané značky do zásobníku a na závěr je uzavřít:

<?php
/** Zkrácení textu s XHTML značkami
@param string $s zkracovaný řetězec bez komentářů a bloků skriptu
@param int $limit požadovaný počet vrácených znaků
@return string zkrácený řetězec se správně uzavřenými značkami
*/
function xhtml_cut($s, $limit)
{
    $length = 0;
    $tags = array(); // dosud neuzavřené značky
    for ($i=0; $i < strlen($s) && $length < $limit; $i++) {
        switch ($s{$i}) {

        case '<':
            // načtení značky
            $start = $i+1;
            while ($i < strlen($s) && $s{$i} != '>' && !ctype_space($s{$i})) {
                $i++;
            }
            $tag = substr($s, $start, $i - $start);
            // přeskočení případných atributů
            while ($i < strlen($s) && $s{$i} != '>') {
                $i++;
            }
            if ($s{$start} == '/') { // uzavírací značka
                array_shift($tags); // v XHTML dokumentu musí být vždy uzavřena poslední neuzavřená značka
            } elseif ($s{$i-1} != '/') { // otevírací značka
                array_unshift($tags, $tag);
            }
            break;

        case '&':
            $length++;
            while ($i < strlen($s) && $s{$i} != ';') {
                $i++;
            }
            break;

        default:
            $length++;

        }
    }
    $s = substr($s, 0, $i);
    if ($tags) {
        $s .= "</" . implode("></", $tags) . ">";
    }
    return $s;
}
?> 

Úprava na tolerantnější HTML verzi by v tomto případě byla o něco složitější, protože v HTML nemusí být nepárové značky označeny a navíc některé značky uzavřeny být mohou, ale nemusí.

Pokud si zadavatel usmyslí, že chce za všech okolností zobrazovat např. čtyři řádky, dá se postupovat tak, že se text zkrátí s dostatečnou rezervou a na čtyři řádky se seřízne u klienta pomocí stylu height: 5em; overflow: hidden; line-height: 1.25;, kde hodnota u height je požadovaný počet řádek vynásobený hodnotou line-height v jednotkách em.


Podobně laděné texty můžete najít i na autorově weblogu PHP triky.

Našli jste v článku chybu?