Hlavní navigace

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

20. 6. 2005
Doba čtení: 3 minuty

Sdílet

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í.

MIF22 EARLY

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.

Autor článku

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.