Přejít k navigační liště

Zdroják » PHP » XMLReader – když se zamotáme do SAX

XMLReader – když se zamotáme do SAX

Články PHP, Různé

V minulém dílu seriálu o práci s XML soubory v jazyce PHP jsme probírali rozhraní SAX. V dnešním pokračování je na řadě rozhraní XMLReader, které je do určité míry podobné (zpracovává XML soubor po částech), ale používá opačný směr toku událostí a dovoluje zjednodušit kód výsledné aplikace.

Jak jsme viděli, rozhraní SAX je sice rychlé a nenáročné na zdroje, ale práce s ním není moc pohodlná. Je to způsobeno push modelem, který SAX používá (viz obrázek 4 – „Princip push modelu přístupu k dokumentu XML“ v minulém dílu). Tuto nevýhodu SAXu překonávají novější rozhraní, která staví na tzv. pull modelu. Zatímco push parser (SAX) zahrnuje naši aplikaci proudem událostí, které zkrátka musíme obsloužit, pull parser aplikaci předá data, jen když si o to aplikace řekne. Data tedy čteme v tu chvíli, kdy je potřebujeme. Díky tomu může být kód aplikace mnohem přehlednější a přímočařejší.

Kosek
Pull model čtení dat

Pull parser je v PHP dostupný jako třída XMLReader. Díky této objektové obálce je práce s  XMLReader velmi jednoduchá. Nejprve si musíme vždy vytvořit novou instanci parseru a pak říci, jaký dokument se bude načítat:

$reader = new XMLReader();
$reader->open("dokument.xml");

V tomto okamžiku je dokument XML připraven ke čtení. V okamžiku, kdy náš skript bude chtít přečíst část dokumentu, stačí použít metodu read(), která přečte další část dokumentu XML (počáteční tag, obsah elementu apod.). V případě, že jsme na konci dokumentu, vrací metoda hodnotu false, v opačném případě true. Chceme-li tedy postupně přečíst celý dokument, stačí metodu volat v cyklu  while:

while ($reader->read())
{
  …
}

Chcete se naučit o PHP víc?

Akademie Root.cz pořádá školení Kurz programování v PHP5. Jednodenní kurz programování v PHP 5 je určen všem webovým vývojářům, kteří se chtějí do hloubky seznámit a sžít s programovacím jazykem PHP ve verzi 5. První část kurzu je zaměřena na nový objektový model se všemi jeho vlastnostmi, ošetření chyb pomocí výjimek a efektivní využití těchto konceptů. Druhá část je zaměřena na nové knihovny PHP 5, především pro práci s databázemi, XML a objekty. Pozornost je věnována i zajištění kompatibility s PHP 4, přechodu z této verze a výhledu na PHP 6. Máte zájem o jiné školení? Napište nám!

Na objektu XMLReader je dostupných několik vlastností, které umožňují zjišťovat informace o části dokumentu, na kterou jsme se právě přesunuli. Můžeme například zjišťovat druh uzlu (nodeType) a jeho název (name). Jsme-li nastaveni na elementu (tedy na jeho počátečním tagu), lze pomocí metody readString() přečíst celý textový obsah elementu. Použití těchto metod pro čtení dokumentu RSS demonstruje následující příklad.

<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN'>
<html lang="cs">
  <head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <title>Přehled zpráv</title>
  </head>
  <body>
<?php

// vytvoření nového XMLReaderu
$reader = new XMLReader();

// otevření souboru pro čtení
$reader->open("../data/luparss.xml");

// dokud je co, čteme další část dokumentu XML
while ($reader->read())
{
  // obsluha odkazu (element link)
  if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "link")
    echo "<h1>Přehled aktuálních zpráv ze serveru
      <a href='" . htmlspecialchars($reader->readString(), ENT_QUOTES) . "'>" .
      $title . "</a></h1>";

  // obsluha názvu kanálu
  if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "title")
    $title = htmlspecialchars($reader->readString());

  // obsluha položky v kanálu
  if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "item")
  {
    echo "<dl>";

    // dokud je co, čteme další část dokumentu XML
    // předpokládáme přitom, že elementy jsou uvnitř elementu item
    while ($reader->read())
    {
      if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "link")
                echo "<dt><a href='" . htmlspecialchars($reader->readString(), ENT_QUOTES) . "'>" .
                    $title . "</a></dt>n";

      if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "title")
                $title = htmlspecialchars($reader->readString());

      if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == "description")
                echo "<dd>" .  htmlspecialchars($reader->readString()) . "</dd>n";
    }

    echo "</dl>";
  }
}

?>
  </body>
</html>

