Pokud chceme zkontrolovat pravopis jednojazyčného HTML dokumentu, je to s využitím rozšíření PSpell poměrně jednoduché. Funkci, která bude kontrolu zajišťovat, navrhneme s využitím statické proměnné rovnou tak, aby se inicializace slovníku nemusela vykonávat při každém zavolání:
<?php
/** Kontrola pravopisu pro všechna slova v textu
@param string $check řetězec ke zkontrolování v kódování UTF-8
@param string $lang kód jazyka používaný knihovnou PSpell
@return array slova nenalezená ve slovníku, false v případě chyby
*/
function spell_check($check, $lang)
{
static $pspell = array(); // pole s inicializovanými slovníky pro jednotlivé jazyky
if (!isset($pspell[$lang])) {
pspell_config_create($lang); // bez vytvoření konfigurace nemusí pspell_new() najít slovník
$pspell[$lang] = @pspell_new($lang, "", "", "utf-8");
}
if (!$pspell[$lang]) { // nepodařilo se načíst slovník pro daný jazyk
return false;
}
$return = array();
$words = array_unique(preg_split('~' . WORD_BOUNDARY . '+~u', $check, -1, PREG_SPLIT_NO_EMPTY));
foreach ($words as $word) {
if (!pspell_check($pspell[$lang], $word)) {
$return[$word] = true;
}
}
return $return;
}
$file = file_get_contents($filename);
$file = strip_tags($file); // pozor, může spojit dvě slova do jednoho
$errors = spell_check($file, 'cs');
?>
Funkce předpokládá, že dokument bude v kódování UTF-8, což v reálných podmínkách často nebude splněno. Pro skutečné nasazení tedy budeme muset kódování detekovat a např. funkcí iconv převést. V kódování UTF-8 nefunguje správně regulární výraz \W
, takže slova rozdělujeme pomocí vlastní konstanty WORD_BOUNDARY
, kterou můžeme definovat např. takto:
<?php
/** Obdoba funkce chr() pro kódování UTF-8
@param int $code pozice znaku
@return string UTF-8 reprezentace kódu
*/
function chr_utf8($code)
{
if ($code < 0) {
return false;
} elseif ($code < 128) {
return chr($code);
} elseif ($code < 2048) {
return chr(192 | ($code >> 6)) . chr(128 | ($code & 63));
} elseif ($code < 65536) {
return chr(224 | ($code >> 12)) . chr(128 | (($code >> 6) & 63)) . chr(128 | ($code & 63));
}
}
define('WORD_BOUNDARY', '[^A-Za-z' . chr_utf8(192) . '-' . chr_utf8(563) . ']');
?>
Znaky s kódy 192 až 563 jsou vyhrazeny znakům odvozeným z Latinky, funkce chr_utf8
potom vrací reprezentaci těchto znaků v kódování UTF-8.
S vynaložením určitého úsilí by se dal kód např. s využitím regulárních výrazů vylepšit tak, aby kontroloval kupříkladu i atributy alt a title nebo aby ignoroval obsah značek <script> a <style>. Mnohem zajímavější ale bude kód upravit tak, aby respektoval atribut lang ve vícejazyčných HTML dokumentech. Tento atribut určuje jazyk hodnot textových atributů a obsahu značek a můžou ho používat skoro všechny HTML značky. Abychom atribut dokázali zpracovat, budeme muset HTML dokument rozebrat některým z následujících způsobů:
- Využít regulární výrazy
- Hledání značek obsahujících daný atribut a odpovídajících uzavíracích značek je mnohokrát zpracovaná úloha, která se pomocí regulárních výrazů neřeší zrovna pohodlně.
- Využít rozšíření XML
- HTML dokumenty nejsou XML, takže bychom je nejprve museli převést – nejsnáze s využitím rozšíření Tidy. Toto rozšíření ostatně HTML dokument do struktury objektů dokáže rozebrat samo o sobě.
- Využít rozšíření DOM
- Standard DOM je k rozebírání struktury HTML a XML dokumentů přímo navržen, ale v PHP je toto rozšíření k dispozici až od verze 5.
Nejčistší řešení bude přes rozšíření DOM:
<?php
$SPELL_ATTRS = array("abbr", "alt", "label", "prompt", "standby", "summary", "title");
/** Kontrola pravopisu DOM objektu a jeho potomků na základě hodnoty atributu lang
@param DOMNode $node kontrolovaný DOM objekt
@param string $lang kód jazyka používaný knihovnou PSpell
@return array slova nenalezená ve slovníku
*/
function spell_check_childs($node, $lang)
{
$return = array();
// nastavení jazyka
if ($node->getAttribute("lang")) {
$lang = $node->getAttribute("lang");
}
// kontrola textových atributů
foreach ($GLOBALS["SPELL_ATTRS"] as $attr) {
if ($node->getAttribute($attr)) {
$return += spell_check($node->getAttribute($attr), $lang);
}
}
// kontrola dětí
foreach ($node->childNodes as $child) {
if ($child instanceof DOMText) {
$return += spell_check($child->nodeValue, $lang);
} elseif ($child instanceof DOMElement && !in_array($child->nodeName, array("script", "style"))) {
$return += spell_check_childs($child, $lang);
}
}
return $return;
}
$dom = DOMDocument::loadHTMLFile($filename);
$html = $dom->getElementsByTagName("html")->item(0);
$errors = spell_check_childs($html, 'cs');
?>
Funkcí DOMDocument::loadHTMLFile se načte HTML dokument a na kořenovou značku <html> se spustí rekurzivní kontrola pravopisu. Ta v případě nastaveného atributu lang změní aktuální jazyk, zkontroluje atributy obsahující textovou informaci a prochází děti DOM objektu – v textových objektech kontroluje pravopis, značky <script> a <style> a komentáře ignoruje a na ostatní objekty volá rekurzivně sama sebe. Chyby se vracejí nadřazené funkci v návratové hodnotě, alternativním řešením by bylo předávat je v parametru funkce volaném referencí.
U kontroly dětí si lze všimnout toho, že vlastnost childNodes je ve skutečnosti objekt typu DOMNodeList. Díky iteraci objektů zavedené v PHP 5 je ale možné tento objekt přímo procházet konstrukcí foreach.
Zdánlivě jednoduchý příklad kontroly pravopisu v HTML dokumentu se zvrtl na rozebírání dokumentu rozšířením DOM. Využili jsme při tom dva nové obraty v PHP 5 – operátor instanceof a iteraci objektů.
Podobně laděné texty můžete najít i na autorově weblogu PHP triky.