2006 05 XML i PHP w praktyce [XML]

background image

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.

background image

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

"

;

}

}

background image

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-

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'

)

;

background image

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

()

;

background image

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

>

';

}

background image

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

>

background image

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

())

;

background image

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'

)

;

background image

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

()

;


Wyszukiwarka

Podobne podstrony:
05 xml domid 5979 ppt
05 xml domid 5979 ppt
2006 06 XML FastCreate [XML]
2006 czerwiec zad 1 Egzamin praktyczny przykład rozwiązania
PHP Praktyczne projekty
2006 05 R odp
doczekalska wielkojezycznosc eps 2006 05 014
kolokwium 2006 05 30
2006 06 RSA w PHP chronimy nasze dane przy użyciu kryptografii asymetrycznej [Kryptografia](1)
PHP Praktyczne wprowadzenie R 4 Wstęp do programowania Proste skrypty PHP
2006 05 Krita–edytor grafiki bitmapowej [Grafika]
Wystapienie 2006 05 18
CYTOFIZJOLOGIA 2006, Histologia, Histologia, HISTO PRAKTYCZNY, szkiełka- grupy, pytania
Święty Pustelnik z Libanu (o ojcu Charbel) Miłujcie się 2006 05
2006 05 mapa
2006 05 Antywzorce w zarządzaniu projektami informatycznymi [Inzynieria Oprogramowania]

więcej podobnych podstron