Artykuł

lut 05 2012
0

Czytanie plików XML z rozszerzeniem SimpleXML w PHP

Tworząc stronę WWW trochę bardziej rozbudowaną niż prosta, jednostronicowa wizytówka, zwykle niezbędne staje się podpięcie dodatkowego źródła danych. Niezależnie, czy jest to baza danych, źródło plikowe, czy też może XML, konieczne staje się oprogramowanie akcji pobierania danych z takiego źródła. Dziś właśnie, chciałbym zająć się takim tematem z perspektywy właśnie tego ostatniego formatu oraz języka skryptowego PHP.

Celem do realizacji tego zadania, będzie rozszerzenie SimpleXML języka PHP, które w bardzo prosty sposób pozwala na odczytywanie zarówno prostych XML jak i tych bardziej złożonych zawierających przestrzenie nazw.

Jeśli interesuje Was ten temat, to nie pozostaje mi nic innego jak zaprosić Was do dalszej części wpisu;)

Studium przypadku

O SimpleXML słyszałem już dość dawno, jednak prawdę powiedziawszy dotychczas nie miałem czasu mu się bliżej przyjrzeć. Zawodowo pracuje jako programista .Net, a czymkolwiek innym zajmuje się ewentualnie po godzinach, w czasie wolnym od regularnej pracy.

Ostatnio pojawiła się właśnie potrzeba/konieczność pogrzebania co nieco w PHP, w związku ze zmianą jednej stworzonych przeze mnie wcześniej stron. Głównym zadaniem było stworzenie nowej galerii. Ponieważ jest to strona firmowa, nie korzystała dotychczas z żadnego gotowego rozwiązania.

Aktualizacja galerii zachodzić będzie raczej rzadko, ale jednak powinna istnieć taka możliwość. Dlatego też, postanowiłem wykorzystać rozwiązanie pośrednie pomiędzy brakiem galerii, a zapisywaniem informacji o niej w bazie danych za pomocą jakiegoś rozwiniętego panelu administracyjnego.

Rozwiązaniem tym został plik XML.

W pliku XML zawarto informacje tylko związane z obrazkami. Podzielono go na sekcje, a każda z nich odnosi się do obrazów zapisanych w innym folderze, a tym samym dla innej podstrony. Można powiedzieć, że w tym przypadku folder pełnił rolę unikalnego klucza.

Oprócz nazwy folderu, w każdej sekcji znajduje się również węzeł images, który opakowuje węzły image reprezentujące poszczególne obrazy w tej sekcji/folderze.

Każdy z obrazów, posiada dodatkowo trzy właściwości:

  • Nazwę
  • Rozszerzenie
  • Podpis

Oczywiście w tym przypadku można iść na pewne ustępstwa. Kluczowym elementem na pewno będzie nazwa. Rozszerzenie możemy pominąć, ponieważ możemy założyć, że domyślnie będzie to jpg, a jeśli będzie inaczej, to po prostu skorzystamy z dodatkowej właściwości. Również podpis będzie nieobowiązkowy.

W moim produkcyjnym rozwiązaniu zastosowałem również przestrzenie nazw.

Jako środek, do realizacji celu wybrałem rozszerzenie języka PHP - SimpleXML. Swoją drogą, nie wiedziałem czy nazwać to rozszerzeniem, biblioteką, klasą, czy jeszcze czymś innym, ponieważ wszystkie wyżej wymienione nazwy zostały użyte w tym kontekście w materiałach dostępnych w sieci.

Na korzyść tego rozszerzenia przemawia fakt, że jest ono niezwykle proste w obsłudze i pozwalać spojrzeć na XML w ujęciu obiektowym.

Skoro zapoznałem Was już z zasadniczymi założeniami analizowanego rozwiązania, to zapraszam teraz do kolejnych punktów, w których pochylimy się nad implementacją rozwiązania. Chociaż sam wybrałem plik z przestrzeniami nazw, zacznę jednak od przykładu, który jest ich pozbawiony.

XML bez przestrzeni nazw

