Xmlreader

Z PHPEdia.pl
Skocz do: nawigacji, wyszukiwania

XMLReader

Język PHP dostarcza kilka rozwiązań w dziedzinie parsowania plików XML. Wszystkie z nich mają swoje zalety i wady. Największą wadą wszystkich rozwiązań (za wyjątkiem SAX) jest ograniczenie w postaci limitu pamięci, jaki jest przydzielony dla skryptów PHP. Ograniczenie to uniemożliwia parsowanie dużych plików XML, które najczęściej spotykane są w aplikacjach typu e-commerce (zdalna aktualizacja oferty, porównywarki cenowe, itp.). Na szczęście istnieje klasa XMLReader. Jest to bardzo prosta klasa, zawierająca około 20 metod oraz dwa razy więcej właściwości i stałych. Dzięki użyciu XMLReader, programista zyskuje bardzo potężne i proste zarazem narzędzie do parsowania dużych plików. Największy, jak dotąd plik, jaki przyszło mi parsować, ważył niecałe 50 MB. Samo parsowanie pliku zajęło około 30 sekund.

Aby rozpocząć pracę z XMLReader, wystarczy poznać jedną metodę (pomijając wczytanie pliku i zamknięcie strumienia) oraz kilka właściwości. Do tego warto zapoznać się w jaki sposób XMLReader identyfikuje węzły w dokumencie XML.

XMLReader działa na zasadzie „czytania” kolejnych węzłów od początku do końca dokumentu. W tym miejscu ujawnia się poważna wada klasy, ponieważ nie ma możliwości dowolnego przechodzenia między węzłami, co jest możliwe np. w DOM. Wprawdzie istnieje metoda umożliwiająca przejście do dowolnego węzła lub atrybutu, jednak w praktyce jest to bardzo niewygodne rozwiązanie i sprawdzi się tylko dla konkretnych przypadków.

Wspomniana wyżej metoda niezbędna do pracy z XMLReader to metoda read(). Metoda ta przesuwa wewnętrzny wskaźnik do kolejnego węzła oraz zwraca true. Jeśli osiągnięty został ostatni węzeł dokumentu, metoda zwróci false.

Poza opisaną powyżej metodą należy znać następujące właściwości:

  • name – nazwa aktualnego węzła
  • nodeType – rodzaj aktualnego węzła (w dalszej części artykułu dokładniej opiszę typy węzłów)
  • value – wartość aktualnego węzła

Do tego dochodzą jeszcze stałe:

  • ELEMENT – węzeł otwierający, np.
  • END_ELEMENT – węzeł zamykający, np.
  • TEXT – węzeł tekstowy zawarty między węzeł otwierający i zamykający, np. Lorem ipsum...
  • CDATA – węzeł tekstowy zawarty między znacznikami CDATA, np. <![CDATA[Lorem ipsum...]]>

Przykład zastosowania XMLReader

Dokument XML (zapisany w plik.xml).

<?xml version="1.0"?>
<note>
	<date>2007-06-24</date>
	<heading><![CDATA[Przypomnienie]]></heading>
	<body><![CDATA[Jedziemy na narty!]]></body>
</note>

Kod PHP odpowiedzialny za parsowanie pliku XML.

$reader = new XMLReader();
$reader->open('plik.xml');
 
while($reader->read()) {
	echo $reader->nodeType.' '.$reader->name.' '.$reader->value.'<br />';
}
 
$reader->close();

Wynik parsowania.

1 note 
14 #text 
1 date 
3 #text 2007-06-24
15 date 
14 #text 
1 heading 
3 #text Przypomnienie
15 heading 
14 #text 
1 body 
3 #text Jedziemy na narty!
15 body 
14 #text 
15 note

Pobieranie danych zapisanych w dokumencie XML

Powyższy przykład można bardzo łatwo zmodyfikować, by zwracał bardziej przyjazne dane.
Począwszy od poniższego przykładu, wszystkie kolejne przykłady, nie będą zawierać kodu odpowiedzialnego za utworzenie instancji obiektu XMLReader.

while($reader->read()) {
	if($reader->nodeType == XMLReader::ELEMENT) {
		$name = $reader->name;
	}
 
	if($reader->nodeType == XMLReader::TEXT || 
	   $reader->nodeType == XMLReader::CDATA)
	{
		echo $name.' '.$reader->value.'<br />';
	}
}

Zwracane wartości

date 2007-06-24
heading Przypomnienie
body Jedziemy na narty!

Parsowanie złożonych dokumentów XML

Problem parsowania bardziej skomplikowanych dokumentów XML można rozwiązać na kilka sposobów. W najprostszej postaci wystarczy kilka instrukcji warukowych if ... else lub switch.

Dokument XML

<?xml version="1.0"?>
<notes>
	<note>
		<date>2007-06-24</date>
		<heading><![CDATA[Przypomnienie]]></heading>
		<body><![CDATA[Jedziemy na narty!]]></body>
	</note>
	<note>
		<date>2008-01-04</date>
		<heading><![CDATA[Rocznica]]></heading>
		<body><![CDATA[Czyjeś urodziny]]></body>
	</note>
</notes>

Skyrpt parsujący powyższy dokument

while($reader->read()) {
	if($reader->nodeType == XMLReader::ELEMENT) {
		$name = $reader->name;
	}
 
	if($reader->nodeType == XMLReader::ELEMENT && 
	$reader->name == 'note')
	{
		echo '<fieldset>';
	}
 
	if($reader->nodeType == XMLReader::TEXT || 
	   $reader->nodeType == XMLReader::CDATA)
	{
		if($name == 'heading') {
			echo '<legend>'.$reader->value.'</legend>';
		}
		else {
			echo $reader->value.'<br />';
		}
	}
 
	if($reader->nodeType == XMLReader::END_ELEMENT && 
	$reader->name == 'note')
	{
		echo '</fieldset>';
	}
}

Powyższy przykład można zmodyfikować tak, aby dane zawarte w dokumencie XML zostały zapisane do tablicy.

$notes = array();
$counter = 0;
while($reader->read()) {
	if($reader->nodeType == XMLReader::ELEMENT) {
		$name = $reader->name;
	}
 
	if($reader->nodeType == XMLReader::ELEMENT && 
	$reader->name == 'note')
	{
		$notes[$counter] = array();
	}
 
	if($reader->nodeType == XMLReader::TEXT || 
	   $reader->nodeType == XMLReader::CDATA)
	{
		$notes[$counter][$name] = $reader->value;
	}
 
	if($reader->nodeType == XMLReader::END_ELEMENT && 
	$reader->name == 'note')
	{
		$counter++;
	}
}

Zawartość utworzonej tablicy

Array
(
    [0] => Array
        (
            [date] => 2007-06-24
            [heading] => Przypomnienie
            [body] => Jedziemy na narty!
        )
 
    [1] => Array
        (
            [date] => 2008-01-04
            [heading] => Rocznica
            [body] => Czyjeś urodziny
        )
)

"Życiowy" przykład

Poniższy przykład prezentuje, jak przy użyciu XMLReader, można parsować dokumenty XML, będące zapisane w postaci RSS. Jako przykład posłuży nowości.

while($reader->read()) {
	if($reader->nodeType == XMLReader::ELEMENT) {
		$name = $reader->name;
	}
 
	if($reader->nodeType == XMLReader::ELEMENT && 
	$reader->name == 'item')
	{
		echo '<hr />';
	}
 
	if($reader->nodeType == XMLReader::TEXT || 
	   $reader->nodeType == XMLReader::CDATA)
	{
		switch($name) {
			case 'link':
				echo '<a href="'.$reader->value.'">zobacz więcej &raquo;</a><br />';
				break;
			case 'title';
				echo '<b>'.$reader->value.'</b> ';
				break;
			case 'description':
				echo $reader->value.'<br />';
				break;
			case 'pubDate':
				echo $reader->value.'<br />';
				break;
		}
	}
}