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>
až<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 atributemhref
.
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 č
.
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á č
.
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);
}
Děkuji článek mi velice pomohl…
Super článek, také děkuji
zkusil někdo: omit-xml-declaration
pak bychom nemuseli dělat strpos a substr …
Když používám
<xsl:output method=„html“ encoding=„utf-8“/>
tak není potřebná finální úprava výstupního stringu.