Choć przestrzenie nazw są bardzo pożyteczne i w dłuższej perspektywie czasu mogą pozwolić uniknąć bałaganu, to jednak wciąż stosunkowo mało osób jest do nich przekonanych. W gruncie rzeczy, często prowadzą one jednak do pewnego skomplikowania kodu (tak jest również w tym przypadku), dlatego też jestem w stanie zrozumieć, jeśli ktoś kto tworzy prostą aplikację/stronę, nie skorzysta z przestrzeni nazw:P

Wróćmy jednak do meritum tematu i spójrzmy na przykładowy plik XML pozbawiony przestrzeni nazw:

<?xml version="1.0" encoding="UTF-8"?>
<imagessections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.mywww.pl/images Images.xsd ">
	<imagessection>
		<folder>abc</folder>
		<images>
			<image>
				<name>01</name>
				<extension>jpg</extension>
				<caption>Podpis</caption>
			</image>
			<image>
				<name>02</name>
				<extension>png</extension>
				<caption>Podpis2</caption>
			</image>
		</images>
	</imagessection>
	<imagessection>
		<folder>def</folder>
		<images>
			<image>
				<name>01</name>
				<extension>jpg</extension>
				<caption>Podpis3</caption>
			</image>
		</images>
	</imagessection>
</imagessections>

Jak widzicie, układ węzłów jest spójny z tym co napisałem w akapicie Studium przypadku.

Skoro mamy już plik danych, należy zadbać o odpowiednie rozwiązanie programistyczne.

Wpierw, musimy dobrać się do pliku. W tym celu, wystarczą nam dwie, proste linijki kodu:

$oFile = file_get_contents('xmlwithoutns.xml');
$oSimpleXmlObject = new SimpleXmlElement($oFile);

W pierwszej z nich, pobieramy zawartość pliku do zmiennej $oFile, by następnie na jej podstawie utworzyć obiekt klasy SimpleXmlElement. Te dwie linie wystarczą, żeby przedstawić nasz plik XML w postaci obiektowej.

Teraz wystarczy za pomocą standardowych strzałek odnieść się do odpowiedniego węzła i odczytać jego dane, np.

print_r($oSimpleXmlObject->imagessection);

Jednak w moim przypadku, problem był troszkę bardziej złożony. Po pierwsze, od razu chciałem dostać się do węzła imagessection przewidzianego do odpowiedniego folderu, a po drugie chciałem szybko przeczytać dane wszystkich obrazów w nim zawartych.

Do tego celu świetnie nadaje się XPath, który ku naszej radości jest obsługiwany w rozszerzeniu SimpleXML:)

Rozwiązanie problemu poniżej:

<?php
$oFile = file_get_contents('xmlwithoutns.xml');
$oSimpleXmlObject = new SimpleXmlElement($oFile);
$oImageSection = $oSimpleXmlObject->xpath(
	'/imagessections/imagessection/folder[.="abc"]/parent::*/images');
if(1 != sizeof($oImageSection))
{
	return(null);	
}
foreach($oImageSection[0]->children() as $oImage)
{
	echo 'Nazwa: '.$oImage->name.', rozszerzenie: '.$oImage->extension.
		', podpis: '.$oImage->caption.'<br />';
}
?>

Linie 2 i 3 powinny brzmieć już dla Was znajomo, dlatego od razu przejdziemy do wyrażenia XPath zawartego w liniach 4-5. W tym przypadku działają wszystkie standardowe konstrukcje XPath, a ta którą ja zaprezentowałem, szuka węzła folder o nazwie abc, by następnie znaleźć jego nadrzędny węzeł imagessection (konstrukcja parent::*) oraz wewnątrz tego węzła wejść głębiej do images. Wiem, że mogło to zabrzmieć z początku trochę skomplikowanie, ale w gruncie rzeczy jest to całkiem proste, jeśli chwilę na to popatrzymy i poszukamy odniesienia w drzewie XML;)

Zapytanie XPath, powinno zwrócić tablicę węzłów images spełniających nasze kryterium wyszukiwania. Ponieważ założyliśmy, że każdy folder użyty w zapytaniu, musi być unikalny, zatem każda liczba wielkość tablicy różna od 1 jest zła. Sprawdzamy to w liniach 6-9.

Ostatnim krokiem, jest wyświetlenie informacji o zaczytanych obrazach. Czynimy to w pętli foreach (10-14) pobierając wszystkie węzły dzieci dla naszego węzła images za pomocą funkcji children. Otrzymujemy w ten sposób kolejne węzły image, dla których w sposób obiektowy, możemy pobrać nazwę, rozszerzenie oraz podpis.

Oczywiście powyższy kod jest bardzo roboczy, a dane lądują prosto na ekran. Zamiast tego warto wszystko obudować w sposób bardziej obiektowy, a zwrócone dane, można upakować do własnych obiektów i przekazać gdzieś dalej.

XML z przestrzenią nazw

Spróbujemy teraz rozpatrzyć przypadek korzystający z przestrzeni nazw. Cel jest analogiczny jak w poprzednim przypadku. Zacznijmy najpierw od poprawionego XMLa:

<?xml version="1.0" encoding="UTF-8"?>
<us:imagessections xmlns:us="http://www.mywww.pl/images" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.mywww.pl/images Images.xsd ">
	<us:imagessection>
		<us:folder>abc</us:folder>
		<us:images>
			<us:image>
				<us:name>01</us:name>
				<us:extension>jpg</us:extension>
				<us:caption>Podpis</us:caption>
			</us:image>
			<us:image>
				<us:name>02</us:name>
				<us:extension>png</us:extension>
				<us:caption>Podpis2</us:caption>
			</us:image>
		</us:images>
	</us:imagessection>
	<us:imagessection>
		<us:folder>def</us:folder>
		<us:images>
			<us:image>
				<us:name>01</us:name>
				<us:extension>jpg</us:extension>
				<us:caption>Podpis3</us:caption>
			</us:image>
		</us:images>
	</us:imagessection>
</us:imagessections>

Zmiany siłą rzeczy musiały dotknąć całego pliku XML, jednak sama struktura nieuległa zmianie. W linii 2, zadeklarowaliśmy przedrostek us używany do reprezentacji naszej przestrzeni. Pojawia się on we wszystkich węzłach zawartych w pliku.

Trochę modyfikacji, pojawia się również w samym skrypcie PHP:

<?php
$oFile = file_get_contents('xmlns.xml');
$oSimpleXmlObject = new SimpleXmlElement($oFile);
$oNamespaces = $oSimpleXmlObject->getNameSpaces(true);
$oImageSection = $oSimpleXmlObject->xpath(
	'/us:imagessections/us:imagessection/us:folder[.="abc"]/parent::*/us:images');
if(1 != sizeof($oImageSection))
{
	return(null);	
}
foreach($oImageSection[0]->children($oNamespaces['us']) as $oImage)
{
	echo 'Nazwa: '.$oImage->name.', rozszerzenie: '.$oImage->extension.
		', podpis: '.$oImage->caption.'<br />';
}
?>

Po pierwsze, w linii 4, pobieramy tablicę zawierającą wszystkie przestrzenie nazw zawarte w dokumencie. Jest to tablica asocjacyjna, a kluczami tej tablicy są zdefiniowane prefiksy przestrzeni nazw.

W liniach 5-6, obserwujemy zmodyfikowane zapytanie XPath. W tym przypadku wystarczyło dodać prefiksy do nazw węzłów, by rozwiązać problem.

Ostatnia modyfikacja dotyczy funkcji children wykorzystywanej w pętli foreach. W poprzednim przykładzie, nie przekazaliśmy jej żadnego argumentu. Tym razem, podajemy wybraną przestrzeń nazw (us) i to wystarczy by uzyskać dostęp do obiektów image.

Podsumowanie

Zaprezentowane wyżej rozwiązanie, to oczywiście tylko wycinek możliwości rozszerzenia SimpleXML - w tym przypadku ściśle dopasowany do określonego problemu. Sama biblioteka ma znacznie więcej możliwości;)

W razie jakichkolwiek pytań/sugestii - zapraszam do komentarzy;)

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus