Vytvoření parseru a analýza dokumentu XML

8. února 2002

Vytvoření parseru XML není v PHP problém. V tomto článku jej nejen vytvořím, ale na jednoduchém souboru XML i prakticky vysvětlím jeho funkčnost.

Soubor knihovna.xml

Jako analyzovaný dokument XML použiji ukázkový soubor knihovna.xml, který kromě elementů s příslušnými atributy obsahuje i další typické prvky jazyka XML: tzv. prolog, tj. XML deklaraci a deklaraci typu dokumentu, určení kódóvání, deklaraci entit a notace, dále instrukci pro zpracování, tedy kód určený ke zpracování jiným programem, komentáře, interní i externí textovou entitu a konečně i externí binární entitu pro vložení obrázku.

<?xml version="1.0" encoding="windows-1250"?>
<!DOCTYPE knihovna SYSTEM „knihovna.dtd“ [
<!NOTATION gif SYSTEM "Compuserve Graphics Interchange Format 87a">
<!ENTITY ikona SYSTEM "books.gif" NDATA gif>
<!ENTITY program "Jednoduchý parser v PHP pro soubor knihovna.xml">
<!ENTITY info SYSTEM "1.xml">
]>
<knihovna>
<datum>Dnes je <?php echo Date("d.m.Y");?></datum>
<!– element DATUM obsahuje instrukci pro zpracování v PHP –>
<nazev>&program;</nazev>
<!– element NAZEV obsahuje interní entitu –>
<obrazek src="ikona"/>
<!– atribut SRC obsahuje externí neanalyzovanou entitu –>
<vypujcni_rad>Informace k výpujčnímu řádu naleznete zde: &info;</vypujcni_rad>
<!– element RAD obsahuje externí entitu –>
<kniha stav="dobrý" pujceno="na skladě">
<titul>Knížka pro PHP</titul>
<autor>Jiří Kosek</autor>
<ID>001</ID>
<str>390</str>
</kniha>
<kniha stav="poškozeno" pujceno="17.12.2001">
<titul>Ferda Mravenec</titul>
<autor>Ondřej Sekora</autor>
<ID>002</ID>
<str>172</str>
</kniha>
<kniha stav="dobrý" pujceno="5.1.2002">
<titul>Robinson Crusoe</titul>
<autor>Daniel Defoe</autor>
<ID>003</ID>
<str>214</str>
</kniha>
</knihovna>

Systémové funkce PHP pro práci s XML

Nejdříve vám představím potřebné systémové funkce PHP 4 pro práci s XML:

xml_parser_create( )
Vytvoří parser XML. V jednom skriptu PHP lze vytvořit více parserů – mohu tak zpracovávat více dokumentů XML. Přiřadím-li tuto funkci např. proměnné $parser, získám parser XML pod jménem $parser.
xml_parser_free(jméno parseru)

Uvolňuje parser XML vytvořený předešlou funkcí. Všechny parsery vytvořené skriptem PHP by měly být uvolněny po přečtení celého dokumentu XML, nebo když při čtení dojde k chybě.

xml_get_current_line_number(jméno parseru), xml_get_current_column_number(jméno parseru)
Vrací číslo aktuálního řádku, resp. sloupce, který parser právě zpracovává. Tyto funkce mají význam pro ošetření chyb při analýze dokumentu XML – s jejich pomocí mohu chybu přesně lokalizovat.

xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false)
Tuto funkci bych musel použít (v přesném znění s uvedenými parametry), pokud bych chtěl názvy elementů vypisovat malými písmeny. Implicitně se názvy elementů vypisují písmeny velkými.

xml_set_element_handler($parser, „startElementHandler“, „endElementHandler“)
Registruje obslužné funkce pro obsluhu počátečních a koncových elementů dokumentu XML. Nazval jsem si je „startElementHandler“ a „endElementHandler“. Parametr $parser určuje jméno parseru XML, kterému se příslušné obslužné funkce zaregistrují. Tento parametr používají i následující systémové funkce a nebudu se k němu tedy již vracet.

xml_set_character_data_handler($parser, „cdataHandler“)
Registruje obslužnou funkci pro čtení znakových dat dokumentu XML, kterou jsem si nazval „cdataHandler“.

xml_set_processing_instruction_handler($parser, „jinyProgram“)
Registruje obslužnou funkci k instrukcím pro zpracování, kterou jsem si nazval „jinyProgram“. Tato funkce bude mít na starosti správnou interpretaci kódu PHP vloženého do analyzovaného souboru knihovna.xml.

xml_set_external_entity_ref_handler($parser, „externiEntita“)
Registruje obslužnou funkci odkazu externí textové entity, kterou jsem si nazval „externiEntita“.

xml_set_notation_decl_handler($parser, „notace“)
Registruje obslužnou funkci deklarace notace, kterou jsem si nazval „notace“.

xml_set_unparsed_entity_decl_handler($parser, „neanalyzEntita“)
Registruje obslužnou funkci deklarace neanalyzované entity, kterou jsem si nazval „neanalyzEntita“. Tato funkce bude mít na starosti správnou interpretaci obrázku vloženého do souboru knihovna.xml
xml_set_default_handler($parser, „nedefinovano“)
Registruje obslužnou funkci, která je volána pokaždé, když parser narazí na uzel dokumentu XML, který není ošetřen žádnou z předešlých funkcí. Tuto obslužnou funkci jsem si nazval „nedefinovano“ a bude mít na starosti správnou interpretaci celého prologu a vložených komentářů.

xml_parse($parser, $data, $posledni)
Stará se o vlastní analýzu – volá zaregistrované funkce, jakmile narazí na příslušné uzly v dokumentu. Parametr $data určuje obsah dokumentu XML a parametr $posledni určuje konec vstupních dat. Není tedy nutné procházet celý obsah souboru XML najednou.

Obslužné funkce

Obslužné funkce jsou již poněkud zábavnější – zde si každý znalý jazyka PHP může nadefinovat výstup uzlů analyzovaného dokumentu XML do kódu HTML. Tento výstup je zcela modifikovatelný – představte si jednotlivé uzly dokumentu XML jako proměnné, se kterými můžete volně pracovat. V PHP není potom problém vytvořit například aplikaci, která by v souboru knihovna.xml dokázala vyhledávat knížky podle autorů, názvů nebo počtu stran a vyhledané knížky zobrazit do libovolné tabulky viditelné nejen v Exploreru, ale i třeba v Netscapu. Abych však princip obslužných funkcí ilustroval jednodušeji, použiji je pouze k vytvoření krátkého skriptu, který přečte a přehledně zobrazí všechny informace obsažené v souboru knihovna.xml. Výsledek v kódu html si můžete prohlédnout zde.

Definice obslužných funkcí

startElementHandler($parser, $name, $attribs)
K zavolání této mnou definované funkce dojde, když parser najde otevírací značku elementu v dokumentu. Parametr $parser identifikuje parser, který tuto funkci volá. Použiji-li tedy k vytvoření parseru například proměnnou $parser, musím i tento parametr nazvat $parser. Parametru $name je přiřazen název elementu. Parametr $attribs je asociativní pole. Jeho jednotlivé indexy odpovídají názvům atributů a odpovídající prvky jejich hodnotám.

function startElementHandler($parser, $name, $attribs) {
global $otevri;
// iniciace globální proměnné pro zpracování externí neanalyzované entity
if ($name = = „KNIHOVNA“):
echo „<br><b><font color=\“red\“>&lt;$name“;
// kořenový element „knihovna“ bude zleva uzavřen do znaku < a bude zobrazen tučně a červeně; bude také odražen o jeden řádek
else:
echo „<font color=\“red\“>&lt;$name“;
// ostatní elementy budou rovněž zleva uzavřeny do znaku < a zobrazeny červeně, ale ne tučně a nebudou odražené
endif;
if (sizeOf($attribs)) {
while (list($atribut, $hodnota) = each($attribs)) {
// přiřazení jednotlivých hodnot indexů a prvků pole $attribs do proměnných $atribut a $hodnota
if($atribut = = „SRC“):
echo “ <font color=\“brown\“>$atribut = </font>$otevri“;
// Z DTD dokumentu knihovna.xml vím, že atribut „src“ obsahuje externí neanalyzovanou entitu – grafiku ve formátu GIF. Zajistím tedy zobrazení názvu atributu v hnědé barvě a zavolám proměnnou $otevri vygenerovanou funkcí neanalyzEntita (viz níže), která zajistí vypsání názvu entity a zobrazení obrázku.
else:
echo “ <font color=\“brown\“>$atribut = \“</font><font color=\“black\“>$hodnota</font><font color=\“brown\“>\“</font>“;
// Vypíši všechny atributy i s příslušnými hodnotami v úvozovkách. Jména atributů i úvozovky budou v hnědé barvě, hodnoty atributů v černé.
endif;
}
}
if ($name = = „KNIHOVNA“):
echo „&gt;</font></b><br>“;
elseif ($name = = „KNIHA“):
echo „&gt;</font></b><br>“;
else:
echo „&gt;</font>“;
endif;
// Kořenový element „knihovna“ a element „kniha“, který obsahuje pouze další elementy a žádná data, odrazím tak, aby na řádku stály samostatně. Všechny elementy uzavřu zprava do znaku >.
}

endElementHandler($parser, $namekon)
K zavolání této mnou definované funkce dojde, když parser najde koncovou značku elementu v dokumentu. Parametr $parser identifikuje parser, který tuto funkci volá. Parametru $namekon je přiřazen název elementu.

function endElementHandler($parser, $namekon) {
if ($namekon = = „KNIHOVNA“):
echo „<b><font color=\“red\“>&lt;/$namekon&gt;</font></b><br>“;
else:
echo „<font color=\“red\“>&lt;/$namekon&gt;</font><br>“;
endif;
}
// Elementy uzavřu párovými uzavíracími tagy. Opět je zobrazím v červené barvě, uzavírací tag kořenového elementu „knihovna“ bude tučný.

cdataHandler($parser, $data)
K zavolání této mnou definované funkce dojde, když parser najde jakýkoli obsah dokumentu bez značek. Parametr $parser identifikuje parser, který tuto funkci volá. Parametru $data jsou přiřazena znaková data dokumentu XML. Parser data vrací přesně v jejich podobě – neodstraňuje mezery.

function cdataHandler($parser, $data) {
echo „$data“;
}
// vypíši znaková data mezi otevírací a uzavírací značkou elementu

jinyProgram($parser, $cil, $data)
K zavolání této mnou definované funkce dojde, když parser najde v dokumentu XML instrukce pro jiný program. Parametr $parser identifikuje parser, který tuto funkci volá. Parametru $cil je přiřazeno jméno programu, který má instrukce zpracovat. Parametru $data jsou předány instrukce. Pokud tedy např. parser $parser narazí v dokumentu na instrukci <?php echo „Ahoj!“; ?> je funkce volána s parametry $cil=“php“ a $data=“echo \“Ahoj!\“;“.

function jinyProgram($parser, $cil, $data) {
if (strcmp(strtolower($cil), „php“)= =0) {
// porovnáním řetězců zjistím, zda se jedná o vložený skript PHP
eval($data);
// příkazem eval zajistím provedení skriptu
}
}

externiEntita($parser, $jmenoEntity, $x, $systemID, $publicID)
K zavolání této mnou definované funkce dojde, když parser narazí v dokumentu XML na externí textovou entitu. Parametr $parser identifikuje parser, který tuto funkci volá. Parametru $jmenoEntity je přiřazen název entity, parametr $x je v současné době prázdný řetězec, parametru $systemID je přiřazen systémový identifikátor externí entity a parametru $publicID veřejný identifikátor externí entity. Pokud tedy např. parser $parser narazí v dokumentu na externí entitu &kniha, která je definována v DTD dokumentu XML jako <!ENTITY kniha SYSTEM „kniha.xml“>, bude funkce volána s parametry $jmenoEntity=“kniha“, $systemID=“kniha.xml“.

function externiEntita($parser, $jmenoEntity, $x, $systemID, $publicID) {
if (!$systemID):
return false;
else:
echo „<a style=\“color:blue\“ href=“.$systemID.“>“.$jmenoEntity.“</a>“;
return true;
endif;
}
// externí entitu ošetřím příslušným linkem v modré barvě

notace($parser, $zapisNotace, $x, $systemID, $publicID)
K zavolání této mnou definované funkce dojde, když parser narazí v dokumentu XML (v jeho DTD) na deklaraci notace. Parametr $parser identifikuje parser, který tuto funkci volá. Parametru $zapisNotace je přiřazen název notace, parametr $x je v současné době prázdný řetězec, parametru $systemID je přiřazen systémový identifikátor deklarace notace a parametru $publicID veřejný identifikátor deklarace notace. Praktický význam v mém skriptu tato funkce nemá, definoval jsem ji jen pro úplnost.

$pomocneAplikace = Array();
function notace($parser, $zapisNotace, $x, $systemID, $publicID) {
global $pomocneAplikace;
if ($systemID):
$pomocneAplikace[$zapisNotace] = $systemID;
else:
$pomocneAplikace[$zapisNotace] = $publicID;
endif;
}
// do proměnné typu pole načtu k jednotlivým notacím jejich příslušné charakteristiky

neanalyzEntita($parser, $jmenoEntity, $x, $systemID, $publicID, $jmenoNotace)
K zavolání této mnou definované funkce dojde, když parser narazí v dokumentu XML na deklaraci externí neanalyzované (binární) entity. Parametr $parser identifikuje parser, který tuto funkci volá. Parametru $jmenoEntity je přiřazen název neanalyzované entity, parametr $x je v současné době prázdný řetězec, parametru $systemID je přiřazen systémový identifikátor neanalyzované entity, parametru $publicID veřejný identifikátor neanalyzované entity a parametru $jmenoNotace typ dat neanalyzované entity. Jestliže například parser najde v dokumentu (v jeho DTD) následující deklaraci: <!ENTITY logo SYSTEM „logo.gif“ NDATA gif>, je funkce volána s parametry $jmenoEntity=“logo“, $systemID=“logo.gif“ a $jmenoNotace=“gif“.

function neanalyzEntita($parser, $jmenoEntity, $x, $systemID, $publicID, $jmenoNotace) {
global $otevri;
$otevri = „<font color=\“blue\“>$jmenoEntity</font> <img src=\“$systemID\“>“;
}
// Globální proměnná $otevri zajistí vypsání jména entity a načtení příslušného obrázku. Tuto proměnnou bude volat funkce startElementHandler, jakmile narazí na atribut „src“.

nedefinovano($parser, $data)
K zavolání této mnou definované funkce dojde, když parser narazí v dokumentu XML na jakýkoli uzel bez zaregistrované obslužné funkce nebo na uzel, k němuž takovouto funkci zaregistrovat nelze. V tomto skriptu ji využiji ke zpracování komentářů a celého prologu dokumentu.

function nedefinovano($parser, $data) {
if (StrPos($data, „!–„)):
// rozpoznámí komentářů
$komentar = HTMLSpecialChars($data);
echo „<font color=\“green\“>$komentar</font><br>“;
// Výpis komentářů v zelené barvě. Funkce HTMLSpecialChars( ) zajistí zobrazení komentářů v prohlížečích, které ignorují normálně vše, co je mezi znaky <!– a –>.
else:
$ostatni = HTMLSpecialChars($data);
echo „<font color=\“blue\“>“.NL2BR($ostatni).“</font>“;
// Obdobným způsobem zajistím výpis prologu XML dokumentu, tentokrát v modré barvě. Funkce NL2BR( ) zajistí správné odrážení řádků.
endif;
}

Vytvoření parseru a registrace obslužných funkcí

Po definici obslužných funkcí mohu vytvořit parser XML a tyto funkce zaregistrovat.

$parser=xml_parser_create();
if(!$parser):
echo „Nepodařilo se vytvořit parser!“;
endif;
xml_set_element_handler($parser, „startElementHandler“, „endElementHandler“);
xml_set_character_data_handler($parser, „cdataHandler“);
xml_set_processing_instruction_handler($parser, „jinyProgram“);
xml_set_external_entity_ref_handler($parser, „externiEntita“);
xml_set_notation_decl_handler($parser, „notace“);
xml_set_unparsed_entity_decl_handler($parser, „neanalyzEntita“);
xml_set_default_handler($parser, „nedefinovano“);

Otevření dokumentu XML

$file=“knihovna.xml“;
$fp = fopen($file, „r“);
// Tímto příkazem otevřu dokument knihovna.xml ke čtení.
if (!$fp):
echo „Nepodařilo se otevřít soubor $file ke čtení!“;
endif;

Přečtení a zpracování dokumentu XML

Dokument je lépe analyzovat po částech, aby u rozsáhlých údajů nedošlo k zahlcení parseru. Současně je možné alespoň základním způsobem ošetřit případné chyby ve struktuře dokumentu (parser v PHP není schopen zkontrolovat validitu dokumentu, tedy to, zda dokument odpovídá deklarovanému DTD).

while ($data = fread($fp, 4096)) {
// budu načítat bloky po 4 kB a předávat je parseru
if (!xml_parse($parser, $data, feof($fp))):
die(sprintf(„Chyba v XML dokumentu na řádku %d a sloupci %d“,
xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
endif;
// Narazí-li parser na chybu, ukončí svou činnost a současně se vypíše číslo řádku a sloupce, kde k chybě došlo
}

Uvolnění parseru

xml_parser_free($parser);

Využití mocných nástrojů PHP na analýzu XML dokumentů k triviálnímu přepisu jednoduchého dokumentu knihovna.xml je samozřejmě tak trochu škoda. Věřím však, že k prvnímu seznámení s těmito nástroji tento příklad snad poslouží dobře.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Předchozí článek Kde se dobře ubytovat
Další článek Přilétající písmo

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *