Přeskočit na hlavní obsah

Ošetření vstupního textu pomocí XSLT

Kontrola textu odeslaného uživatelem včetně ošetření případných HTML tagů je poměrně častou úlohou. Pro tento účel je téměř ideální použití jednoduché XSL transformace. Typickým případem, kdy takovou kontrolu použijeme, je zpracování příspěvků zasílaných do různých diskuzí.

V nich většinou chceme kromě běžného textu zachovat také několik základních HTML tagů, případně i atributů. Naopak, všechny ostatní tagy a atributy je potřeba zrušit. Předejdeme tím možnému narušení validity stránky, rozbití layoutu, podstrčení nějakého skriptu apod.

Tradiční postupy

Nejjednodušším řešením, které nabízí dokonce samotné PHP, je funkce strip_tags. Její druhý parametr umožňuje zadat seznam tagů, které chceme nechat. Všechny ostatní tagy tato funkce odstraní. Neumí ale už zachovat resp. zrušit jen určité jednotlivé atributy. Funkce se také nemusí chovat podle očekávání, pokud zpracovávaný text obsahuje špatně uzavřené tagy apod.

Další možností je nahrazování určených tagů a atributů pomocí regulárních výrazů. Takové předpisy ale bývají často značně složité a nepřehledné. Lehce se v nich tak ztratíte nebo uděláte chybu.

XSL transformace

Pro náš účel je téměř ideální použití XSL transformací. V následujícím příkladu si ukážeme vytvoření jednoduché transformace, která bude zachovávat:

  • odstavce <p> a nadpisy <h1><h3>
  • nové řádky <br/>
  • zvýraznění <strong> a <em>, přičemž jimi nahradí i případné výskyty méně vhodných značek <b> resp. <i>
  • odkazy <a> s atributem href.

Všechny ostatní tagy a atributy budou ze zadaného textu vypuštěny. Uvedený postup navíc zajistí správně strukturovaný (well-formed) výstup výsledného textu.

Vytvoření šablony

Základem je vytvoření příslušné XSLT šablony. Soubor s ní si nazvěme třeba sablona.xsl:

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">

        <xsl:output encoding="utf-8"/>

        <xsl:template match="p|h1|h2|h3">
                <xsl:element name="{name()}">
                        <xsl:apply-templates/>
                </xsl:element>
        </xsl:template>

        <xsl:template match="b|strong">
                <strong>
                        <xsl:apply-templates/>
                </strong>
        </xsl:template>

        <xsl:template match="i|em">
                <em>
                        <xsl:apply-templates/>
                </em>
        </xsl:template>

        <xsl:template match="a">
                <xsl:element name="a">
                        <xsl:copy-of select="@href"/>
                        <xsl:apply-templates/>
                </xsl:element>
        </xsl:template>

</xsl:stylesheet>

Princip fungování šablony je myslím zřejmý. Snad jenom poznámka k definici xsl:output, která zajišťuje výstup veškerého textu v UTF-8 kódování. Pro český text je její uvedení nutné. XSL procesor implementovaný v PHP totiž bez ní vrací text v kódování ISO-8859–1 resp. US-ASCII, přičemž diakritické znaky převádí na příslušné číselné textové entity. Například místo „č“ bychom tak dostali &#x10D;.

Provedení transformace

Připravenou šablonu nyní stačí v našem PHP skriptu aplikovat na vstupní text. Ten mám na začátku uložený v UTF-8 kódování v proměnné $text. Pro reprezentaci dokumentu použijeme DOM knihovnu, pro transformaci pak XSL knihovnu, obojí standardně dostupné v PHP verze 5. Kromě samotné transformace nám navíc metoda DomDocument::loadHtml() zajistí správné strukturování vstupního textu.

$xsl = new XsltProcessor();
$xsl->importStylesheet(DomDocument::load('./sablona.xsl'));
$text = $xsl->transformToXml(DomDocument::loadHtml($text));

Při převodu vstupního dokumentu do interní DOM reprezentace v metodě DomDocument::loadHtml() však dochází k zásadní chybě. PHP totiž na vstupu místo našeho UTF-8 předpokládá kódování ISO-8859–1, které si ještě navíc nahrazováním textovými entitami převádí do US-ASCII. Takže například ze znaku „č“ udělá &#xC4;&#x8D;.

Tomu předejdeme malým trikem, kdy na začátek vstupního textu přidáme příslušnou meta-hlavičku content-type. Tím parseru vnutíme, že má text opravdu považovat za UTF-8.

Druhá úprava se týká naopak výstupního textu. Metoda XsltProcessor::transformToXml totiž na jeho začátek automaticky přidává XML deklaraci. Tu ale pravděpodobně vůbec nechceme, na konci zpracování ji tedy z výstupního textu musíme opět odstranit.

Celý skript tedy nakonec vypadá nějak takto:

$text = '<meta http-equiv="content-type"
        content="text/html; charset=utf-8"/>' . $text;

$xsl = new XsltProcessor();
$xsl->importStylesheet(DomDocument::load('./sablona.xsl'));
$text = $xsl->transformToXml(DomDocument::loadHtml($text));

if (strpos($text, '<?xml ') === 0) {
        $text = substr($text, strpos($text, "\n") + 1);
}

Komentáře

  1. Děkuji článek mi velice pomohl…

  2. Super článek, také děkuji ;-)

  3. zkusil někdo: omit-xml-declaration
    pak bychom nemuseli dělat strpos a substr …

  4. Když používám
    <xsl:output method=„html“ encoding=„utf-8“/>
    tak není potřebná finální úprava výstupního stringu.