www.phpsolmag.org
62
PHP Solutions Nr 5/2006
Dla zaawansowanych
www.phpsolmag.org
63
PEAR
PHP Solutions Nr 5/2006
Dla zaawansowanych
XML i PHP w praktyce
Guillaume Ponçon
S
tandard XML, czyli eXtensible
Markup Language został określo-
ny przez World Wide Web Con-
sortium (W3C). Obecnie jest przeważnie
wykorzystywany jako format gromadze-
nia i wymiany danych (np. SXW, ODT,
RDF, RSS) oraz tworzenia specjalistycz-
nych języków opartych na znacznikach
– jest więc metajęzykiem. Każdy doku-
ment XML zawiera dane uporządkowa-
ne w hierarchii drzewiastej. Spójrzmy na
Listing 1: przedstawiamy na nim przykła-
dowy kod XML. Jego struktura opiera się
na znacznikach zawartych w nawiasach
trójkątnych (podobnie jak w przypadku
HTML-a). Para znaczników, np.
<title>
i
</title>
określa element dokumentu
XML-owego, który może zawierać tekst
lub kolejne elementy (zwane podrzędny-
mi lub potomnymi). Składnia każdego do-
kumentu XML musi być ściśle przestrze-
gana (nie wolno np. pozostawiać niedo-
mkniętych znaczników; można też okre-
ślić inne zasady korzystając z plików
Bazy danych, dokumenty biurowe, RSS: coraz
więcej formatów gromadzenia i przesyłania
danych opiera się na XML-u. Jego główną
zaletą jest łatwość tworzenia i przetwarzania
dokumentów XML niezależnie od platformy
sprzętowej i systemowej. Jako programiści PHP,
mamy szerokie możliwości wykorzystania XML-
a przy użyciu co najmniej kilku technik...
DTD czy XML Schema), gdyż w innym
wypadku program odczytujący ten doku-
ment zgłosi błąd.
Techniki przetwarzania
XML-a w PHP
Pisząc skrypty w języku PHP możemy
swobodnie korzystać z dokumentów XML-
owych. Do ich przetwarzania służą roz-
szerzenia języka PHP o nazwach: SAX,
W SIECI
• http://www.w3.org/XML/
– oficjalna specyfikacja stan-
dardu XML
• http://www.saxproject.org/
– strona projektu SAX
• http://www.w3.org/DOM/
– oficjalna specyfikacja stan-
dardu DOM
• http://books.evc-cit.info/
odbook/book.html – oficjalna
specyfikacja formatu Open-
Document
Stopień trudności: ll
l
Co należy wiedzieć...
Przydatna będzie znajomość podstaw
programowania obiektowego w PHP5.
Co obiecujemy...
Przedstawimy zasady działania roz-
szerzeń języka PHP: DOM, SAX oraz
SimpleXML oraz pokażemy, jak korzy-
stając z SimpleXML tworzyć i odczyty-
wać dokumenty XML-owe, w tym pliki
OpenOffice.org i dane programu Gant-
tproject.
www.phpsolmag.org
62
PHP Solutions Nr 5/2006
Dla zaawansowanych
www.phpsolmag.org
63
PEAR
PHP Solutions Nr 5/2006
Dla zaawansowanych
DOM-XML (PHP4), DOM (PHP5) oraz
SimpleXML (tylko PHP5). Omówimy po-
krótce SAX, DOM-XML oraz SimpleXML,
podając przykłady wykorzystania każdego
z tych rozszerzeń.
SAX
SAX (http://www.saxproject.org/, http://
www.php.net/xml).oznacza Simple API for
XML i umożliwia sekwencyjny odczyt do-
kumentów XML. Pozwala na odczyt prak-
tycznie każdego rodzaju dokumentów
XML, nawet tych, które zostały utworzo-
ne niepoprawnie. SAX nie umożliwia nato-
miast zapisu ani modyfikacji danych.
Na Listingu 2 przedstawiamy skrypt
służący do odczytu dokumentu XML-owe-
go z użyciem techniki SAX. Zaczynamy od
utworzenia parsera
($xml_parser
), który
jest zasobem (ang. resource) tworzonym
przy użyciu funkcji
xml_parser_create()
i
reprezentującym dokument XML, który bę-
dziemy przetwarzali. Do tego ostatniego w
technice SAX służą funkcje zwane handle-
rami (uchwytami), które są wywoływane
przez język PHP podczas odczytu nasze-
go dokumentu. Musimy je sami utworzyć,
zanim się do nich odwołamy. W naszym
przykładzie zdefiniujemy najpierw dwa
podstawowe uchwyty: początku (
startE-
lement()
) i końca (
endElement()
) każdego
elementu XML-owego, uruchamiane od-
powiednio, gdy parser natrafi na znacznik
rozpoczynający (np.
<author>
) i kończący
(np.
</author>
) dany element. W przypad-
ku napotkania elementu danych pomiędzy
dwoma znacznikami XML wywoływany
jest uchwyt
dataHandler
. Handlery począt-
ku i końca elementu definiujemy przy po-
mocy funkcji
xml_set_element_handler()
,
a uchwyt elementu danych używając
xml_
set_character_data_handler()
. Przetwa-
rzanie dokumentu XML rozpoczniemy ko-
rzystając z funkcji
xml_parse()
, jako jej
parametry podając: instancję parsera (za-
sób
$xml_parser
), źródło dokumentu w
wersji tekstowej (np. odczytane z pliku; my
użyjemy zmiennej
$xml
, w której umieści-
my fragment kodu z Listingu 1. Na koniec,
zwalniamy zasób
$xml_parser
korzystając
z funkcji
xml_parser_free()
.
DOM-XML
Rozszerzenie DOM-XML umożliwia prze-
twarzanie plików XML w PHP zgodnie ze
standardem DOM (skrót od Document
Object Model). DOM-XML pozwala za-
Rysunek 1.
Okno programu Ganttproject: po lewej stronie wykres Gantta
Instalacja
Aby można było uruchomić przykłady z tego artykułu, zalecane jest posiadanie PHP
5.1.4 lub nowszego. Pod systemem Windows możemy w tym celu skorzystać z ze-
stawu typu AMP (Apache, MySQL, PHP) czy też 3 w 1 o nazwie WampServer (http:
//www.wampserver.com). Jego instalacja jest trywialna i odbywa się przy użyciu pro-
stego wizarda.
Listing 1.
Dokument XML Simple (opis książki)
<
document id=
"12"
>
<
author
>
Guillaume Ponçon
<
/author
>
<
title
>
Best practices PHP 5
<
/title
>
<
chapter
>
<
title
>
Strumienie XML
<
/title
>
<
paragraph type=
"introduction"
>
...
<
/paragraph
>
<
/chapter
>
<
/document
>
Listing 2.
Przeglądamy dokument XML-owy przy użyciu SAX (wyświetlanie
znaczników i zawartości)
function
startElement
(
$parser
,
$name
,
$attrs
)
{
echo
$name
.
"
\n
"
;
}
function
endElement
(
$parser
,
$name
)
{
echo
$name
.
"
\n
"
;
}
function
dataHandler
(
$parser
,
$data
)
{
echo
'-> '
.
trim
(
$data
)
.
"
\n
"
;
}
$xml_parser
= xml_parser_create
()
;
$xml
='
<
document id=
"12"
><
author
>
Guillaume Ponçon
<
/author
><
/document
>
';
xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, '
dataHandler'
)
;
xml_parse
(
$xml_parser
,
$xml
, true
)
;
xml_parser_free
(
$xml_parser
)
;
Listing 3.
Przykład odczytu dokumentu XML przy użyciu DOMXML (wyświetlanie
informacji zawartych na Listingu 1)
$dom
=
new
DOMDocument
()
;
$dom
-
>
LoadXML
(
$xml
)
;
$title
=
$dom
-
>
getElementsByTagName
(
'title'
)
;
echo
"Rozdziały książki "
.
$title
-
>
item
(
0
)
-
>
nodeValue .
" :
\n
"
;
foreach
(
$dom
-
>
getElementsByTagName
(
'chapter'
)
as
$element
)
{
$titles
=
$element
-
>
getElementsByTagName
(
'title'
)
;
echo
"
\n
- "
.
$titles
-
>
item
(
0
)
-
>
nodeValue .
"
\n
"
;
$paragraphs
=
$element
-
>
getElementsByTagName
(
'paragraph'
)
;
foreach
(
$paragraphs
as
$paragraph
)
{
echo
' * '
.
$paragraph
-
>
nodeValue .
"
\n
"
;
}
}
www.phpsolmag.org
64
PHP Solutions Nr 5/2006
Dla zaawansowanych
równo na odczyt, jak i zapis (tworzenie i
modyfikowanie) dokumentów XML. Stan-
dard DOM znajduje zastosowanie nie tyl-
ko w PHP, ale także w innych językach
(m.in. Python, Java, JavaScript). Głów-
ną ideą DOM jest to, że struktura XML
jest traktowana jako hierarchiczne drze-
wo węzłów (ang. nodes), z których każ-
dy jest reprezentowany jako obiekt języka
PHP (lub innego języka, dla którego istnie-
je implementacja DOM). W PHP obsługę
standardu DOM umożliwiają rozszerzenia
DOM-XML (PHP4, http://www.php.net/
domxml) i DOM (PHP5). Jak już powie-
dzieliśmy, skorzystamy z tego pierwszego.
Na Listingu 3 przedstawiamy przy-
kład odczytu, a na Listingu 4 przykład
zapisu dokumentu XML za pomocą
DOM. W obu przypadkach zaczynamy
tworząc obiekt
$dom
klasy
DOMDocument
(musimy go utworzyć), do którego bę-
dziemy się odwoływać przy wykonywa-
niu operacji na dokumencie. Następnie
ładujemy kod XML w wersji tekstowej (ze
zmiennej łańcuchowej
$xml
) korzysta-
jąc z metody
LoadXML
obiektu
$dom
. Aby
odczytać lub zmodyfikować dany wę-
zeł, musimy go najpierw odnaleźć meto-
dą
getElementsByTagName()
, tworząc no-
wy obiekt (najlepiej o nazwie odpowiada-
jącej nazwie węzła). Następnie używamy
atrybutów i metod tego obiektu, umożli-
wiających odczytywanie i zapisywanie
danych w zaznaczonym węźle. My wy-
korzystamy omówioną metodę do zna-
lezienia węzła
title
(element pomię-
dzy znacznikami
<title>
i
</title>
; pa-
miętajmy, że posługujemy się cały czas
przykładowym kodem XML z Listingu 1).
Należy wiedzieć, że omówiona meto-
da
getElementsByTagName()
odnajdu-
je wszystkie węzły określone daną parą
znaczników (np. <title>...</title>) i spo-
rządza ich listę, więc nawet, gdy istnieje
tylko jeden węzeł o wybranej nazwie (jak
u nas), musimy go wskazać (np. używa-
jąc metody item(), choć istnieją też inne
sposoby). Analogicznie wyświetlimy listę
rozdziałów (element
chapter
) i akapitów
(
paragraph
). Tekst zapisany w danym
węźle odczytamy i zmodyfikujemy korzy-
stając z atrybutu
nodeValue
.
Utworznie nowego węzła nastę-
puje poprzez użycie aż dwóch metod:
createElement()
, która pozwala na utwo-
rzenie obiektu węzła o określonej nazwie
(u nas
category
) i zawartości (u nas
PHP
),
który go reprezentuje oraz
appendChild()
,
która z kolei pozwala dodać ten element w
odpowiednim miejscu drzewa węzłów. W
naszym przypadku, element ten będzie
podrzędny wobec pierwszego odnalezio-
nego elementu drzewa (
title
), niższego
o jeden poziom od korzenia drzewa (u nas
document
). Wreszcie, do zapisu gotowe-
go dokumentu XML w pliku służy metoda
save()
obiektu
$dom
.
Jak widać, rozwiązanie DOM jest sku-
teczne i spójne logicznie, ale niestety bar-
dzo rozwlekłe i wymagające dużo kodu.
Dlatego też jest relatywnie rzadko sto-
sowane, choć zawiera bardzo pożytecz-
ne narzędzia, takie jak np. XSLT (połą-
czenie dokumentu XML i arkusza stylów
XSL) czy XPath (język zapytań umożli-
wiający wyszukiwanie elementów i warto-
ści w dokumentach XML, będący niejako
XML-owym odpowiednikiem SQL-a).
SimpleXML
SimpleXML (http://www.php.net/simplexml)
to rozszerzenie, które pojawiło się wraz
z PHP5. Jest bardzo łatwe w obsłudze i
Rysunek 2.
Skrypt odczytujący dokument utworzony przez program Ganttproject i wy-
świetlający jego strukturę
RSS – podstawy
RSS (Really Simple Syndication, znane dawniej jako Rich Site Summary czy RDF
Site Summary) to popularny i prosty format strumienia przesyłania newsów (prze-
ważnie krótkich) w Internecie. Zestawy newsów są dostępne w postaci plików okre-
ślanych jako kanały (ang. channels), strumienie lub feeds. Każdy news zawiera na
ogół tytuł, krótki opis, rozwinięcie i link do dłuższego artykułu. RSS opiera się na
standardzie XML. Sajty udostępniające wiadomości w postaci RSS (głównie gazety,
magazyny i portale internetowe, takiej jak Yahoo!, Slashdot (http://rss.slashdot.org/
Slashdot/slashdot) czy freshmeat.net (http://rss.freshmeat.net/freshmeat/feeds/fm-re-
leases-global)) są zwykle oznaczone ikonką zawierającą skrót RSS, RDF lub XML
na stronie głównej.
Newsy RSS mogą być następnie pobierane i wyświetlane przez czytnik umiesz-
czony na komputerze klienta lub na dowolnej witrynie internetowej, takiej jak np. pry-
watna strona domowa czy blog, a także niektóre programy pocztowe (np. Thunder-
bird). Wyświetlanie zawartości kanałów RSS jest możliwe również przy użyciu prze-
glądarki internetowej: po wpisaniu adresu RSS powinna się w niej pojawić pełna
struktura i zawartość pliku XML z newsami.
Listing 4.
Modyfikujemy dokument XML-owy korzystając z DOM-XML (dodanie
węzła category w dokumencie XML z Listingu 1)
$dom
=
new
DOMDocument
()
;
$dom
-
>
LoadXML
(
$xml
)
;
$title
=
$dom
-
>
getElementsByTagName
(
'title'
)
;
$title
-
>
item
(
0
)
-
>
nodeValue =
"Best practices in PHP5"
;
$element
=
$dom
-
>
createElement
(
'category'
,
'PHP'
)
;
$rootElement
=
$dom
-
>
getElementsByTagName
(
'document'
)
;
$rootElement
-
>
item
(
0
)
-
>
appendchild
(
$element
)
;
$dom
-
>
save
(
'/tmp/document.xml'
)
;
echo
file_get_contents
(
'/tmp/document.xml'
)
;
www.phpsolmag.org
65
PHP Solutions Nr 5/2006
Dla zaawansowanych
umożliwia zarówno odczyt (w PHP 5.0),
jak i zapis (w PHP 5.1.4) dokumentów
XML. Jest domyślnie skompilowane w
każdej z tych dystrybucji PHP.
Na Listingu 5 pokazujemy, jak od-
czytywać zawartość dokumentu XML,
a na Listingu 6, jak ją modyfikować. W
obu przypadkach korzystanie z Simple-
XML zaczynamy od utworzenia obiek-
tu
$simpleXml
, do czego służy funkcja
simplexml_load_string()
, która tworzy
dokument w oparciu o kod XML w po-
staci tekstowej (zawarty w zmiennej łań-
cuchowej). Podobnie, jak w przypadku
DOM, w SimpleXML struktura dokumen-
tu XML jest reprezentowana przez ze-
staw obiektów języka PHP, tyle, że są
one tworzone automatycznie i mają na-
zwy odpowiadające nazwom węzłów
(elementów) naszego dokumentu, a ko-
rzystanie z nich jest znacznie prostsze,
co możemy sami zobaczyć porównując
długość kodu w obu przypadkach: pod-
czas, gdy w DOM trzeba było tworzyć
rozwlekły skrypt, w SimpleXML wystar-
czy parę linijek! Co więcej, z elementów
dokumentu XML korzystamy przeważnie
(zarówno do odczytu, jak i zapisu) uży-
wając atrybutów (pól) obiektów poszcze-
gólnych węzłów. Pola te mogą zawierać
zarówno treść tekstową, jak i listy ele-
mentów podrzędnych, które możemy ite-
rować korzystając z instrukcji
foreach
.
Znacznie prostsze niż w DOM jest rów-
nież dodawanie nowych węzłów: musimy
jedynie skorzystać z metody
addChild()
i gotowe.
Odczyt strumienia RSS
za pomocą SimpleXML
RSS to standard przesyłania wiadomo-
ści w Internecie, o którym więcej mówi-
my w Ramce RSS – podstawy. Na Li-
stingu 7 przedstawiamy przykładowy
plik RSS, a na Listingu 8 skrypt odczy-
tujący strumień RSS spod adresu http:
//rss.freshmeat.net/freshmeat/feeds/fm-
releases-global, czyli informacje o no-
wych projektach programistycznych, któ-
re zostały zamieszczone na witrynie fre-
shmeat.net. Ładowanie zestawu wiado-
mości następuje poprzez zwykłe załado-
wanie pliku znajdującego się pod wspo-
mnianym adresem, po czym przystępu-
jemy do ich wyświetlania w oknie prze-
glądarki: zaczynamy od wypisania tytułu
kanału (element
<title>
, podrzędny wo-
bec
<channel>
). Następnie iterujemy wia-
domości, z których każda jest elemen-
tem podrzędnym wobec
<channel>
ozna-
czonym jako
<item>
i wyświetlamy ich
elementy potomne:
<description>
oraz
<date>
. Korzeń (root) dokumentu XML
jest oznaczony jako
<rss>
(czasem też
jako
<RDF>
) i jest reprezentowany przez
sam obiekt
$rss
.
Po uruchomieniu tego skryptu zoba-
czymy zestaw wiadomości – zauważmy,
że niektóre z nich zawierają również gra-
fikę.
Manipulujemy
wykresami Gantta za
pomocą SimpleXML
oraz Ganttproject
Wykres Gantta (ang. Gantt chart) to po-
pularna metoda wizualizacji etapów i po-
Czym jest Ganttproject?
Ganttproject (Rysunek 1) jest napisanym w Javie opensourcowym programem do
sporządzania wykresów Gantta. Możemy go pobrać spod adresu http://ganttproje
ct.sourceforge.net. Zgodnie ze specyfiką wykresów Gantta, Ganttproject pozwa-
la na wizualizację organizacji zadań i zasobów przydzielonych do wybranego pro-
jektu w czasie. Jedną z podstawowych funkcji programu Ganttproject jest tworze-
nie zadań, które możemy układać w kategorie. Narzędzie to pozwala nam również
na śledzenie postępu zadań, sporządzanie listy zasobów (osób pracujących nad
projektem), wyświetlanie wykresu strat oraz wykonywanie wielu innych użytecz-
nych operacji.
Rysunek 3.
Przykład pliku tekstowego stworzonego w edytorze OpenOffice.org Writer
Listing 5.
Odczyt dokumentu przy wykorzystaniu SimpleXML (wyświetlanie infor-
macji zawartych na Listingu 1)
$simpleXml
= simplexml_load_string
(
$xml
)
;
echo
'+ '
.
$simpleXml
-
>
title .
"
\n
"
;
echo
' by '
.
$simpleXml
-
>
author.
"
\n\n
"
;
foreach
(
$simpleXml
-
>
chapter as
$chapter
){
echo
'- '
.
$chapter
-
>
title .
"
\n
"
;
foreach
(
$chapter
-
>
paragraph as
$paragraph
){
echo
' -> '
.
$paragraph
.
"
\n
"
;
}
echo
"
\n
"
;
}
Listing 6.
Przykład modyfikacji dokumentu XML-oweog za pomocą SimpleXML
(dodanie rozdziału do dokumentu XML z Listingu 1)
$simpleXml
= simplexml_load_string
(
$xml
)
;
$simpleXml
-
>
title =
"Dobre wprawki w PHP 5"
;
$newChapter
=
$simpleXml
-
>
addChild
(
'chapter'
)
;
$newChapter
-
>
addChild
(
'title'
,
'Programowanie obiektowe'
)
;
$newChapter
-
>
addChild
(
'paragraph'
,
'Programowanie zorientowane obiektowo jest
wspaniałe ...'
)
;
echo
$simpleXml
-
>
asXml
()
;
www.phpsolmag.org
66
PHP Solutions Nr 5/2006
Dla zaawansowanych
stępów wykonania projektu, często sto-
sowana w firmach i innych organiza-
cjach. Istnieje wiele narzędzi służących
do sporządzania takich wykresów; jed-
nym z nich jest opensourcowy program
Ganttproject, o którym mówimy więcej
w Ramce Czym jest Ganttproject?. Po-
nieważ dane programu Ganttproject są
gromadzone w postaci plików XML, więc
korzystanie z nich w skryptach PHP jest
całkiem proste.
Każdemu wykresowi przypisany
jest jeden plik XML, w którym zbierane
są dotyczące go dane, takie jak np. ko-
lor przypisany do każdego zadania. Ko-
rzeniem (rootem) tego pliku jest element
o nazwie
<project>
. Jego atrybuty za-
wierają różne informacje, takie jak da-
ta ostatniego odczytu czy wersja pliku.
Podrzędne wobec
<project>
elementy
pierwszego poziomu stanowią kategorie
informacyjne: kalendarze, zadania, za-
soby, przypisanie zasobów, zwolnienia,
role i różne kategorie ustawień. Elemen-
ty drugiego i następnych poziomów za-
wierają informacje związane z każdą ka-
tegorią.
Odczytujemy wykres Gantta z
programu Ganttproject
Aby odczytać dokument programu Gant-
tproject, napiszemy niewielki skrypt
działający w linii poleceń i korzystają-
cy z SimpleXML (Listing 10). Odczyta
on wszystkie zadania zawarte w pliku,
którego nazwę podajemy jako parametr
w linii poleceń i wyświetli informacje o
każdym z nich: datę początkową, czas
trwania i tytuł. Potrzebujemy na to około
dziesięciu linii kodu. Jak widzimy, nasz
algorytm składa się z funkcji rekurencyj-
nej, dzięki której możemy przetwarzać
kolejne zadania w hierarchii zadań. Na
Rysunku 2 przedstawiamy efekt działa-
nia naszego skryptu: wylistowaną w linii
poleceń hierarchię zadań wraz ze wspo-
mnianymi informacjami (datą początko-
wą, czasem trwania i tytułem).
Modyfikujemy zadanie
utworzone w Ganttproject
Modyfikacja istniejącego zadania pliku da-
nych Ganttproject z użyciem SimpleXML
jest równie prosta: jak widzimy na Listingu
11, potrzebujemy do tego zaledwie trzech
linijek kodu! W naszym przykładzie zmie-
nimy nazwę (parametr
name
) pierwszego
zadania na wykresie Gantta.
Dodajemy zadanie
Aby dodać zadanie do wykresu Gantta,
musimy umieścić nowy element
<task>
w
pliku XML (Listing 12). W przypadku Sim-
pleXML służy do tego metoda
addChild()
,
o której już mówiliśmy. Zaczniemy od za-
ładowania pliku XML (openoffice.xml).
Aby dodać nowe zadanie, będziemy mu-
sieli nadać mu identyfikator będący nu-
merem większym o jeden od ID ostatnio
Czym jest przestrzeń nazw?
Przestrzeń nazw umożliwia podzielenie dużych plików XML na kategorie, co uła-
twia zorientowanie się w jego zawartości i manipulowanie nią. Z użyciem przestrzeni
nazw mamy do czynienia, gdy znacznik w pliku XML-owym składa się z dwóch czę-
ści oddzielonych dwukropkiem (
:
), np.
<text:p>...</text:p>
. Identyfikatorem prze-
strzeni nazw jest słowo kluczowe znajdujące się przed dwukropkiem. Przykładowo,
każdy znacznik zawarty w pliku Open Office content.xml o nazwie
text:p
(więc nale-
żący do przestrzeni nazw
text
), odpowiada jednemu akapitowi tekstu.
Rysunek 4.
Zawartość pliku tekstowego model.odt
Listing 7.
Struktura strumienia RSS
<
rss
>
<
channel
>
<
title
>
Tytuł strumienia wiadomości
<
/title
>
(...)
<
item
>
<
title
>
Tytuł pierwszej wiadomości
<
/title
>
<
description
>
Opis pierwszej wiadomości
<
/description
>
(...)
<
/item
>
<
item
>
(...)
<
/item
>
(...)
<
/channel
>
<
/rss
>
Listing 8.
Odczyt strumienia wiadomości RSS przy użyciu SimpleXML
$rss
= simplexml_load_file
(
'
http://rss.freshmeat.net/freshmeat/feeds/
fm-releases-global
'
)
;
echo
'
<
h1
>
'.$rss->channel->title.'
<
/h1
>
';
foreach ($rss->channel->item as $item) {
echo '
<
h3
>
' . $item->date . '
:
' . $item->title . '
<
/h3
>
';
echo '
<
p
>
'
. $item-
>
description . '
<
/p
>
';
}
www.phpsolmag.org
67
PHP Solutions Nr 5/2006
Dla zaawansowanych
zdefiniowanego zadania. Ponieważ kolej-
ny identyfikator jest zawsze o 1 większy
od poprzedniego, wystarczy znaleźć naj-
większe ID. W tym celu sporządzimy naj-
pierw listę wszystkich zadań korzystając z
xpath, a następnie odnajdziemy najwięk-
szy identyfikator używając funkcji
max()
w pętli
foreach
. Po wykonaniu tych czyn-
ności musimy wydłużyć czas trwania za-
dania macierzystego, którym dla naszego
nowego zadania (które umieścimy na tym
samym poziomie, co
tests
) będzie
Base
.
Teraz dodamy nowe zadanie przy uży-
ciu metody
addChild()
, która jest dostęp-
na dla każdego węzła drzewa dokumentu
w SimpleXML oraz nadamy mu atrybuty:
indentyfikator, nazwę, datę rozpoczęcia,
okres trwania i parametry wyświetlania
(m.in. kolor). Na koniec zapiszemy nasze
modyfikacje w pliku korzystając z funk-
cji wbudowanej PHP
file_put_contents()
oraz metody
asXml()
obiektu
$project
,
który reprezentuje nasz dokument XML-
owy. Tak, jak w poprzednich przykładach,
dzięki zastosowaniu SimpleXML ograni-
czyliśmy zestaw czynności do niezbęd-
nych, unikając pisania dodatkowego ko-
du, które miałoby miejsce np. w przypad-
ku DOM.
Korzystamy z
dokumentów
OpenOffice.org za
pomocą SAX
OpenOffice.org to opensourcowy pa-
kiet biurowy o ciągle rosnącej popular-
ności. Korzystają z niego m.in. firmy, in-
stytucje publiczne, organizacje pozarzą-
dowe i osoby prywatne z całego świata.
Wszystkie formaty dokumentów OpenOf-
fice.org (edytora, arkusza kalkulacyjne-
go, programu do tworzenia prezentacji i
innych są oparte na XML-u. W wersji 2
tego pakietu wprowadzono format Open-
Document, którym się posłużymy w na-
szym artykule.
Kompozycja dokumentu
OpenOffice.org
Każdy dokument OpenOffice.org to w rze-
czywistości skompresowane archiwum za-
wierające pliki XML. Przykładowo, archi-
wum pliku example.odt utworzonego w
OO Writer (edytorze tekstu) będzie za-
wierało tekst oraz style i obrazy osadzo-
ne w tekście.
Głównym plikiem tego archiwum, le-
żącym w jego katalogu głównym jest plik
content.xml, którego przykładową zawar-
tość (fragment) prezentujemy na Listingu
13. Zawiera on dane naszego dokumen-
tu i kilka deklaracji stylów. Zdefiniowane w
dokumencie style tekstowe są zapisane w
pliku styles.xml.
My pokażemy sposób tworzenia tytu-
łu i akapitu.
Odczyt i modyfikacja dokumentu
OpenOffice.org
Zanim będziemy mogli odczytać lub zmo-
dyfikować dokument OpenOffice (test.odt),
musimy wydobyć z archiwum plik con-
tent.xml (Rysunek 3). W tym celu użyjemy
biblioteki
pclzip
, którą pobierzemy spod
adresu http://www.phpconcept.net/pclzip/.
Plik content.xml, którego fragment już
przedstawiliśmy na Listingu 13 składa się
ze znacznika korzenia
office:document
oraz pierwszego poziomu hierarchii XML
Rysunek 5.
Dokument tekstowy OpenOffice.org Writer generowany przez skrypt napi-
sany w PHP
Listing 9.
Zawartość dokumentu XML programu Ganttproject
<
?xml version=
"1.0"
encoding=
"UTF-8"
?
>
<
project name=
"OpenOfficeManager"
company=
"Anaska"
webLink=
"http://www.anaska.com"
(...)
>
<
description/
>
<
view zooming-state=
"default:3"
/
>
<
calendars
>
<!-- właściwości ogólne kalendarza (dni wolne, etc.) -->
<
/calendars
>
<
tasks color=
"#8cb6ce"
>
<
taskproperties
>
<!-- deklaracja typów zadań -->
<
/taskproperties
>
<
task id=
"0"
name=
"kernel"
(...)
>
<
task id=
"1"
name=
"modelisation"
(...)
>
<
depend id=
"2"
type=
"2"
difference=
"0"
hardness=
"Strong"
/
>
<
/task
>
<
task id=
"2"
name=
"development"
(...)
>
<
depend id=
"3"
type=
"2"
difference=
"0"
hardness=
"Strong"
/
>
<
/task
>
<
task id=
"3"
(...)/
>
<
/task
>
<
/tasks
>
<
resources/
>
<
allocations/
>
<
vacations/
>
<
taskdisplaycolumns
>
(...)
<
/taskdisplaycolumns
>
<
previous/
>
<
roles roleset-name=
"Default"
/
>
<
/project
>
www.phpsolmag.org
68
PHP Solutions Nr 5/2006
Dla zaawansowanych
przedstawiającego kategorie zawartości.
W kategorii
office:body
zawarte są dane
naszego dokumentu.
Odczytamy plik content.xml przy uży-
ciu SAX oraz zmodyfikujemy go korzysta-
jąc z SimpleXML i dodając tytuł i akapit.
Gotowy skrypt przedstawiamy na Listingu
14 – składa się on z 4 części:
• wydobycie pliku content.xml zawiera-
jącego zawartość naszego dokumen-
tu z archiwum,
• odczyt pliku content.xml za pomocą
SAX,
• dodanie tytułu i akapitu przy użyciu
SimpleXML,
• zapis pliku na dysk.
Zaczniemy od dołączenia biblioteki pcl-
zip (pclzip.lib.php) i utworzenia obiek-
tu
$zip
klasy
PclZip
, któremu jako pa-
rametr konstruktora przekazujemy na-
zwę archiwum, które chcemy rozpako-
wać (test.odt). Następnie korzystając z
metody
listContent()
tego obiektu prze-
szukamy listę plików zawartych w archi-
wum, aż natrafimy na content.xml. Je-
go ekstrakcji dokonamy używając metody
extractByIndex()
obiektu
$zip
. Mając plik
content.xml poza archiwum, skorzystamy
z PHP-owej funkcji
file_get_contents()
,
aby go załadować.
Czas na przetwarzanie odczytanego
źródła XML przy pomocy SAX. Dużą za-
letą tego rozwiązania jest jego szybkość
i prostota, co zdążyliśmy już częściowo
poznać. Tworzymy więc parser SAX przy
użyciu
xml_parser_create()
i przy pomo-
cy funkcji rozszerzenia SAX
xml_parse_
into_struct()
przekształcimy odczyta-
ną zawartość pliku XML-owego w tabli-
cę. Następnie użyjemy na tej tablicy pę-
tli
foreach
, aby wyekstrahować z niej ty-
tuły i akapity.
Teraz utworzymy obiekt
$xml
, repre-
zentujący ten dokument w SimpleXML. W
tym celu skorzystamy z funkcji
simplexml_
load_file()
i załadujemy ponownie ten
sam plik (content.xml).
Następnie użyjemy metody
xpath()
obiektu
$xml
, aby wydobyć węzeł
office:
text
, który obejmuje zawarty w pliku tekst.
Węzeł ten zawiera ciąg deklaracji akapi-
tów, a pobierzemy go z pierwszego ele-
mentu tablicy zwróconej przez
xpath()
(o
indeksie
0
).
Dodamy teraz nowy tytuł i akapit.
W tym celu musimy utworzyć dwa wę-
zły
text:p
potomne wobec węzła
office:
text
korzystając ze znanej już nam meto-
dy
addChild()
. Dla każdego węzła poda-
jemy jego nazwę (
text:p
), zawartość (No-
wy tytuł i Nowy akapit) i przestrzeń nazw
(
text
). Po dodaniu obu węzłów dodamy
akapitowi atrybut
text:style-name
, który
określa styl (u nas
Standard
).
Przejdźmy teraz do zapisywania pli-
ku. Tak jak poprzednio, użyjemy meto-
dy
asXml()
aby uzyskać tekstową wer-
sję dokumentu XML, którą zapiszemy
w pliku content.xml przy użyciu funkcji
file_put_contents()
.
Pozostała nam już tylko czwarta
część: usunięcie starego pliku content.xml
z archiwum test.odt przy pomocy pclzip i
zastąpienie go zmodyfikowanym przez
nas oraz skasowanie pliku content.xml z
bieżącego katalogu (
unlink()
). Oczywi-
ście, tę ostatnią funkcję musimy wywołać
Listing 10.
Odczyt listy zadań z dokumentu aplikacji Ganttproject
// Pobieranie pliku
do
przetwarzania
$file
=
$_SERVER
[
'argv'
][
1
]
;
// Otwieranie pliku przy użyciu SimpleXML
$xml
= simplexml_load_file
(
$file
)
;
// Funkcja rekurencyjna odczytująca zadania
function
display_tasks
(
&
$xml
,
$level
= 0
)
{
$prefix
= str_repeat
(
'|'
,
$level
)
.
'+-> '
;
foreach
(
$xml
-
>
task as
$task
)
{
echo
$task
[
'start'
]
.
' '
;
echo
(
$task
[
'duration'
]
>
9
?
''
:
'0'
)
.
$task
[
'duration'
]
;
echo
' day(s) '
.
$prefix
.
" "
.utf8_decode
(
$task
[
'name'
])
.
"
\n
"
;
display_tasks
(
$task
,
$level
+ 1
)
;
}
}
// Wywołanie funkcji rekurencyjnej display_tasks()
display_tasks
(
$xml
-
>
tasks
)
;
Listing 11.
Zmiana nazwy pierwszego zadania w pliku programu Ganttproject
(kernel staje się Base)
$project
= simplexml_load_file
(
'openoffice.xml'
)
;
$project
-
>
tasks-
>
task
[
0
][
'name'
]
=
'Base'
;
file_put_contents
(
'openoffice.xml'
,
$project
-
>
asXml
())
;
Listing 12.
Dodanie zadania w pliku programu Ganttproject za pomocą
SimpleXML
$project
= simplexml_load_file
(
'openoffice.xml'
)
;
// Poszukiwanie identyfikatora największego zadania
$xpath
=
$project
-
>
xpath
(
"//task/@id");
$maxId
= 0;
foreach
(
$xpath
as
$item
)
{
$maxId
=
max
((
int
)
$item
[
'id'
]
,
$maxId
)
;
}
// Modyfikacja trwania zadania macierzystego
$project
-
>
tasks-
>
task
[
0
][
'duration'
]
= 11;
// Dodanie nowego zadania
$newTask
=
$project
-
>
tasks-
>
task
[
0
]
-
>
addChild
(
'task'
)
;
$newTask
[
'id'
]
=
$maxId
+ 1;
$newTask
[
'name'
]
=
"preprod_tests"
;
$newTask
[
'color'
]
=
'#0066ff'
;
$newTask
[
'meeting'
]
=
'false'
;
$newTask
[
'start'
]
=
'2006-12-03'
;
$newTask
[
'duration'
]
=
'5'
;
$newTask
[
'complete'
]
=
'0'
;
$newTask
[
'priority'
]
=
'1'
;
$newTask
[
'expand'
]
=
'true'
;
// Zapisywanie modyfikacji
file_put_contents
(
'openoffice.xml'
,
$project
-
>
asXml
())
;
www.phpsolmag.org
69
PHP Solutions Nr 5/2006
Dla zaawansowanych
na samym końcu, gdy już dodamy ten plik
do archiwum.
Generujemy dokument
OpenOffice.org Writer na
podstawie szablonu
Utworzymy teraz dokument OO Writera
przy użyciu szablonu, czyli istniejącego
dokumentu, który posłuży nam jako model
(trochę podobnie jak w przypadku syste-
mów szablonów HTML-owych, takich jak
np. Smarty). Będzie to wymagało wydoby-
cia pliku content.xml z archiwum szablo-
nu (u nas będzie to model.odt) i wykorzy-
stania go w celu utworzenia nowego pli-
ku content.xml, zawierającego dane, któ-
re dodamy. Na koniec umieścimy ten no-
wy plik w archiwum będącym kopią na-
szego szablonu.
Wbudowane do PHP rozszerzenie
ZIP nie pozwala niestety na zapisanie pli-
ku: będziemy więc wywoływać programy
zip oraz unzip korzystając z funkcji
shell_
exec()
. Nic nie stoi również na przeszko-
dzie, abyśmy użyli biblioteki pclzip, którą
poznaliśmy w poprzednim przykładzie.
Naszemu szablonowi (Rysunek 4)
nadamy nazwę model.odt, a następnie
użyjemy systemu szablonów Smarty (http:/
/smarty.php.net) w celu wygenerowania do-
kumentu ze strumienia RSS (Rysunek 5).
Nasz przykład (Listing 15) jest prze-
znaczony dla PHP5; wymaga również
dostępu do plików zip (używanego w ce-
lu dodania pliku do archiwum) i unzip (słu-
żącego do wydobycia pliku z archiwum) w
systemie operacyjnym.
Główną częścią naszego skryptu ge-
nerującego plik OpenOffice Writera na
podstawie szablonu jest klasa
OpenOffice
,
która dziedziczy ze
Smarty
, co pozwala
nam używać metod i atrybutów tej dru-
giej. W rzeczywistości, klasa OpenOffi-
ce będzie więc stanowiła API posiadają-
ce silnik szablonów do generowania do-
kumentów OpenOffice. Przykładowo,
metoda
assign()
należy do
Smarty
, zaś
mergeAndRight()
do
OpenOffice
.
W ramach waszych dalszych działań
i rozwinięć, możecie umieścić klasę Ope-
nOffice w osobnym pliku, aby móc ją do-
dać do innych przetwarzań. Korzystanie
z tej klasy okazuje się bardzo proste, jak
tego dowodzą cztery linijki z Listingu 15.
Musimy tylko po prostu instancjonować
nowy obiekt podając w parametrze na-
zwę pliku OpenOffice Writer, który zamie-
rzamy stworzyć. Następnie, dokonujemy
przypisań i zakańczamy przez wywołanie
Listing 13.
Plik content.xml obejmujący zawartość dokumentu OO Writera
<
office:document-content office:version=
"1.0"
>
<
office:scripts/
>
<!-- Specyficzne style dokumentu -->
<
office:font-face-decls
>(
...
)<
/office:font-face-decls
>
<
office:automatic-styles/
>
<!-- Zawartość dokumentu-->
<
office:body
>
<
office:text
>
<
text:sequence-decls
>(
...
)<
/text:sequence-decls
>
<
text:p text:style-name=
"Heading"
>
Tytuł
<
/text:p
>
<
text:p text:style-name=
"Standard"
>
Paragraf...
<
/text:p
>
<
/office:text
>
<
/office:body
>
<
/office:document-content
>
Listing 14.
Manipulacja danymi w dokumencie OpenOffice.org Writer
include
"pclzip.lib.php"
;
$zip
=
new
PclZip
(
'test.odt'
)
;
foreach
(
$zip
-
>
listContent
()
as
$file
)
{
if
(
$file
[
'filename'
]
==
'content.xml'
)
{
$zip
-
>
extractByIndex
(
$file
[
'index'
])
;
$xml_txt
= file_get_contents
(
'content.xml'
)
;
break
;
}
}
if
(
!
isset
(
$xml_txt
)){
exit
;
}
// Parsing dokumentu przy użyciu SAX i wyświetlanie danych
$p
= xml_parser_create
()
;
xml_parse_into_struct
(
$p
,
$xml_txt
,
$vals
,
$index
)
;
xml_parser_free
(
$p
)
;
foreach
(
$vals
as
$value
)
{
if
(
isset
(
$value
[
'value'
]))
{
switch
(
$value
[
'tag'
])
{
case
'TEXT:H'
:
echo
'
<
h1
>
'.utf8_decode($value['
value'
])
."
<
/h1
>
\n";
break
;
case
'TEXT:P'
:
echo
'
<
p
>
'.utf8_decode($value['
value'])."
<
/p
>
\n";
break
;
}
}
}
// Dodanie tytułu i akapitu za pomocą SimpleXML
$xml
= simplexml_load_file
(
'content.xml'
)
;
$contentPart
=
$xml
-
>
xpath
(
'/office:document-content/office:body/office:text/'
)
;
$contentPart
=
$contentPart
[
0
]
;
$title
=
$contentPart
-
>
addChild
(
'text:p'
,
'New title'
,
'text'
)
;
$title
[
'text:style-name'
]
=
'Heading'
;
$paragraph
=
$contentPart
-
>
addChild
(
'text:p'
,
'New paragraph...'
,
'text'
)
;
$paragraph
[
'text:style-name'
]
=
'Standard'
;
file_put_contents
(
'content.xml'
,
$xml
-
>
asXml
())
;
// Zapisywanie modyfikacji
$zip
-
>
deleteByIndex
(
$file
[
'index'
])
;
$zip
-
>
add
(
'content.xml'
)
;
unlink
(
'content.xml'
)
;
www.phpsolmag.org
70
PHP Solutions Nr 5/2006
Dla zaawansowanych
metody
mergeAndWrite
, która stworzy do-
kument, łącząc w całość dane zapisane i
model model.odt.
Na początku tej klasy zdeklarujemy
stałe
ZIP_EXECUTABLE
i
UNZIP_EXECUTABLE
,
którym przypiszemy nazwy programów zip
i unzip, co pozwala je zmienić wedle po-
trzeb (np. w ramach dostosowywania do
innego niż Linux systemu operacyjnego).
Następnie zainicjujemy zmienne prywatne
$OoFile
i
$OoModel
i przejdziemy do two-
rzenia konstruktora, w którym zdefiniuje-
my parametry (nasze dwie zmienne i usta-
wienia Smarty'ego). Ostatnim krokiem bu-
dowania naszej klasy będzie utworze-
nie metody publicznej mergeAndWrite(),
której zadaniem będzie tworzenie doku-
mentu, w którym połączymy dane otrzy-
mane z pliku podanego w
$this->OoFile
oraz szablonu. Rozpoczniemy ją kopiu-
jąc archiwum szablonu (model.odt) jako
test.odt. Następnie wydobędziemy z te-
go archiwum plik content.xml i potraktuje-
my go jako szablon używając w tym ce-
lu odziedziczonej po klasie
Smarty()
me-
tody
fetch()
, która zwraca skompilowa-
ny dokument powstający przy użyciu sza-
blonu. Otrzymany dokument przypiszemy
do zmiennej
$content
, a następnie zwol-
nimy zasób zajmowany przez skompilo-
waną wersję szablonu używając metody
$this->clear_compiled_tpl()
. Pozostało
jeszcze zapisać otrzymany dokument ja-
ko content.xml oraz dodać ten plik do ar-
chiwum test.odt, zastępując nim poprzed-
nio istniejący.
Pobierzemy teraz dane ze strumie-
nia RSS (z portalu Yahoo!), wykorzystu-
jąc do tego, jak poprzednio, SimpleXML.
Pozostało jeszcze utworzenie obiektu
$oo
klasy
OpenOffice
, któremu przez para-
metr konstruktora przekażemy nazwę pli-
ku test.odt. Następnie korzystając z me-
tod klasy Smarty przypiszemy zmiennym
news
i
name
załadowanego szablonu odpo-
wiednio treść odczytanych newsów (któ-
re przetworzymy w pętli
foreach
) oraz
nazwisko twórcy dokumentu (Guillaume
Ponçon). Na koniec wywołamy metodę
mergeAndWrite()
obiektu
$oo
, aby zapisać
zmieniony plik. Nasza praca z dokumenta-
mi OpenOffice.org jest zakończona.
Podsumowanie
Pokazaliśmy, jak łatwo i bezproblemo-
wo korzystać z XML-a w PHP przy użyciu
technik SAX, DOM i SimpleXML. Ta ostat-
nia metoda okazała się szczególnie war-
ta uwagi, ze względu na połączenie sze-
rokich możliwości (włącznie z wykorzysta-
niem XPath) z prostotą tworzenia oparte-
go na niej kodu. Warto pamiętać, że cza-
sami najlepsze może się okazać połą-
czenie kilku technik, jak to ukazaliśmy na
przykładzie aplikacji przetwarzającej do-
kumenty edytora OpenOffice.org Writer.
Bądźmy również świadomi, że możliwo-
ści pracy skryptów PHP z dokumentami
XML-owymi nie kończą się na SAX, DOM
i SimpleXML: istnieją i wciąż powstają no-
we rozszerzenia i biblioteki, np. wysoko-
poziomowe API do łatwiejszego przetwa-
rzania wiadomości RSS czy danych pa-
kietu biurowego, czy też pakiety ułatwia-
jące tworzenie dokumentów XML według
wzorca. W każdym przypadku, dobre opa-
nowanie obsługi XML-a utoruje nam dro-
gę do tworzenia nowoczesnych aplika-
cji, które mogą współpracować z przyjęty-
mi standardami, nie będziemy więc skaza-
ni na izolację naszych rozwiązań od resz-
ty świata. n
Guillaume Ponçon jest architektem
PHP i autorem Best practices PHP 5,
francuskiej książki wydanej nakładem
wydawnictwa Eyrolles.
O autorze
Listing 15.
Tworzenie pliku edytora OpenOffice.org Writer w oparciu o szablon
include
'smarty/Smarty.class.php'
;
class
OpenOffice
extends
Smarty
{
const ZIP_EXECUTABLE =
'zip'
;
const UNZIP_EXECUTABLE =
'unzip'
;
private
$vars
=
array
()
;
private
$OoFile
=
''
;
private
$OoModel
=
''
;
public
function
__construct
(
$oo_file
,
$oo_model
=
'model.odt'
)
{
$this
-
>
OoFile =
$oo_file
;
$this
-
>
OoModel =
$oo_model
;
$this
-
>
left_delimiter =
'{{'
;
$this
-
>
right_delimiter =
'}}'
;
$this
-
>
Smarty
()
;
$this
-
>
template_dir =
dirname
(
__FILE__
)
.
'/'
;
$this
-
>
compile_dir =
$this
-
>
template_dir;
$this
-
>
config_dir =
$this
-
>
template_dir;
$this
-
>
cache_dir =
$this
-
>
template_dir;
$this
-
>
caching = false;
}
// Tworzy dokument, łącząc w całość przesłane dane i szablon
public
function
mergeAndWrite
(
)
{
copy
(
$this
-
>
OoModel,
$this
-
>
OoFile
)
;
shell_exec
(
self::UNZIP_EXECUTABLE.
' '
.
$this
-
>
OoFile.
' content.xml'
)
;
$content
=
$this
-
>
fetch
(
'content.xml'
)
;
$this
-
>
clear_compiled_tpl
(
'content.xml'
)
;
file_put_contents
(
'content.xml'
,
$content
)
;
shell_exec
(
self::ZIP_EXECUTABLE.
' '
.
$this
-
>
OoFile.
' -mq content.xml'
)
;
}
}
// Pobieranie danych ze strumienia RSS
$news
=
array
()
;
$xml_news
= simplexml_load_file
(
'http://rss.news.yahoo.com/rss/world'
)
;
foreach
(
$xml_news
-
>
channel-
>
item as
$item
)
{
$news
[]
=
(
string
)
$item
-
>
title;
}
// utworzenie i użycie obiektu OpenOffice
// do stworzenia nowego dokumentu "test.odt".
$oo
=
new
OpenOffice
(
'test.odt'
)
;
$oo
-
>
assign
(
'news'
,
$news
)
;
$oo
-
>
assign
(
'name'
,
"Guillaume Ponçon"
)
;
$oo
-
>
mergeAndWrite
()
;