V příkladu je použita konstanta XMLREADER_ELEMENT, která definuje kód uzlu, který odpovídá začátku elementu. Zároveň vidíme, že podle potřeby můžeme čtení z dokumentu XML zanořovat níže do struktury kódu – např. když chceme jinak zpracovat obsah elementu item. Práce s  XMLReader je principem podobná jako sekvenční čtení souborů, a proto je mnoha programátorům velmi blízká. Navíc zachovává výhody rozhraní SAX, jako je rychlost a malá paměťová náročnost.

V příštím díle se podíváme na snad nejznámější rozhraní pro práci s XML, na rozhraní DOM. Toto rozhraní je webovým vývojářům dobře známé, protože stejné rozhraní se používá i v JavaScriptu pro manipulaci se stránkou načtenou do prohlížeče.

Více informací o knize naleznete na stránkách nadavatelství Grada a na stránkách autora.

Komentáře

Subscribe
Upozornit na
guest
11 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
brablc

Byl jsem na Webexpu a poprve slysel o XMLReader a tesil se na pokracovani clanku o XML tady, ale ted jsem trosku rozpacity.

I tenhle jednoduchy priklad ukazuje, ze to ma koncepcni chyby. Element link se poprve vyskytuje zde: /rss/channel/i­mage/link. Takze prvni nalezeni tohoto elementu se tiskne jeste predtim nez mame title z /rss/channel/title takze H1 mame ve vysledku 2× a jako premii PHP Notice.

To by se dalo nejak opravit, ale z ceho me mrazi je ten vnoreny „while ($reader->read())“. Porad jsem marne hledal misto, kde se to opousti po ukonceni elementu item a nenasel. Ono to fakt bezi furt dal. Takze pokud bude jednomu itemu chybet title, tak se tam vesele natiskne ten predchozi.

Jakub Vrána

Validace v tomto případě nepomůže, protože kód závisí na pořadí jednotlivých značek (které v RSS není určeno) a navíc na absenci povolených značek (konkrétně <image>).

Sám píšeš, že u proudových parserů je potřeba si držet informaci o stavu. Zásadní chybu příkladů (u minulého a tohoto článku) vidím v tom, že si tento stav nedrží. Příklady pak vůbec neodpovídají skutečnému použití.

peter

SAX a XMLReader su v podstate skoro to iste. Osobne si myslim, ze SAX je z hladiska spracovania lepsi a z hladiska kodu prehladnejsi. Samozrejme pre dobreho programatora. :)
XMLReader mi pripada ako dorbeny neskor pre tych, ktori nevedia pochopit ako moze fungovat SAX. :)

Jakub Vrána

Koncepční chyba není v XMLReaderu, ale v příkladu. Osobně mi přijde vhodnější všechno načítat v jednom while cyklu a kontext si sledovat v proměnné. Ukázka RSS importu pomocí XMLReaderu je na http://www.clipboard.cz/4ng. Má pouze to omezení, že očekává značky <item> až za <link> a <title> celého zdroje. Pokud by to tak nebylo, pak bychom výstup nemohli rovnou vypisovat, ale museli bychom si ho ukládat do dočasné proměnné.

brablc

On je cely ten XMLReader divny:

$xml->nodeType == XMLReader::ELEMENT
$xml->nodeType == XMLReader::EN­D_ELEMENT
$xml->nodeType == XMLReader::TEXT

Takze uzavreny element je typ uzlu?

A tenhle priklad je uz natolik nepochopitelny, ze muze byt i spravne. Nicmene pouziti jednoho $xml->read() asi uplne rusi veskere vyhody XMLReaderu. Zatim rikam zlaty SAX (samozrejme ze pro pripad RSS je zlaty simplexml_load_fi­le).

Jakub Vrána

Důležitá je ještě vlastnost isEmptyElement, která umožňuje rozpoznat prázdné elementy (<x/>). Ty totiž END_ELEMENT nemají.

Jakub Vrána

Zásadně pomalejší to není. Kontext si nějak držet musíme, takže kromě vlastního zásobníků zbývá rekurzivní funkce. Udělal jsem ji co nejchytřeji (do rekurze vstupuje jen když potřebuje změnit kontext) a výsledky jsou tyto:

0.98 s – porovnávání pole s kontextem
0.91 s – chytrá rekurzivní funkce

Jedná se o medián ze tří pokusů při načítání titulku z exportu Wikipedie. Zdrojový kód je na http://www.clipboard.cz/5ng. Dlužno ale podotknout, že pokud bychom kontextů chtěli rozlišovat více, tak by se kód dramaticky zesložitil. Např. RSS import bych v tom psát opravdu nechtěl.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.