This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Przedmowa XML, XML, XML... Ten skrót pojawia się na czapeczkach i koszulkach. W każdym czasopiśmie technicznym, w radiu, w czasopismach kobiecych... no, może sprawy nie zaszły jeszcze tak daleko, ale niewiele brakuje. XML, rozszerzalny język znaczników (ang. Extensible Markup Language), wydaje się pojawiać w każdej branży technicznej, a szczególnie często słychać o nim w środowisku programistów języka Java®. Aplikacji nie uważa się już za produkt przemysłowy, jeśli nie użyto w niej w jakiś sposób języka XML. Wreszcie powstała możliwość nowych zastosowań dla starych, firmowych systemów, a przedsiębiorstwa oszczędzają miliony i miliardy dolarów na integracji systemów — wszystko dzięki tym trzem literkom. Programiści Javy będą zmuszeni do przyswajania sobie jeszcze jednej technologii, a zadanie wydaje się być tym trudniejsze, im bardziej się w nie zagłębić. Drogę do XML-a wytyczają kolejne skróty: XSL, XPath, RDF, XML Schema, DTD, PI, XSLT, XSP, JAX®, SAX, DOM i inne. I nie ma już chyba dyrektora, który nie wymagałby od swojego personelu informatycznego znajomości XML-a! Kiedy na początku 1998 roku XML zyskał status specyfikacji formalnie określonej przez World Wide Web Consortium, niewiele osób zgodziłoby się uznać, że powstała właśnie najwspanialsza technologia od czasów Javy (a może jeszcze wspanialsza!). Ale już dwa lata później XML i grupa technologii służących do manipulacji tym językiem zaczęły stanowić podstawowy sposób reprezentacji danych w Javie. XML ma szansę wnieść do formatu danych to, co Java wniosła do języków programowania — całkowitą przenośność. I właściwie to zamierzenie twórców Javy osiągnąć można tylko dzięki językowi XML; przenośność Javy bardzo ucierpiała ze względu na używane od lat, firmowe formaty danych — aplikacja mogła zostać uruchomiona na wielu platformach, ale nie w wielu firmach, na standardowym formacie danych. XML może rozwiązać ten problem — powoduje, że te firmowe formaty danych zaczynają być zbędne i umożliwia wzajemne komunikowanie się systemów za pomocą standardowej reprezentacji danych. To książka o języku XML, ale napisana głównie z myślą o programistach Javy. Zarówno XML, jak i Java są potężnymi narzędziami. W tej książce jednak uwaga skupiona zostanie na tym, jak ze sobą współpracują — to właśnie dzięki tej współpracy XML staje się naprawdę potężną technologią. Przedstawione zostaną różne słowniki XML. Czytelnik nauczy się tworzyć, zawężać i przekształcać XML, pozna także wszystkie interfejsy programistyczne (API) do obsługi języka XML z poziomu kodu Javy. W książce zostaną także podjęte najciekawsze zagadnienia dotyczące języka XML: dynamiczna zawartość, usługi powiadamiania, e-biznes i składnice danych. Za każdym razem będzie stosowany ten sam, wąski punkt widzenia programisty, któremu zależy na wykonaniu konkretnego zadania z użyciem opisywanych narzędzi. Zostaną przedstawione narzędzia, jakie
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 7
8
Przedmowa
udostępnia XML; a jeśli istnieje coś, co — choć popularne! — nie jest zbyt użyteczne, zostanie tylko pobieżnie omówione. Jeżeli natomiast któraś wyjątkowo przydatna funkcja XML-a sprawia wrażenie „ukrytego rodzynka” — zostanie on wydobyty i opisany pod kątem użyteczności. Java i XML ma służyć pomocą Czytelnikowi. Nie jest pozycją referencyjną ani nie ma na celu reklamowania standardu XML. Połowa tej książki to kod — praktyczny i działający. Można go także pobrać z Internetu, ale umieszczono go w książce po to, aby Czytelnik mógł prześledzić proces tworzenia różnych aplikacji XML. Czytelnik powinien więc raczej zagłębiać się w te przykłady, a nie je pomijać. Jest tutaj przedstawiony także nowy interfejs API do manipulacji XML-em z poziomu Javy — został on opisany w pełni, z przykładami. To książka dla programisty Javy, dlatego zastosowano podejście praktyczne. To nie jest ani teoretyczny, ani pobieżny przegląd „fajnej” technologii. Tam, gdzie to możliwe, unikamy słów-haseł; tam, gdzie nie — podajemy ich precyzyjne definicje. Cały kod oraz rozwiązania z tej książki zostały ręcznie wpisane do edytora i przetestowane. Są tutaj po to, by pomóc Czytelnikowi w solidnym opanowaniu Javy i XML-a.
Organizacja książki Książka została podzielona w szczególny sposób. W pierwszej połowie (rozdziały 1. do 7.) został przedstawiony standard XML oraz najważniejsze interfejsy API Javy do jego obsługi. Może nie są to rozdziały niezwykle interesujące, jednak powinny być czytane w proponowanej kolejności, a przynajmniej dokładnie przejrzane — nawet jeśli XML nie jest Czytelnikowi obcy. Rozdział 8. to jakby fragment przejściowy pomiędzy dwiema częściami książki. Opisano w nim niezwykle ciekawy nowy interfejs API do obsługi XML-a z poziomu Javy — JDOM. Ten rozdział trzeba przeczytać koniecznie, ponieważ publikacja tego interfejsu zbiegnie się z wprowadzeniem niniejszej książki do sprzedaży, a tutaj przedstawiono pełne materiały referencyjne odnośnie tego API (interfejs ten, który autor utworzył wraz z Jasonem Hunterem, ukierunkowany jest właśnie na rozwiązywanie problemów z wykorzystaniem Javy i XML-a!). W pozostałej części książki (rozdziały 9. do 14.) omawiane są specyficzne tematy związane z XML-em, nieustannie poruszane na konferencjach i w samouczkach programistów. Analiza tych tematów pozwoli Czytelnikowi maksymalnie wykorzystać XML we własnych aplikacjach — i to od zaraz! Książkę zamykają dwa dodatki. Poniżej przedstawiony jest skrótowy opis poszczególnych rozdziałów: Rozdział 1. Wprowadzenie Omówienie alfabetu XML-a i próba znalezienia odpowiedzi na pytanie, dlaczego standard ten jest tak istotny dla obecnych i przyszłych aplikacji stosowanych w przedsiębiorstwach. Rozdział 2. Tworzenie dokumentów XML Omówienie procesu budowania dokumentu XML od podstaw; zastosowanie najważniejszych konstrukcji XML, takich jak elementy, atrybuty, encje. Rozdział 3. Przetwarzanie kodu XML Przedstawienie interfejsu API Javy służącego do obsługi XML-a — Simple API for XML (SAX). Szczegółowo opisano proces przetwarzania oraz zdarzenia udostępniane przez interfejs SAX, przeznaczone do wykorzystania przez programistów. Rozdział 4. Zawężanie danych XML Przedstawienie dwu sposobów zawężenia dokumentu XML: zastosownie definicji typu dokumentu (DTD) oraz XML Schema.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 8
Organizacja książki
9
Rozdział 5. Sprawdzanie poprawności składni XML-a Uzupełnienie poprzedniego rozdziału. Omówiono, jak za pomocą poznanego interfejsu SAX umożliwić weryfikację składni dokumentu oraz reagować, gdy dokument wykracza poza narzucone zawężenia. Rozdział 6. Przekształcanie kodu XML Opis rozszerzalnego języka arkuszy stylów XSL oraz innych istotnych komponentów służących do przekształcania dokumentów XML z jednego formatu na inny. Omówiono różne metody konwersji dokumentów XML na inne formaty tekstowe oraz sposób wykorzystania obiektów formatujących do konwersji na formaty binarne. Rozdział 7. Czerpanie z XML-a Kontynuacja tematu przekształcania dokumentów XML. Przedstawiono procesory transformacji XML oraz sposoby konwersji dokumentów XML na inne formaty. Opisano również model obiektowy dokumentu (DOM) i wyjaśniono, w jaki sposób można za jego pomocą obsłużyć dane XML. Rozdział 8. JDOM Omówienie interfejsu Java API for XML Parsing (JAXP) i próba przeanalizowania, jak duże znaczenie w XML-u ma niezależność od rozwiązań firmowych. Zaprezentowano też interfejs JDOM, wskazano przyczyny jego powstania, szczegółowo opisano sposób korzystania z niego oraz porównano go z interfejsami SAX oraz DOM. Rozdział 9. Struktury publikacji WWW Analiza struktur publikacji WWW i wybór najodpowiedniejszej. Omówiono także strukturę Apache Cocoon oraz wyjaśniono, w jaki sposób korzystać z niej przy budowaniu dynamicznej zawartości stron WWW. Rozdział 10. XML-RPC Przedstawienie zdalnych wywołań procedur (RPC), uwzględnienie ich znaczenia w systemach rozproszonych w porównaniu z RMI oraz wyjaśnienie, dlaczego technologie RPC w przypadku niektórych problemów tak wiele zyskują dzięki XML-owi. Omówienie biblioteki XML-RPC Javy oraz konstrukcji klientów XML-RPC. Rozdział 11. XML na potrzeby konfiguracji Omówienie sposobów tworzenia danych konfiguracyjnych w postaci XML i wyjaśnienie, dlaczego format ten jest tak istotny w aplikacjach wieloplatformowych, szczególnie w systemach rozproszonych. Rozdział 12. Tworzenie danych XML w języku Java Przedstawienie procesu generowania i modyfikowania formatu XML z poziomu Javy przy wykorzystaniu mechanizmów działających po stronie serwera, takich jak serwlety. Wskazano też, na co trzeba zwrócić uwagę przy dokonywaniu mutacji XML. Rozdział 13. Operacje typu firma-firma Omówienie sposobu komunikacji pomiędzy firmami i wewnątrz firmy z wykorzystaniem przenośnego formatu danych XML. Posłużą do tego przykładowe aplikacje, napisane w różnych językach i porozumiewające się ze sobą za pomocą XML-a. Rozdział 14. XML Schema Omówienie przyczyn niezwykłego zainteresowania specyfikacją XML Schema oraz wyjaśnienie, dlaczego technologie Java i XML Schema nawzajem się uzupełniają.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 9
10
Przedmowa
Dodatek A. Interfejs API — materiał referencyjny W tym dodatku wymieniono wszystkie klasy, interfejsy i metody dostępne w interfejsach programistycznych SAX, DOM, JAXP i JDOM. Dodatek B. Funkcje i właściwości SAX 2.0 W tym dodatku wymieniono wszystkie funkcje i właściwości implementacji interfejsu SAX 2.0.
Dla kogo jest ta książka? Podstawą napisania książki było przekonanie, że XML jest już nieodłącznym elementem programowania w Javie. Książka została napisana w taki sposób, że najpierw Czytelnik dowiaduje się, jak w ogóle korzystać z XML-a i Javy. Nie ma tutaj rozważań dotyczących tego, czy powinno się korzystać z XML-a. Programista Javy powinien korzystać z XML-a. Dlatego, jeśli Czytelnik programuje w Javie, chce programować w Javie, zarządza programującymi w Javie lub jest odpowiedzialny albo związany z projektem pisanym w Javie — książka na pewno mu się przyda. Jeśli Czytelnik chce się rozwijać, pogłębiać wiedzę programistyczną, pisać bardziej przejrzysty kod, tworzyć udane projekty w terminie i bez przekraczania budżetu, musi korzystać z danych w formatach firmowych, tworzyć rozproszone systemy lub po prostu ciekaw jest, skąd całe to zainteresowanie XML-em — książka na pewno mu się przyda. Pisząc książkę, starałem się czynić jak najmniej założeń. Nie chcę rozpoczynać omawiania XML-a od takiego poziomu, że zniechęci to część Czytelników. Ale uważam także, że jeśli Czytelnik wydał już pieniądze na tę książkę, to zależy mu na czymś więcej niż tylko na podstawach. Dlatego zakładam jedynie znajomość Javy i zagadnień programowania po stronie serwera (takich jak serwlety Javy czy komponenty Enterprise JavaBean®. Jeśli Czytelnik nie miał jeszcze nigdy do czynienia z Javą albo dopiero rozpoczyna przygodę z tym językiem, być może warto najpierw przeczytać Learning Java Pata Niemeyera i Jonathana Knudsena (wyd. O'Reilly & Associates). Zakładam, że Czytelnik nie wie nic o XML-u, więc zaczynam od samych podstaw. Ale zakładam również, że Czytelnik będzie ciężko pracował i szybko przyswajał wiedzę. Dlatego podstawy zostaną omówione błyskawicznie, by jak najszybciej przejść do bardziej zaawansowanych zagadnień. Materiał nie będzie wielokrotnie powtarzany poza tymi miejscami, gdzie jest to absolutnie koniecznie. Może więc zajść potrzeba ponownego przeczytania wcześniejszych fragmentów. Jeśli Czytelnik chce uczyć się XML-a, trochę zna Javę i przygotowany jest na „wstukanie” przykładowego kodu do swojego ulubionego edytora tekstów, nie powinien mieć większych problemów z przyswojeniem materiału opisanego w książce.
Oprogramowanie i wersje Informacje w tej książce dotyczą XML-a w wersji 1.0 oraz różnych słowników XML w wersjach najbardziej aktualnych w czasie ukończenia książki, tj. w kwietniu 2000 r. Ponieważ rozmaite specyfikacje XML nie uzyskały jeszcze ostatecznej postaci, pomiędzy drukowaną wersją książki a opisywanymi specyfikacjami mogą wystąpić drobne różnice. Cały kod Javy prezentowany w tej książce oparty jest na platformie Java 1.1, za wyjątkiem opisów dotyczących interfejsu JDOM 1.0. Ta odmienność jest odnotowana i uzasadniona w odpowiednim miejscu, w rozdziale 8. Wykorzystano parser Apache Xerces, procesor Apache Xalan oraz biblioteki Apache FOP w najnowszych stabilnych wersjach z kwietnia 2000 r. Zastosowano strukturę publikacji WWW Apache Cocoon w wersji 1.7.3, a biblioteki Javy XML-RPC w wersji 1.0 beta 3. Całe oprogramowanie można pobrać za darmo z adresów http://java.sun.com, http://xml.apache.org oraz http://www.xml-rpc.com.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 10
Konwencje typograficzne
11
Kod źródłowy przykładów, w tym klasy narzędziowe com.oreilly.xml, jest w całości zamieszczony w książce. Zarówno kod źródłowy, jak i postać binarną wszystkich przykładów (w tym szeroką dokumentację Javadoc) można pobrać z adresów http://www.oreilly.com/catalog/javaxml oraz http://www.newInstance.com. Wszystkie przykłady mogące funkcjonować jako serwlety lub nadające się do przekonwertowania na serwlety można obejrzeć w działaniu online pod adresem http://www.newInstance.com. Pełną dystrybucję JDOM 1.0, wraz ze specyfikacją, implementacją referencyjną, kodem źródłowym, dokumentacją interfejsu oraz postacią binarną można pobrać z adresu http://www.jdom.org. Ponadto stworzono drzewo CVS zawierające kod JDOM i pozwalające na uzupełnianie i komentowanie źródeł. Informacje o tym, jak korzystać z CVS-a JDOM, można znaleźć także pod adresem http://www.jdom.org.
Konwencje typograficzne W książce zastosowane zostały następujące konwencje typograficzne: Druk pochyły wykorzystywany jest do oznaczania: • uniksowych ścieżek dostępu, nazw plików i nazw programów, • adresów internetowych (nazw domen i identyfikatorów URL), • nowych, definiowanych terminów. Czcionka proporcjonalna oznacza: • tekst wpisywany w wierszu poleceń i opcje — czyli to, co ma być wpisane dokładnie tak, jak wydrukowano, • nazwy i słowa kluczowe w programach Javy, w tym nazwy metod, zmiennych i klas, • nazwy elementów i znaczników XML-a, nazwy atrybutów i innych konstrukcji XML-a wyglądających tak, jak wyglądałyby w dokumencie XML . Czcionka proporcjonalna pogrubiona służy do oznaczania: • nowo wprowadzonych fragmentów kodu, • fragmentów większego kodu, które właśnie są opisywane w tekście.
Opinie i komentarze Opinie i komentarze dotyczące tej książki można przysyłać do wydawcy: O'Reilly & Associates, Inc. 101 Morris Street Sebastopol, CA 95472 (800) 998-9938 (Stany Zjednoczone lub Kanada) (707) 829-0515 (międzynarodowe lub lokalne) (707) 829-0104 (faks) Można także wysłać list elektroniczny. W celu zapisania się na listę adresową lub zamówienia katalogu, wystarczy wysłać e-mail pod adres [email protected].
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 11
12
Przedmowa
Podziękowania Kiedy tak spoglądam na plik arkuszy składających się na manuskrypt tej książki, to widzę, że w tych kilku akapitach nie udałoby się podziękować wszystkim, którzy przyczynili się do jej powstania. A jednak jest to na pewno prostsze niż omówienie całej tematyki Javy i XML-a, więc za chwilę spróbuję... Gdybym kogoś pominął, bardzo proszę — wybaczcie! Historia tej książki rozpoczęła się w Święto Dziękczynienia w roku 1999. Wtedy to mój redaktor, Mike Loukides, zaproponował mi napisanie tej książki, mimo że w tym czasie zajęty byłem pisaniem innej książki dla O'Reilly. Nieco wahałem się przed odłożeniem na jakieś sześć miesięcy tekstu, którego tworzenie bardzo mnie pochłonęło. Mike jednak przekonywał mnie tak wprawnie, jak potem redagował gotowy tekst do czytelniejszej postaci. Teraz ten czas wspominam jako najciekawszy okres w mojej karierze technicznej i wiem, jak wiele zawdzięczam właśnie Mike'owi: służył mi wskazówkami przy pisaniu trudnych, pierwszych rozdziałów; wysłuchał narzekań, gdy trzykrotnie (tak, trzykrotnie!) zmieniałem rozdział o XML Schema z powodu aktualizacji specyfikacji; wreszcie, kiedy trzeba było trochę odpocząć, okazywał swoje wszechstronne zdolności muzyczne. Bez niego ta książka nie osiągnęłaby tak wysokiego poziomu. Książce mogłem poświęcić tak wiele wysiłku i czasu tylko dzięki rodzinie i przyjaciołom: rodzicom, którzy dzień w dzień przez osiemnaście lat zabiegali o poprawność mojego języka; cioci, która pasjonowała się tym, co robię, nawet jeśli nie wiedziała, o co w tym chodzi; Jody Durret, Carlowi Henry'emu i Pam Merryman, którzy poświęcili bardzo dużo czasu na zrobienie ze mnie autora książek; Gary'emu i Shirley Greathouse'om, którzy zawsze przypominali, abym wytrwale parł naprzód i nie osiadał na laurach; oraz moim dziadkom, Deanowi i Gladys McLaughlinom, którzy cały czas mnie wspierali. Nad merytoryczną poprawnością i precyzją książki czuwali liczni recenzenci techniczni: Marc Loy, Don Weiss, George Reese (którego komentarze spowodowały dodanie całego rozdziału!), Matthew Merlo i James Duncan Davidson. Szczególną pomoc okazał James — pieczołowicie punktował drobne błędy i był brutalnie szczery, dzięki czemu nigdy nie zapomniałem, że jestem przede wszystkim programistą, a dopiero potem autorem książek. Bardzo dużo zawdzięczam Jasonowi Hunterowi, autorowi Java Servlet Programming (O'Reilly & Associates). Choć pisanie tej książki rozpoczęło się w listopadzie 1999 roku, to przeżyła ona swoiste odrodzenie w marcu 2000, kiedy to Jason i ja całe popołudnie spędziliśmy na trawniku w Santa Clara, narzekając na istniejące interfejsy API Javy do obsługi XML-a. Te utyskiwania zaprocentowały w dwójnasób — przede wszystkim, stworzyliśmy opisywany w tej książce interfejs JDOM (przy pomocy i zachętach ze strony Jamesa Davidsona z Sun Microsystems). Uważamy, że interfejs ten znacząco przyczyni się do zacieśnienia współpracy pomiędzy XML-em i Javą i spowoduje, że XML z punktu widzenia Javy będzie standardem przejrzystym i użytecznym, a nie zagmatwanym i nieprzydatnym. Po drugie, bardzo zaprzyjaźniliśmy się z Jasonem. Pomógł mi w niełatwym procesie tworzenia książki. Niejednokrotnie siedzieliśmy do późnych godzin nocnych, zastanawiając się, co można zrobić by JDOM i inne programy działały bardziej intuicyjnie. Na pewno ta książka nie powstałaby, gdyby nie moja żona, Leigh. Jakimś cudem wytrzymała i nie wyrzuciła mnie z domu przez te ostatnie 6 miesięcy, w ciągu których najczęściej byłem zmęczony, nieprzystępny i w ogóle zajęty. Te krótkie chwile, jakie udawało nam się zdobyć dla siebie po moim pisaniu i pełnoetatowej pracy konsultanta, sprawiały, że cała moja praca wydawała mi się więcej warta. Tęsknię już za nią bardzo i nie mogę się doczekać, kiedy wrócę do zwyczajnego try-
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 12
Podziękowania
13
bu życia właśnie z nią, z naszymi trzema bassetami (Charlie, Molly i Daisy) i moimi labradorami (Sethem i Mosesem). Dziękuję też mojemu dziadkowi, Robertowi Earlowi Burdenowi, który nie doczekał wydania tej książki. Był takim człowiekiem, jakim ja zawsze chciałem być. Dziękuję mu za to, że nauczył mnie, iż nigdy nie powinienem poprzestawać tylko na tym, czego oczekują ode mnie inni.
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 13
14
Przedmowa
C:\WINDOWS\Pulpit\Szymon\Java i XML\00-08.doc — strona 14
1 Wprowadzenie XML. W ciągu ostatnich dwóch lat chyba każdy programista w którymś momencie poczuł ciarki na plecach związane z tym skrótem. Ciarki na plecach, choć z różnych powodów: konieczność wyuczenia się kolejnego skrótu, ekscytacja nową technologią, zagubienie. I, co ciekawe, każda z tych reakcji na XML jest uzasadniona. Tak, trzeba zapamiętać nowy skrót, a także skróty jemu towarzyszące: XSL, XSLT, PI, DTD, XHTML i inne. Tak, XML oferuje nowe możliwości: to, co Java wniosła w przenośność kodu, XML ma wnieść w przenośność danych. W ostatnich miesiącach Sun promuje nawet to rozwiązanie następującym sloganem: „Java + XML = przenośny kod + przenośne dane”. I wreszcie — tak, XML powoduje pewne zagubienie. Spróbujemy przybliżyć i rozgryźć XML — będziemy omawiać go tylko na tyle ogólnie i abstrakcyjnie, żeby nie popaść w bezużyteczność, i tylko na tyle szczegółowo, żeby nie tworzyć kolejnej suchej specyfikacji. To książka dla programisty Javy, który chce poznać i nauczyć się korzystać z narzędzi operujących na tej technologii. Przed programistą współczesnej aplikacji WWW stoi wiele problemów, których dziesięć lat temu nie było nawet w prognozach. Pojawiła się konieczność uruchamiania szybkich i bezawaryjnych systemów rozproszonych na przestrzeni tysięcy kilometrów, konieczność pobierania danych z systemów heterogenicznych, baz danych, usług katalogowych i aplikacji bez utraty choćby jednej cyfry po przecinku. Aplikacje muszą porozumiewać się nie tylko z innymi komponentami danej firmy, ale także z innymi systemami biznesowymi — często znajdującymi się w innych firmach i zbudowanymi w oparciu o inne technologie. Klienty to już nie tylko klienty uproszczone (ang. thin client), ale całe przeglądarki WWW obsługujące HTML, telefony komórkowe z obsługą protokołu aplikacji bezprzewodowych (WAP) czy asystenty osobiste („organizery”) obsługujące zupełnie inne języki znaczników. Oreganizacja danych i ich przetwarzanie — oto podstawowe zadania współczesnych aplikacji. XML pozwala programiście sprostać wszystkim tym wyzwaniom. Programiści Javy mają zaś jeszcze do dyspozycji arsenał interfejsów umożliwiających korzystanie z XML-a i jego towarzyszy bez konieczności opuszczania zintegrowanego środowiska programistycznego Javy (Integrated Development Environment — IDE). Jeśli to wszystko wydaje się zbyt piękne, by było prawdziwe — warto czytać dalej. Poznamy zalety i wady interfejsów API Javy służących do przeprowadzania operacji na XML-u, a także korzyści płynące z zastosowania najnowszych specyfikacji XML-a. Cały czas będziemy przyjmować punkt widzenia programisty. Ta książka nie mówi o tym, dlaczego powinno się korzystać z XML-a, ale jak to robić. Jeśli w specyfikacji znajdą się elementy niezbyt przydatne, to powiemy, dlaczego tak jest, i przejdziemy dalej; jeśli jakieś zagadnienie jest
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 15
16
Rozdział 1. Wprowadzenie
szczególnie godne uwagi, poświęcimy mu więcej czasu. Będziemy traktować XML jako narzędzie, a nie jako slogan reklamowy czy kolejną „zabawkę”. Pamiętając o tym, spróbujmy odpowiedzieć na pytanie, czym jest XML.
Co to jest XML? XML to Extensible Markup Language, czyli rozszerzalny język znaczników. Podobnie jak jego przodek, SGML, XML jest metajęzykiem służącym do definiowania innych języków. Jest jednak o wiele prostszy i bardziej praktyczny niż SGML. XML to język znaczników, w którym nie sprecyzowano ani zestawu znaczników, ani gramatyki języka. Zestaw znaczników (ang. tag set) w języku znaczników określa, jakie znaczniki mają znaczenie dla parsera języka. Na przykład, w HTML-u można używać znaczników ze ściśle określonego zestawu. Możemy wstawić znacznik
, ale nie możemy użyć (angielskie słowo table oznacza nie tylko tabelę, ale także stół). Pierwszy ze znaczników ma dla aplikacji wykorzystującej dane HTML specyficzne znaczenie, a drugi nie — większość przeglądarek po prostu go zignoruje, ale niektóre mogą zachować się nieprzewidywalnie. Wszystko to dlatego, że kiedy definiowano standard HTML, określono w nim konkretny zestaw znaczników. W każdej kolejnej wersji HTML-a dodawane są nowe znaczniki. Jeśli jednak znacznik nie jest zdefiniowany, to przetwarzanie dokumentu zawierającego taki znacznik może spowodować błąd. Gramatyka języka definiuje poprawne użycie jego znaczników. I znów weźmy HTML jako przykład. Do znacznika
można dodać szereg atrybutów, określających szerokość tabeli, kolor tła, wyrównanie itd. Nie możemy jednak wstawić np. atrybutu TYPE, bo nie pozwala na to właśnie gramatyka języka. XML nie definiuje ani znaczników, ani gramatyki. Ma więc nieograniczone możliwości rozbudowy, jest rozszerzalny — i stąd jego nazwa. Jeśli postanowimy korzystać ze znacznika
, a potem jeszcze w danych objętych tym znacznikiem zagnieździć kilka znaczników , to bez trudności możemy to zrobić. Jeśli chcielibyśmy zdefiniować atrybut określający typ krzesła, np. TYPE, też mamy taką możliwość. Możemy nawet wstawiać znaczniki o nazwach takich jak imiona naszych dzieci! Możliwości te zostały przedstawione w przykładzie 1.1.
Przykład 1.1. Przykładowy plik XML
Drewnosklep S.A.
2 doskonała niebieski 3 średnia
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 16
Co to jest XML?
17
Jeśli Czytelnik nigdy nie widział pliku XML, a miał do czynienia tylko z HTML-em lub innymi językami znaczników, to przykład może się wydawać dość osobliwy. To dlatego, że znaczniki i gramatyka opisująca dane zostały w całości zmyślone. Żadna strona WWW ani specyfikacja nie definiuje znaczników
, czy poduszka (ale mogłoby tak być — w podobny sposób specyfikacja XHTML definiuje znaczniki HTML wewnątrz XML-a). Tu właśnie drzemie siła XML-a — umożliwia on definiowanie zawartości danych na różne sposoby i wymaga jedynie zgodności z ogólną strukturą języka. W dalszej części książki są przedstawione różnego rodzaju ograniczenia, ale na razie wystarczy pamiętać, że XML został stworzony po to, by zapewnić elastyczność formatowania danych. Ta elastyczność jest jedną z największych zalet XML-a, a jednocześnie jedną z jego największych wad. Dokumenty XML można przetwarzać na tak wiele różnych sposobów i w tak wielu różnych celach, że powstała bardzo duża liczba związanych z XML-em standardów opisujących tłumaczenie formatów danych i same formaty. Te dodatkowe skróty i ich nieustanne pojawianie się „w okolicach” XML-a często powoduje błędne rozumienie idei tego standardu. Kiedy ktoś mówi „XML”, to jest niemal pewne, że nie ma na myśli samego języka XML, tylko jedno z towarzyszących mu narzędzi. Niejednokrotnie zajmować się będziemy właśnie tymi narzędziami; trzeba jednak pamiętać, że najczęściej „XML” nie oznacza samego języka, lecz „XML i towarzyszące mu wspaniałe metody manipulowania danymi”. Skoro to rozróżnienie mamy już za sobą, możemy przejść do rozszyfrowania najbardziej popularnych skrótów związanych z XML-em. Skróty te są niezwykle istotne dla całej książki, a więc warto zostawić w tym miejscu zakładkę, aby móc w każdej chwili zajrzeć na te strony. Poniższe opisy powinny przybliżyć Czytelnikowi wzajemne związki między narzędziami związanymi z XML-em oraz wyjaśnić, czym jest XML. Nie będziemy tutaj omawiać mechanizmów publikacyjnych, aplikacji i narzędzi dla XML-a, bo są one przedstawione w dalszej części książki. Poniżej Czytelnik znajdzie informacje jedynie o specyfikacjach i zaleceniach. Większość z nich powstała z inicjatywy W3C (World Wide Web Consortium). Organizacja ta zajmuje się definiowaniem standardów XML i spełnia rolę podstawowej bazy informacji o tym standardzie — podobnie jak firma Sun jest podstawowym źródłem informacji o Javie i związanych z nią interfejsach API. Więcej informacji o W3C można znaleźć pod adresem http://www.w3.org.
XML XML to oczywiście punkt wyjścia wszystkich tych trzy- i czteroliterowych skrótów. Definiuje sam język i określa strukturę metadanych. XML sam w sobie ma niewielką wartość — to tylko struktura. Ale rozmaite technologie bazujące na tej strukturze dają programistom i administratorom zawartości niespotykaną do tej pory elastyczność w zarządzaniu i przesyłaniu danych. XML ma już status ukończonego zalecenia W3C, co oznacza, że nic się nie zmieni aż do opublikowania nowej wersji standardu. Pełna specyfikacja XML 1.0 znajduje się pod adresem http://www.w3.org/ TR/REC-xml/. Specyfikacja ta jest trudną lekturą nawet dla programistów dobrze znających XML, więc polecałbym raczej zapoznanie się z jej doskonałą wersją opatrzoną komentarzami, dostępną pod adresem http://www.xml.com. Temat ten będzie w dalszych rozdziałach omawiany szczegółowo, więc teraz wystarczy pamiętać o dwóch podstawowych koncepcjach koniecznych do zrozumienia istoty dokumentu XML. Pierwsza mówi, że dokument XML, aby był w jakikolwiek sposób przydatny i mógł zostać przetworzony, musi być poprawnie uformowany. Poprawnie uformowany dokument to taki, w którym każdemu znacznikowi otwierającemu odpowiada znacznik zamykający, w którym znaczniki nie są zagnieżdżone niezgodnie z regułami oraz którego składnia jest zgodna ze specyfikacją. Ale zaraz — czyż nie powiedzieliśmy wcześniej, że XML nie posiada żadnych reguł składniowych? Niezupełnie.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 17
18
Rozdział 1. Wprowadzenie
Powiedzieliśmy, że nie ma reguł gramatycznych. Dany dokument może definiować własne znaczniki i atrybuty, ale wciąż musi być zgodny z regułami ogólnymi. Reguły te wykorzystywane są następnie przez aplikacje i parsery znające XML w celu odczytania danych z dokumentu i wykonania na nich pewnych czynności — np. znalezienia ceny krzesła czy utworzenia z danych dokumentu PDF. Zagadnienie to jest dokładniej omówione w rozdziale 2., Pisanie w XML-u. Druga istotna koncepcja związana z dokumentami XML mówi, że mogą one — ale nie muszą — być poprawne. Poprawny dokument to taki, który spełnia wymagania odpowiadającej mu definicji typu dokumentu DTD (o niej za chwilę). Mówiąc krótko, DTD definiuje gramatykę i zestaw znaczników na potrzeby określonego formatowania XML. Jeśli w dokumencie określono konkretną definicję DTD i jeśli jest on zgodny z tą definicją, to dokument taki określa się jako poprawny. Dokumenty XML mogą zostać także zawężone w ramach schematu — jest to nowy sposób narzucania formatu XML, który wyprze DTD. Dokument może być więc także zgodny ze schematem. W dalszej części książki zajmiemy się dokładniej przedstawionymi wyżej zagadnieniami. Najpierw jednak musimy poznać kilka skrótów i specyfikacji używanych wewnątrz dokumentu XML.
PI PI to instrukcja przetwarzania (ang. processing instruction) w dokumencie XML. Instrukcja przetwarzania nakazuje aplikacji wykonanie określonego zadania. Instrukcje PI, choć zajmują niewiele miejsca w specyfikacji XML, są na tyle ważne, że znalazły się wśród omawianych akronimów. PI wyróżnia się spośród innych danych w dokumencie XML tym, że oznacza polecenie przekazywane parserowi XML lub innemu programowi korzystającemu z dokumentu. W naszym przykładowym dokumencie 1.1 pierwszy wiersz, wskazujący wersję standardu XML, stanowi instrukcję przetwarzania. Informuje parsery, z której wersji standardu będziemy korzystać. Instrukcje przetwarzania mają postać . Wszystkie instrukcje PI, w których jako cel określono XML, należą do standardowego zestawu instrukcji określonego w ramach XML-a i powinny być rozpoznawane przez parsery. Nazywa się je często instrukcjami XML. Ale instrukcje PI mogą także zawierać informacje, które mają zostać wykorzystane przez aplikacje przechwytujące czynności parsera; w takim przypadku jako cel instrukcji przetwarzania można określić słowo kluczowe odpowiadające danej aplikacji (np. „cocoon”). Instrukcje przetwarzania nabierają dużego znaczenia, gdy dane XML wykorzystywane są w aplikacjach znających ten standard. Rozważmy aplikację, która przetwarza nasz przykładowy plik XML, a następnie tworzy reklamę mebli opartą na produktach wymienionych w dokumencie. Instrukcja przetwarzania może poinformować aplikację, że dany mebel jest na liście „zapotrzebowania” i że ma zostać przekazany do innej aplikacji, np. takiej, która wysyła zamówienia — zatem taki mebel ma nie pojawiać się w reklamie. Parser XML rozpozna instrukcje PI odwołujące się do celów zewnętrznych i przekaże je w niezmienionej postaci zewnętrznym aplikacjom.
DTD DTD to document type definition, czyli definicja typu dokumentu. DTD narzuca szereg ograniczeń na dokument (lub grupę dokumentów) XML. Definicja DTD sama w sobie nie jest specyfikacją, ale została zdefiniowana jako część specyfikacji XML. Deklaracja typu dokumentu wewnątrz dokumentu XML może sama zawierać ograniczenia odnośnie znaczników, ale może także odwoływać się do zewnętrznego dokumentu opisującego takie ograniczenia. Definicją typu dokumentu jest suma tych dwóch powyższych zestawów ograniczeń. DTD definiuje sposób, w jaki ma być skonstruowany dokument XML. Ponownie spójrzmy na przykład 1.1. Choć stworzyliśmy grupę własnych znaczników, to dokument taki jest bezużyteczny dla innej aplikacji, a nawet dla innego użytkownika, który nie potrafi zinterpretować naszych znaczników. Powstają wątpliwości — czy
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 18
Co to jest XML?
19
znacznik mówi nam, ile krzeseł jest na stanie? Czy atrybut drewno może zostać użyty w znaczniku ? Jeśli dokument ma zostać poprawnie przetworzony przez parser, na te wszystkie pytania trzeba znaleźć odpowiedzi. Dokument uważa się za poprawny, jeśli jest zgodny z ograniczeniami narzucanymi przez DTD odnośnie formatowania danych XML. Jest to szczególnie istotne, gdy zamierzamy przenosić dane pomiędzy aplikacjami — wymaga to uzgodnienia formatu i składni, aby dwa różne systemy mogły się porozumieć. Jak już wcześniej wspomniano, definicja DTD definiuje ograniczenia, mówiąc inaczej — zawęża konkretny dokument lub zestaw dokumentów XML. Programista lub autor zawartości tworzy także definicję DTD w postaci dodatkowego dokumentu, do którego odwołuje się z plików XML; może także zawrzeć ją w samym pliku XML — w każdym razie definicja nie ingeruje w żaden sposób w dokument XML. Tak naprawdę to właśnie DTD przyczynia się do przenośności danych XML. Może ona na przykład określać, że jedynymi poprawnymi wartościami atrybutu drewno są „klon”, „sosna”, „dąb” i „mahoń”. Dzięki temu parser potrafi określić, czy zawartość dokumentu jest do przyjęcia, i zapobiec ewentualnym błędom formatowania danych. Definicja DTD określa także kolejność zagnieżdżania znaczników. Może na przykład stanowić, że znacznik ma prawo być zagnieżdżany jedynie w znacznikach . Dzięki temu inna aplikacja, która otrzymuje nasz przykładowy plik XML, wie, jak przetworzyć i przeszukiwać otrzymane dane. Definicja DTD nie tylko przyczynia się do wysokiej elastyczności formatowania danych w XML-u, ale także umożliwia przetwarzanie i sprawdzanie poprawności danych w dowolnym systemie potrafiącym odnaleźć tę definicję.
Przestrzeń nazw Przestrzeń nazw to jedno z niewielu pojęć związanych z XML-em, dla których nie wymyślono akronimu. Nie ma takiej potrzeby — w tym przypadku nazwa dobrze odpowiada funkcji tego elementu. Przestrzeń nazw (ang. namespace) to odwzorowanie pomiędzy przedrostkiem elementu a identyfikatorem URI. Odwzorowanie to umożliwia zapobieżenie kolizjom przestrzeni nazw oraz określenie struktur danych, które pozwalają parserom zapobiec takim kolizjom. Przeanalizujmy przykład kolizji przestrzeni nazw. Załóżmy, że w dokumencie pomiędzy znacznikami i znajduje się znacznik . Ale w ramach definicji krzesła jest też znacznik , który przecież także może posiadać własny znacznik . Zauważmy również, że dokument może odwoływać się do innego dokumentu XML w celu pobrania informacji o prawach autorskich. W obu dokumentach powinny pojawić się znaczniki lub np. , ale jak rozpoznać, który znacznik odwołuje się do czego? Taka dwuznaczność to problem dla parsera XML. Czy znacznik ma być interpretowany w różny sposób zależnie od tego, w którym elemencie jest zagnieżdżony? Czy może autor zawartości pomyłkowo użył go w dwóch kontekstach? Bez informacji o przestrzeni nazw nie jest możliwe określenie, czy był to błąd w konstrukcji dokumentu XML, a jeśli było to działanie celowe — jak rozporządzić danymi kolidujących znaczników. Zalecenie opisujące przestrzenie nazw w XML-u definiuje mechanizm do kwalifikowania nazw. Mechanizm ten wykonuje swoje zadanie z użyciem identyfikatora URI, ale tego na razie nie musimy wiedzieć. Poprawne stosowanie znaczników w rodzaju opisywanego nie wymaga wpisywania niezbyt mądrych nazw w rodzaju czy . Zamiast tego przestrzeń nazw tworzona jest z wykorzystaniem przedrostka w postaci elementu XML — a więc na przykład: czy . Parser XML umie potem rozróżnić te przestrzenie i wcale nie trzeba tworzyć nowych nazw. Przestrzenie nazw są najczęściej używane w dokumentach XML, ale pojawiają się także w schematach i arkuszach sty-
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 19
20
Rozdział 1. Wprowadzenie
lów i w innych specyfikacjach powiązanych z XML-em. Zalecenie odnośnie przestrzeni nazw można znaleźć pod adresem http://www.w3.org/TR/REC-xml-names.
XSL i XSLT XSL to Extensible Stylesheet Language, czyli rozszerzalny język arkuszy stylów. Służy do przekształcania i tłumaczenia danych XML z jednego formatu XML na inny. Rozważmy przykład, w którym ten sam dokument XML musi zostać wyświetlony w formatach HTML, PDF i Postscript. Bez zastosowania XSL-a dokument XML musiałby być ręcznie skopiowany, a następnie przekształcony na jeden ze wspomnianych formatów. Mechanizm XSL umożliwia zdefiniowanie arkuszy stylów wykonujących to zadanie. Zamiast zmieniać dane pod kątem różnych reprezentacji, rozdzielamy dane (zawartość) od reprezentacji — właśnie za pomocą języka XSL. Jeśli dokument XML musi zostać odwzorowany w postaci innej reprezentacji, jest używany XSL. Wykonuje podobne zadanie jak program w Javie, który tłumaczy dane na format PDF lub HTML, ale zadanie to wykonuje z wykorzystaniem standardowego interfejsu. Tłumaczenie może dokonywać się za pomocą obiektów formatujących (ang. formatting objects) zawartych wewnątrz dokumentu XSL. Obiekty te mają postać specyficznych, nazwanych znaczników, pod które może zostać podstawiona zawartość składająca się na docelowy typ dokumentu. Typowym obiektem formatującym jest znacznik, który przez jakiś procesor wykorzystywany jest do przetworzenia dokumentu XML na PDF; w takim przypadku znacznik ten zostanie zastąpiony informacją specyficzną dla dokumentu PDF. Obiekty formatujące to specyficzne instrukcje XSL i choć zostaną pokrótce omówione, to jednak wykraczają poza tematykę niniejszej książki. Skoncentrujemy się natomiast na XSLT, całkowicie tekstowym procesie transformacyjnym. W wyniku działania procesu XSLT — transformacji rozszerzalnego języka arkuszy stylów (ang. Extensible Stylesheet Language Transformation) — tekstowy arkusz stylów XSL oraz tekstowy dokument XML są „łączone”, tworząc dane XML sformatowane zgodnie z arkuszem stylów XSL. Zagadnienie to przybliża przykład 1.2.
Przykład 1.2. Inny przykładowy plik XML Strona testowa WYSIWYG, czyli What you see is what you get!
Powyższy dokument zadeklarowano jako dane XML w wersji 1.0. Następnie podano położenie odpowiadającego mu arkusza stylu XSL, akuku.xsl. Podobnie deklaruje się definicję DTD — do DTD odwołujemy się w XML-u po to, aby określić strukturę danych, zaś do pliku XSL po to, aby określić, w jaki sposób dane mają być wyświetlane i prezentowane. W przykładzie 1.3 jest przedstawiony arkusz stylów XSL, do którego odwołujemy się w dokumencie.
Przykład 1.3. Arkusz stylów dla przykładu 1.2
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 20
Co to jest XML?
21
Ten arkusz stylu ma za zadanie przekształcić bazowy dokument XML na postać HTML-ową, nadającej się do obejrzenia w przeglądarce WWW. Wszystkie te zagadnienia zostaną omówione w dalszej części książki, tutaj przyjrzymy się tylko znacznikom . Każde wystąpienie takiego znacznika powoduje, że odpowiedni element, na przykład akapit, jest zamieniany na zawartość arkusza XSL — w tym wypadku powoduje to wstawienie znacznika
i ustawienie pochyłości czcionki. Po transformacji dokumentu XML zgodnie z pokazanym arkuszem XSL otrzymujemy taki wynik, jak w przykładzie 1.4.
Przykład 1.4. Wynikowy dokument HTML powstały z przykładów 1.2 i 1.3 Strona testowa
WYSIWYG, czyli What you see is what you get!
Nie szkodzi, że na razie nie są zrozumiałe wszystkie przedstawione konstrukcje XSL i XSLT; wystarczy, że Czytelnik zda sobie sprawę z faktu, iż korzystanie z XSL-a umożliwia uzyskanie bardzo różnych formatów dokumentów na podstawie tych samych danych bazowych XML. Więcej informacji na temat XSL-a zawarto w rozdziale 6., Przekształcanie XML-a. Standard XSL w organizacji W3C ma obecnie status Working Draft (wersja robocza). Zalecenie odnośnie standardu XSL można znaleźć pod adresem http://www.w3.org/Style/XSL.
XPath XPath (język XML Path Language) stanowi samodzielną specyfikację, ale jest intensywnie wykorzystywany w ramach XSLT. Specyfikacja XPath określa, w jaki sposób zlokalizować określony element dokumentu XML. W tym celu wykorzystuje się odwołania do specyficznych węzłów do-
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 21
22
Rozdział 1. Wprowadzenie
kumentu XML; węzeł (ang. node) oznacza dowolny komponent XML, taki jak element, atrybut czy dane tekstowe. Według specyfikacji XPath, dokument XML stanowi strukturę drzewiastą takich węzłów. Dostęp do dowolnego węzła można osiągnąć poprzez określenie położenia w danym drzewie. Szczegóły związane z XPath zostaną przedstawione dopiero przy dokładniejszym omawianiu XSL i XSLT, ale trzeba pamiętać, że z XPath będziemy korzystali wszędzie tam, gdzie musimy odwołać się do specyficznych danych wewnątrz dokumentu XML. Oto przykładowe wyrażenie XPath: *[not(self::JavaXML:Tytul)]
Powyższe wyrażenie oznacza wszystkie elementy potomne elementu bieżącego, których nazwa jest inna niż JavaXML:Tytul. W przypadku poniższego fragmentu dokumentu: Java i XML &OReillyCopyright;
wykonanie wyrażenia, gdy bieżącym węzłem jest element JavaXML:Ksiazka, spowoduje zwrócenie elementów JavaXML:Spis oraz JavaXML:Copyright. Pełna specyfikacja XPath jest dostępna pod adresem http://www.w3.org/TR/xpath.
XML Schema Schemat XML Schema to zamiennik definicji DTD o znacznie większych możliwościach. Służy do ograniczania (zawężania) dokumentów XML. Choć tylko pobieżnie omówiliśmy definicje DTD, to już można dostrzec dość poważne ograniczenia tego standardu: brak znajomości hierarchii, trudności w obsłudze konfliktów przestrzeni nazw i niemożność określenia dozwolonych relacji pomiędzy dokumentami XML. Ograniczenia te są ze wszech miar usprawiedliwione — skąd zespoły pracujące nad specyfikacją miały wiedzieć, że XML będzie wykorzystywany na tak wiele różnych sposobów? Jednak wady definicji DTD zaczęły dokuczać autorom i programistom korzystającym z XML-a. Najistotniejszą cechą XML Schema jest fakt, że umożliwia on zbliżenie definicji DTD do języka XML. To może wydawać się niejasne, ale przypomnijmy sobie, że każdy akronim, o którym ostatnio mówiliśmy, określany jest na podstawie standardu XML. Arkusze stylów, przestrzenie nazw i cała reszta korzystają z XML-a w celu zdefiniowania specyficznych zastosowań i właściwości tego języka. A definicje DTD są zupełnie inne — nie wyglądają jak XML, nie współdzielą hierarchicznej struktury XML-a i nawet nie reprezentują w ten sam sposób danych. Definicje DTD nie przystają więc zbytnio do świata XML, a ponieważ to właśnie one obecnie definiują, jak dokumenty XML mają wyglądać, łatwo się w tym wszystkim pogubić. Problem ten naprawia XML Schema — w standardzie tym definiowanie XML-a odbywa się za pomocą samego XML-a. Nie mówiliśmy jeszcze wiele o „definiowaniu danych opisujących dane”, ale także to potrafi XML Schema. Specyfikacja XML Schema pozwala w większym stopniu korzystać z konstrukcji tylko jednego języka; nie trzeba uciekać się do innych konstrukcji definicji DTD. Członkowie W3C i twórcy XML-a słusznie uznali, że przebudowa DTD nie przyniosłaby oczekiwanego rezultatu. Dlatego postanowiono stworzyć schematy XML Schema, poprawiając w ten sposób nieodłączne wady DTD oraz dodając nowe funkcje, odpowiadające współczesnym zastosowaniom
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 22
Co to jest XML?
23
XML-a. Więcej informacji o tym istotnym projekcie W3C można znaleźć pod adresami http://www. w3.org/TR/xmlschema-1 oraz http://www.w3.org/TR/xmlschema-2. Ciekawe wprowadzenie do XML Schema jest dostępne pod adresem http://www.w3.org/TR/xmlschema-0.
XQL... XQL to język zapytań, dzięki któremu w dokumentach XML można w prosty sposób odwoływać się do baz danych. XQL, choć jeszcze nie został formalnie przyjęty przez W3C, jest na tyle powszechny i przydatny, że faktycznie stanowi już popularną metodę uzyskiwania dostępu do baz danych z dokumentów XML. Struktura zapytania definiowana jest za pomocą pojęć XPath, a zestaw wynikowy — za pomocą standardowego XML-a i znaczników specyficznych dla XQL-a. Na przykład, poniższe wyrażenie XQL przeszukuje tablicę ksiazki i zwraca wszystkie rekordy, w których tytuł zawiera słowo „Java”. Dla każdego rekordu wyświetlany jest odpowiadający mu rekord w tablicy autorzy: //ksiazki[tytul contains "Java"] ( .//autorzy )
Zestaw wynikowy takiego zapytania może wyglądać następująco:
Najprawdopodobniej w miarę dojrzewania specyfikacji nastąpią w niej jakieś zmiany; na pewno jednak warto trzymać rękę na pulsie i interesować się XQL-em. Obecna propozycja standardu XQL znajduje się pod adresem http://metalab.unc.edu/xql/xql-proposal.html. Spotkała się ona z zainteresowaniem grupy W3C w styczniu 2000 roku, a obecne wymagania odnośnie języka zapytań XML można znaleźć pod adresem http://www.w3.org/TR/xmlquery-req.
... i cała reszta Mamy już za sobą pobieżne wprowadzenie do najważniejszych z omawianych w książce specyfikacji powiązanych z XML-em. Nie zostały omówione wszystkie liczące się akronimy, a jedynie te, które mają szczególne znaczenie przy omawianiu języka XML z punktu widzenia Javy. Pozostałe są wymienione poniżej, wraz z adresami odpowiednich zaleceń lub projektów roboczych: • Struktura Resource Description Framework (RDF): http://www.w3.org/TR/PR-rdf-schema/ • Język odsyłaczy XML (ang. XML Link Language — XLL): — XLink: http://www.w3.org/TR/xlink/ — XPointer: http://www.w3.org/TR/xptr/ • XHTML: http://www.w3.org/TR/xhtml-basic/ Kiedy Czytelnik będzie czytał tę książkę, prawdopodobnie powyższa lista będzie już nieaktualna — wciąż pojawiają się nowe rozwiązania w ramach XML-a. Nie wszystkie zostały szczegółowo omówione w tej książce, ale to wcale nie znaczy, że nie zasługują na uwagę. Nie są po prostu tak bardzo istotne przy omawianiu obsługi XML-a z poziomu Javy. Pełne zrozumienie XML-a na
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 23
24
Rozdział 1. Wprowadzenie
pewno wymaga opanowania zarówno tych technologii, które dokładniej opisujemy, jak i tych jedynie wspomnianych. Ale i tak może się zdarzyć, że w pewnym miejscu książki Czytelnik napotka standardy, które nie zostały omówione — wówczas takie zagadnienie zostanie dokładnie przedstawione.
Jak z tego korzystać? Wszystkie wspaniałe zalety XML-a nie na wiele się zdadzą, jeśli nie będzie narzędzi umożliwiających zastosowanie ich w naszym ulubionym środowisku programistycznym. Na szczęście, XML jest związany z Javą. Java posiada najpełniejszy wybór interfejsów API do obsługi XML-a bezpośrednio z kodu. Co prawda języki takie jak C, C++ i Perl nadrabiają zaległości w tym zakresie, ale to właśnie Java ustanawia standardy dotyczące sposobu obsługi XML-a z poziomu aplikacji. Z punktu widzenia aplikacji, cykl działania dokumentu XML dzieli się na dwa etapy (rysunek 1.1): przetwarzanie dokumentu i analizowanie danych w nim zawartych.
Rysunek 1.1. Cykl dzialania dokumentu XML z punktu widzenia aplikacji Jako programiści Javy, mamy dostęp do bardzo prostych sposobów wykonywania powyższych, a także wielu innych czynności.
SAX SAX to Simple API for XML, czyli prosty interfejs API do obsługi XML-a. Do przetwarzania danych XML — czyli procesu odczytywania dokumentu i rozbijania danych na odpowiednie elementy — udostępnia strukturę opartą na obsłudze zdarzeń. Na każdym etapie definiowane są zdarzenia, które mogą wystąpić. Na przykład SAX definiuje interfejs org.xml.sax.ContentHandler, który z kolei definiuje metody, takie jak startDocument() i endElement(). Za pomocą takiego interfejsu można uzyskać pełną kontrolę nad odpowiednimi etapami procesu przetwarzania dokumentu XML. Podobny interfejs stworzono do obsługi błędów i konstrukcji leksykalnych. Definiowany jest zestaw błędów i ostrzeżeń, co umożliwia obsługę różnych sytuacji mogących wystąpić w czasie przetwarzania dokumentu XML — np. napotkanie dokumentu niepoprawnego lub źle sformatowanego. Można dodawać nowe zachowania, co umożliwia obsługę zadań związanych z bardzo specyficznymi aplikacjami — a wszystko to w ramach standardowego interfejsu XML. Dokumentacja i inne informacje na temat interfejsu SAX znajdują się pod adresem http://www.megginson.com/SAX.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 24
Jak z tego korzystać?
25
Przed przystąpieniem do omawiania kolejnych zagadnień należy koniecznie wyjaśnić pewne nieporozumienie związane z interfejsem SAX. Otóż SAX często jestb uważany za parser XML-a. Nawet tutaj piszemy, że SAX umożliwia przetwarzanie danych XML. Ale tak naprawdę SAX udostępnia tylko strukturę dla parserów i definiuje zdarzenia procesu przetwarzania podlegające monitorowaniu. Żeby jakiekolwiek przetwarzanie mogło nastąpić, konieczne jest jeszcze zastosowanie zewnętrznego parsera. Powstało wiele świetnych parserów napisanych w Javie, takich jak Project X Suna, Xerces z Apache Software Foundation, XML Parser firmy Oracle i XML4J IBM-a. Wszystkie one podłączane są do interfejsu SAX i umożliwiają uzyskanie przetworzonych danych XML. Interfejs SAX udostępnia sposób na przetwarzanie dokumentu, ale sam nie jest parserem.
DOM DOM to interfejs API dla obiektowego modelu dokumentu (ang. Document Object Model). SAX umożliwia jedynie dostęp do danych w dokumencie XML, natomiast DOM pozwala na manipulowanie danymi. W ramach interfejsu DOM dokument XML reprezentowany jest jako struktura drzewiasta. Ponieważ taki sposób reprezentowania znany jest już od dawna, nie ma trudności z przeszukiwaniem i przetwarzaniem tego rodzaju struktur z poziomu języków oprogramowania — a więc także z poziomu Javy. W interfejsie DOM cały dokument XML wczytywany jest do pamięci, a wszystkie dane są składowane jako węzły, a więc bardzo szybko uzyskać można dostęp do poszczególnych części dokumentów — wszystko jest w pamięci dopóty, dopóki istnieje drzewo DOM. Poszczególne węzły odpowiadają konkretnym danym pobranym z oryginalnego dokumentu. DOM ma jednak pewną poważną wadę. Ponieważ cały dokument wczytywany jest do pamięci, zasoby szybko ulegają wyczerpaniu, często powodując spowolnienie działania aplikacji, a nawet uniemożliwienie jej poprawnego działania. Im większy i bardziej złożony jest dokument, tym bardziej spada wydajność. Należy o tym pamiętać — DOM to użyteczny i popularny, ale nie jedyny sposób manipulacji danymi XML. Interfejsowi temu poświęcimy nieco czasu; napiszemy także kod służący do manipulacji danymi wprost z interfejsu SAX. Charakterystyka aplikacji zadecyduje o tym, które rozwiązanie okaże się lepsze w danym projekcie programistycznym. Zalecenie W3C odnośnie interfejsu DOM znajduje się pod adresem http://www.w3.org/DOM.
JAXP JAXP to interfejs Javy do przetwarzania XML-a (ang. Java API for XML Parsing) firmy Sun. Interfejs ten, będący całkiem nowym narzędziem w arsenale programisty XML-a, ma na celu nadanie spójności interfejsów SAX i DOM. Nie jest rozwiązaniem konkurencyjnym w stosunku do nich, nie został też stworzony jako ich następca — JAXP oferuje uproszczone metody mające na celu ułatwienie korzystania z interfejsów Javy do obsługi XML-a. Jest zgodny ze specyfikacjami SAX i DOM, a także ze wspomnianym wcześniej zaleceniem dotyczącym przestrzeni nazw. JAXP nie definiuje zachowania interfejsów SAX ani DOM, ale zapewnia standardową warstwę dostępu dla wszystkich parserów XML-a z poziomu Javy. Oczekuje się, że JAXP będzie ewoluował w miarę modyfikacji interfejsów SAX i DOM. Można także założyć, że ostatecznie zajmie on miejsce wśród innych specyfikacji firmy Sun, jako że zarówno mechanizm serwletów Tomcat, jak i specyfikacja EJB 1.1 wymagają plików konfiguracyjnych i wdrożeniowych sformatowanych w XML-u. Choć specyfikacje J2EE® 1.3 oraz J2SE® 1.4 nie mówią jawnie o JAXP, to oczekuje się, że także w tych narzędziach zostanie zintegrowana obsługa JAXP. Pełna specyfikacja JAXP znajduje się na stronie http://java.sun.com/xml. Te trzy interfejsy składają się na warsztat programistów Javy mających do czynienia z formatem XML. Nie jest to zatwierdzone formalnie, ale właśnie ta trójka oferuje mechanizm pobierania
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 25
26
Rozdział 1. Wprowadzenie
i manipulowania danymi z poziomu zwykłego kodu w Javie. Interfejsy te będą nam potrzebne w całej książce; dowiemy się wszystkiego o klasach, które każdy z nich udostępnia.
Dlaczego warto korzystać z technologii XML? W powyższych podrozdziałach zostały omówione najważniejsze skróty opisujące technologie związane z XML-em. Być może Czytelnik zorientował się już, że XML to coś więcej niż kolejny sposób tworzenia warstwy prezentacji. Ale nie jest jeszcze pewien, w którym miejscu tworzonych aplikacji standard ten można zastosować. Nie udałoby nam się jeszcze przekonać pracodawcy, że powinniśmy poświęcić więcej czasu na poznawanie XML-a — po prostu nie umielibyśmy udowodnić mu, że XML może przyczynić się do stworzenia lepszej aplikacji. Chcielibyśmy pewnie nawet wypróbować to lub tamto narzędzie do pracy na dokumentach XML, ale nie wiemy, od czego zacząć. Jeśli Czytelnik ma teraz takie właśnie odczucie — zainteresowanie nową technologią i trudność zdecydowania, co robić dalej — to warto czytać dalej! W tej części poznamy relacje między XML-em a rzeczywistymi aplikacjami i uzasadnimy potrzebę korzystania z tego standardu. Najpierw zostanie omówione zastosowanie języka XML we współczesnych aplikacjach. Następnie autor przedstawi wsparcie oferowane dla XML-a i powiązanych z nim technologii — wszystko to w świetle aplikacji w Javie. Java oferuje cały wachlarz parserów, narzędzi przekształcających, mechanizmów publikacyjnych i struktur opracowanych specjalnie pod kątem XML-a. W zakończeniu zostaną omówione kierunki rozwoju XML-a. Te wiadomości będą argumentami służącymi do przekonania przełożonego (i jego przełożonych), jak bardzo XML liczy się teraz na rynku.
Java i XML — małżeństwo idealne Wiemy już, że XML to naprawdę przydatna technologia i że zdobywa ogromną popularność, jednak Czytelnik tej książki może sobie zadawać pytanie, dlaczego traktuje ona o Javie i XML-u, a nie tylko o tym ostatnim. Otóż Java idealnie pasuje do XML-a z jednego zasadniczego powodu: Java to przenośny kod, XML to przenośne dane. Oddzielnie obie te technologie są naprawdę bardzo dobre, ale mają ograniczenia. Java wymaga od programisty tworzenia formatów danych do przesyłania przez sieć i do prezentacji oraz korzystania z technologii typu Java Server Pages™ (JSP), które nie zapewniają faktycznej separacji warstw zawartości i prezentacji. XML to po prostu metadane i bez parserów czy procesorów XSL jest to „oprogramowanie - widmo”. Kiedy Java i XML zostaną połączone, wszystkie te ograniczenia programistyczne znikają. Kiedy napiszemy program w Javie, to mamy pewność, że skompilowany kod bajtowy będzie mógł zostać uruchomiony na dowolnym sprzęcie i w dowolnym systemie operacyjnym wyposażonym w wirtualną maszynę Javy (JVM). Jeśli do tego dodamy możliwość reprezentacji danych wejściowych i wyjściowych aplikacji z wykorzystaniem niezależnej od systemu, standardowej warstwy danych, to otrzymujemy całkowitą przenośność informacji. Aplikacja jest przenośna i porozumiewa się z dowolną inną aplikacją za pomocą tych samych (szeroko przyjętych) standardów. Mało tego, wspomnieliśmy już także, że Java udostępnia szerszy wachlarz interfejsów, parserów, procesorów, struktur publikacji i narzędzi dla XML-a niż jakikolwiek inny język. Pamiętając o tym wszystkim, zobaczymy, jak te dwie technologie współpracują dziś i jak ta współpraca będzie przebiegała w latach następnych.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 26
Dlaczego warto korzystać z technologii XML?
27
XML dzisiaj Wśród wielu programistów i pracowników firm branży technologicznej panuje przekonanie, że XML to rzeczywiście nowoczesna technologia, ale że nie jest jeszcze gotowa do zastosowania w aplikacjach krytycznych, od których zależy funkcjonowanie całej firmy. To mylna opinia. XML i spokrewnione technologie, o których wspomnieliśmy, trwale zadomowiły się w aplikacjach — przyjęły się znacznie szybciej niż zaprezentowana kilka lat temu Java. Najprawdopodobniej to właśnie XML pobił rekord Javy w zdobywaniu rynku informatycznego. My, programiści, musimy sobie zdawać sprawę z faktu, że te dwie technologie uzupełniają się, a nie zwalczają. Dane i aplikacje nie mogą być już bardziej przenośne niż te napisane w Javie i XML-u. Obydwie technologie są już teraz bardzo często stosowane.
XML w prezentacji danych XML najczęściej stosowany jest do rozdzielania zawartości od sposobu jej prezentacji. Zawartością (ang. content) nazywamy dane, które mają zostać wyświetlone u klienta, zaś prezentacją (ang. presentation) samo formatowanie tych danych. Na przykład, nazwa i adres pracownika działu administracji to zawartość, a sformatowana strona w HTML-u, z grafiką i emblematem firmy, to prezentacja. Zasadnicza różnica polega na tym, że zawartość jest uniwersalna względem aplikacji i może zostać poddana dowolnemu formatowaniu; prezentacja natomiast związana jest z konkretnym typem klienta (przeglądarką WWW, telefonem z obsługą Internetu, aplikacją Javy) oraz z funkcjami udostępnianymi przez klienta (HTML 4.0, język WML, Java™ Swing). Za zawartość odpowiedzialny jest wtedy XML, zaś za prezentację odpowiadającą poszczególnym klientom — XSL i XSLT. Jednym z największych wyzwań stojących przed współczesnymi aplikacjami, szczególnie aplikacjami WWW, jest różnorodność klientów z nich korzystających. Jeszcze dziesięć lat temu istniały niemal wyłącznie klienty pełne (ang. thick clients), z zainstalowanym oprogramowaniem pozwalającym na korzystanie z aplikacji. Trzy lata temu klienty aplikacji miały niemal zawsze postać przeglądarek internetowych „rozumiejących” HTML. Obecnie korzysta się z przeglądarek na najrozmaitszych platformach systemowych, popularne stały się telefony komórkowe z obsługą bezprzewodowego języka znaczników WML (ang. Wireless Markup Language) oraz asystenty kieszonkowe („organizery”) z obsługą podzbioru HTML-a. Taka różnorodność powoduje, że nieraz tworzy się niezliczone wersje tej samej aplikacji — po jednej wersji dla każdego typu klienta — a i tak nie wszystkie klienty są poprawnie obsługiwane. Aplikacja nie musi koniecznie obsługiwać np. telefonu komórkowego, ale przecież jeśli już odbiorcy lub pracownicy firmy wyposażeni są w takie urządzenia, to czy nie warto z nich skorzystać? Może i kieszonkowy asystent nie umożliwia wykonywania wszystkich operacji, jakie udostępnia zwyczajna przeglądarka WWW, ale czy osoby często przebywające w podróży nie docenią możliwości przynajmniej podstawowej obsługi swoich spraw w naszej firmie za ich pośrednictwem? Twórcy aplikacji i osoby zarządzające firmami mają dylemat — czy tak jak dawniej oferować wszystkie funkcje tylko wybranym klientom, czy, zgodnie z nowymi tendencjami, oferować jedynie ograniczone funkcje, ale wszystkim klientom? Odpowiedzią jest XML. Jak powiedzieliśmy wcześniej, XML nie jest technologią prezentacyjną. Jedynie może być użyty do wygenerowania warstwy prezentacyjnej. Jeśli Czytelnik nie dostrzega różnicy, może warto odwołać się do HTML-a jako technologii prezentacyjnej. HTML ma postać języka znaczników dobranych tak, że umożliwiają graficzne rozplanowanie elementów na stronie wyświetlanej przez przeglądarkę WWW. Ale przecież zastosowanie HTML-a nie jest dobrym sposobem reprezentacji danych. Dokumentu w tym formacie ani łatwo się nie przeszukuje, ani nie przetwarza. Format został zdefiniowany dość swobodnie, a przynajmniej połowę pliku zajmują znaczniki prezentacji — właściwe dane to tylko niewielki procent dokumentu. XML jest zupełnie inny. Ten język znaczników ukierunkowany jest na dane, a nie na prezentację. Niemal całość dokumentu XML to
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 27
28
Rozdział 1. Wprowadzenie
dane i opis ich struktury. Do danych nie odwołują się jedynie instrukcje dla parsera XML lub aplikacji przechwytujących. Przeszukiwanie dokumentu XML i jego przetwarzanie z wykorzystaniem interfejsów API i narzędzi jest proste, dzięki ściśle określonej strukturze narzuconej przez DTD lub schemat. XML jest całkowicie odłączony od warstwy prezentacji. Może jednak zostać wykorzystany do tworzenia warstwy prezentacyjnej poprzez zastosowanie spokrewnionych technologii XSL i XSLT. W ramach XSL tworzona jest definicja i konstrukcje odpowiedzialne za prezentacje, a także instrukcje informujące, jak te konstrukcje mają się do danych w dokumencie XML. XSLT umożliwia wyświetlenie oryginalnego dokumentu XML u klienta na różne sposoby, także poprzez bardzo złożony format HTML. Przez cały ten czas bazowy dokument XML pozostaje odseparowany od warstwy prezentacji. W każdej chwili może w równie prosty sposób zostać dostosowany do zupełnie innego stylu prezentacji, np. interfejsu użytkownika Swing, bez konieczności wprowadzania jakichkolwiek zmian w oryginalnych danych. Być może najpotężniejszą funkcją oferowaną przez XML i XSL na potrzeby prezentacji jest możliwość tworzenia wielu arkuszy stylów odpowiadających dokumentowi XML albo narzucania z zewnątrz takich arkuszy. Ta możliwość zwiększa elastyczność prezentacji: nie tylko można korzystać z tego samego dokumentu XML na potrzeby wielu prezentacji, ale struktura publikacji dokonująca transformacji może sama określić, od jakiego klienta nadeszło żądanie dokumentu, i wybrać odpowiedni arkusz stylu. Nie istnieje standardowy sposób przeprowadzania tego procesu nie ma też standardowych kodów odpowiadających różnym klientom, ale struktura publikacji XML pozwala na przeprowadzanie takich czynności w sposób dynamiczny. Proces tworzenia wielu arkuszy stylów XSL w ramach dokumentu XML nie jest zależny od produktu żadnej firmy, więc jedyne, co należy zrobić w dokumencie XML w celu wykorzystania go w strukturze publikacji, to wprowadzenie jednej czy dwóch instrukcji przetwarzających. Instrukcje takie, jeśli nie są obsługiwane ze strony aplikacji, zostają zignorowane, a więc oznakowane w ten sposób dane pozostają całkowicie przenośne i w stu procentach zgodne ze standardem XML.
XML w komunikacji Ten sam dokument XML może zostać nie tylko przekształcony, jak to zostało opisane wyżej, możliwe jest również przesłanie danych, które zawiera, do innej aplikacji. Wdrożenie takiej komunikacji nie sprawia żadnych trudności, ponieważ dane XML nie są związane z żadnym typem klienta — nie są nawet wykorzystywane bezpośrednio przez klienta. W szybki sposób uzyskać można również prostą reprezentację danych, łatwą do przesłania w sieci. To właśnie ten aspekt XML-a — łatwość przesyłania danych — jest chyba najbardziej niedoceniany. Aby zrozumieć, jak dużo daje możliwość komunikacji za pośrednictwem standardu XML, trzeba najpierw rozszerzyć swój sposób postrzegania aplikacji-klienta. Kiedy omawialiśmy prezentację, w typowy sposób założyliśmy, że klient to użytkownik uzyskujący dostęp do części aplikacji. Ale w dzisiejszych czasach jest to duże uproszczenie. Klient to tak naprawdę cokolwiek (tak, cokolwiek!), co uzyskuje dostęp do danych lub usług aplikacji. Klienty to np. komputery lub telefony komórkowe użytkowników; klienty to inne aplikacje, systemy składowania danych w rodzaju baz danych lub usług katalogowych; klientem może być nawet aplikacja łącząca się sama z sobą. Tak szerokie pojęcie klienta pozwala zrozumieć, jak duże znaczenie może mieć XML. Spróbujmy najpierw podzielić klienty na dwie grupy: klienty wymagające warstwy prezentacji i te, które warstwy takiej nie wymagają. Nie zawsze łatwo przeprowadzić taki podział. Na pewno formaty HTML i WML (Wireless Markup Language) to prezentacja, ale co zrobić, jeśli dane muszą zostać sformatowane jedynie nieznacznie na potrzeby innej aplikacji — np. poprzez odfiltrowanie danych zabezpieczonych lub zmianę nazw elementów? Tak naprawdę rzadko mamy do czynienia z klientem, który nie wymaga jakiegoś konkretnego formatowania danych.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 28
Dlaczego warto korzystać z technologii XML?
29
Powyższa próba kategoryzacji powinna przekonać Czytelnika, że dane niemal zawsze podlegają transformacji, często wielokrotnej. Rozważmy dokument XML, który został przekonwertowany na format użyteczny dla innej aplikacji za pomocą arkusza XSL (rysunek 2.2). Plik wynikowy jest wciąż dokumentem XML. Taka aplikacja może następnie wykorzystać dane do stworzenia nowego zestawu wynikowego i utworzenia nowego dokumentu XML. Nowe informacje mają zostać przekazane ponownie pierwszej aplikacji, a więc nowy dokument XML jest znów przekształcany na oryginalny format — choć teraz zawiera inne dane! To bardzo typowy scenariusz.
Rysunek 1.2. Transformacje XML/XSL pomiędzy aplikacjami To właśnie ten cykliczny proces przetwarzania dokumentu i nieustannego tworzenia nowej postaci wynikowej XML czyni z XML-a tak potężne narzędzie komunikacyjne. Ten sam zestaw zasad wykorzystywany jest na każdym etapie procesu. Zawsze rozpoczyna się od dokumentu XML, przekształca zgodnie z jednym lub więcej arkuszami stylów XSL i uzyskuje się dokument XML, który wciąż jest użyteczny dla tych samych narzędzi, które stworzyły dokument oryginalny. Zauważmy także, że XML to dane reprezentowane w sposób czysto tekstowy. Ponieważ reprezentacja tekstowa nie wymaga dużych zasobów i łatwo wykonać na niej serializację danych, zatem przesyłanie tego typu danych w sieci jest łatwiejsze. Niektóre formaty binarne można przesyłać bardzo wydajnie, a jednak tekstowy XML niemal zawsze okazuje się szybszym sposobem komunikacji.
XML-RPC Specyfikacja, w której skupiono się właśnie na wykorzystaniu XML-a w komunikacji, to XML-RPC. Opisuje ona komunikację nie tyle pomiędzy aplikacjami, co pomiędzy poszczególnymi elementami aplikacji albo współdzielonymi usługami działającymi pomiędzy aplikacjami. RPC to skrót utworzony od słów Remote Procedure Calls (zdalne wywoływanie procedur). Jest jednym z najważniejszych poprzedników technologii zdalnego wywoływania metod RMI (ang. Remote Method Invocation). Za pomocą RPC możliwe jest wywoływanie procedur poprzez sieć i — także poprzez sieć — otrzymywanie odpowiedzi. Należy zauważyć, że to rozwiązanie jest zasadniczo różne od RMI, technologii, która pozwala klientowi uruchamiać metody na danym obiekcie za pośrednictwem „namiastek” metod (ang. stub) i szkieletów ładowanych poprzez sieć. Główna różnica polega na tym, że wywołania RPC powodują wygenerowanie odpowiedzi zdalnie; odpowiedź taka zwracana jest później poprzez sieć, a klient nigdy nie komunikuje się bezpośrednio z obiektem
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 29
30
Rozdział 1. Wprowadzenie
zdalnym (żąda wywołania metody za pomocą interfejsów RPC). Natomiast technologia RMI umożliwia klientowi bezpośrednią interakcję z obiektem zdalnym i nie ma żadnego „pośredniczenia” w żądaniach. Bardziej szczegółowy opis zasady działania technologii XML-RPC można znaleźć pod adresem http://www.xml-rpc.com. Technologia XML-RPC stała się bardzo użytecznym sposobem odwoływania się do zdalnych usług. Z powodu trudności stworzenia standardowego modelu żądanie-odpowiedź, technologia RPC niemal zupełnie przestała być używana w programach w Javie — zastąpiono ją technologią RMI. Istnieją jednak sytuacje, w których wysyłanie i odbieranie danych tekstowych jest wydajniejsze niż ładowanie zdalnych „namiastek” i szkieletów. RPC obciążony jest historycznym problemem polegającym na tym, że zawsze starano się reprezentować złożone obiekty wyłącznie tekstowo (zarówno żądania, jak i odpowiedzi). Problem ten rozwiązano w XML-u i RPC znów okazuje się dobrym sposobem komunikacji systemów zdalnych. Dzięki standardowi umożliwiającemu reprezentowanie dowolnego typu danych za pomocą dokumentów tekstowych mechanizm XML-RPC potrafi odwzorowywać parametry egzemplarza (ang. instance) obiektu na elementy XML i w prosty sposób dekodować ten „graf” obiektu na serwerze. Uzyskana odpowiedź może znowu zostać przekształcona na „graf” XML-a i zwrócona klientowi (rysunek 1.3). Więcej informacji o mechanizmie XML-RPC można znaleźć w rozdziale 10., XML-RPC.
Rysunek 1.3. Komunikacja i powiadamianie w standardzie XML-RPC
Firma-firma Ostatni ze sposobów zastosowania XML-a w celach komunikacyjnych tak naprawdę nie stanowi oddzielnej metody czy specyfikacji; ponieważ jednak pojęcie komunikacji i handlu typu firma-firma (ang. business-to-business) jest ostatnio bardzo popularne, należy je objaśnić. Komunikacja firma-firma oznacza nie tyle porozumiewanie się dwóch różnych aplikacji, co komunikację pomiędzy przedsiębiorstwami, a nawet całymi branżami. XML jest w takich przypadkach naprawdę pomocnym narzędziem — zapewnia komunikację pomiędzy systemami zamkniętymi, umożliwiając wdrożenie usług, na które kiedyś mogły sobie pozwolić jedynie największe firmy. Przeanalizujmy to zagadnienie na przykładzie. Załóżmy, że niewielki, lokalny operator telekomunikacyjny sprzedaje swojemu klientowi linię do przesyłania danych, np. DSL lub T1. Pociąga to za sobą szereg procesów (rysunek 1.4). Trzeba złożyć zamówienie u dostawcy linii, należy skonfigurować router.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 30
Dlaczego warto korzystać z technologii XML?
31
O ustawieniach routera trzeba poinformować usługodawcę internetowego (ISP). Następnie przeprowadzana jest faktyczna instalacja, do wykonania której może zostać wynajęta — np. na zasadzie outsourcingu — jeszcze inna firma. W tej względnie prostej operacji sprzedaży łącza sieciowego biorą udział aż trzy firmy! Jeśli do tego dodamy techników zatrudnionych przez producenta routera, firmę telekomunikacyjną świadczącą inne usługi klientowi czy NASK zajmujący się rejestracją domeny, to cały proces zaczyna urastać do sporego problemu. Na szczęście proces ten można uprościć, właśnie za pomocą XML-a (rysunek 1.5). Wyobraźmy sobie, że pierwotne zamówienie na założenie łącza zostaje wprowadzone do systemu, który konwertuje je na dokument XML. Dokument ten jest przetwarzany (za pomocą XSL-a) na format, który można przesłać dostawcy linii. Dostawca linii dodaje do tego dokumentu własne informacje i przekształca go na nowy dokument XML, który zwracany jest naszemu lokalnemu operatorowi. Ten nowy dokument przekazywany jest firmie instalującej z dodatkowymi informacjami o tym, gdzie znajduje się firma klienta. Po instalacji firma instalująca dodaje do dokumentu notatkę informującą o powodzeniu lub niepowodzeniu instalacji i po przekształceniu (znów za pomocą XSL-a) dokument jest oddawany do głównej aplikacji u operatora. Elegancja tego rozwiązania polega na tym, że zamiast kilku systemów firmowych, z których każdy ma swój sposób formatowania, na każdym etapie wykorzystywany jest ten sam interfejs XML — bez względu na rodzaj aplikacji, systemu czy firmy.
Rysunek 1.4. Proces instalowania łącza sieciowego z wykorzystaniem systemów firmowych
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 31
32
Rozdział 1. Wprowadzenie
XML w konfiguracji Jeszcze jedno istotne zastosowanie standardu XML w aplikacjach i technologiach związanych z Javą ma miejsce na poziomie serwera aplikacji. Specyfikacja Enterprise JavaBeans (EJB) 1.1 wymaga, aby deskryptory wdrożeniowe dla programów JavaBean (definiujące sposób działania i inne parametry związane z EJB) oparte były na XML-u. Wcześniej w tym celu używano serializowanych deskryptorów wdrożeniowych. W środowisku programistów EJB tę zmianę przyjęto z zadowoleniem, ponieważ deskryptory wdrożeniowe nie muszą być już tworzone zgodnie z wymogami rozwiązań firmowych. Ponieważ deskryptory wdrożeniowe mają być teraz zgodne z wcześniej zdefiniowaną definicją DTD, wszyscy producenci wykorzystują te same deskryptory. To zaś przyczynia się do zwiększenia przenośności kodu EJB.
Rysunek 1.5. Proces instalowania łącza sieciowego z wykorzystaniem danych w standardzie XML Standard XML wykorzystywany jest także w konfiguracji interfejsu API serwletów (wersja 2.2). Plik XML opisuje konfigurację samego mechanizmu serwletów (określa parametry złącza, początkowy kontekst serwleta i inne aspekty specyficzne dla mechanizmu). Pliki konfiguracyjne XML wykorzystywane są także do konfigurowania indywidualnych serwletów — umożliwiają przekazywanie argumentów początkowych, konfigurację wielu nazw serwleta oraz dopasowywanie adresów URL w specyficznych kontekstach serwletów. Chociaż zarówno specyfikacja EJB 1.1, jak i mechanizm serwletów Tomcat są nowymi elementami w krajobrazie języka Java, to zastosowanie XML-a jako podstawy konfiguracji świadczy o tym, że firma Sun przyjęła strategię wykorzystywania standardu XML w tego typu zastosowaniach. W miarę wzrostu popularności parserów XML, pliki konfiguracyjne w XML-u będą stosowane coraz częściej, i to nie tylko w serwerach opartych na Javie — również w np. serwerach HTTP czy bazach danych.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 32
Dlaczego warto korzystać z technologii XML?
33
Obsługa XML-a W drugiej połowie 1999 roku standard XML przeżył rozkwit. Pojawiły się — a w chwili obecnej zyskały już stabilność i wysoką wydajność — parsery XML, procesory XSLT, struktury publikacji, edytory XML i zintegrowane środowiska programistyczne IDE, a także całe bogactwo narzędzi pokrewnych. Choć tematem tej książki jest interfejs API Javy do manipulacji danymi XML, to jednak parsery, procesory i inne elementy niewątpliwie stanowią część procesu przetwarzania XML-a, a więc poświęcone im będzie należne miejsce. Ponieważ technologia XML zmienia się niezwykle szybko, nie będziemy tutaj mówili o konkretnych wersjach — w czasie pojawienia się tej książki na rynku będą już zapewne nowsze. Ponadto jest bardzo możliwe, że wtedy dostępne będą nowe narzędzia. Jeśli Czytelnik nie znajdzie w książce informacji o obsłudze XML-a z poziomu określonego narzędzia, powinien uzyskać je od producenta oprogramowania.
Parsery Parser stanowi jedną z najważniejszych warstw aplikacji obsługującej XML. Element ten odpowiedzialny jest za niezwykle istotne zadanie analizowania dokumentu. Sprawdza, czy jest poprawnie sformatowany, czy umieszczono w nim odwołanie do odpowiedniej definicji DTD lub schematu; może także sprawdzać poprawność składni. Po takim przetworzeniu zazwyczaj uzyskiwana jest struktura danych — w naszym przypadku przeniesiona na grunt Javy — którą łatwo potem manipulować poprzez inne narzędzia XML lub interfejs API Javy. Nie będziemy teraz szczegółowo opisywać takich struktur, ponieważ są one przedstawione w kolejnych rozdziałach. Tymczasem wystarczy pamiętać, że parser to jeden z najważniejszych składników mechanizmu przetwarzania dokumentu XML. Wybór parsera XML nie jest zadaniem prostym. Nie obowiazują tutaj sztywne zasady, ale zazwyczaj brane są pod uwagę dwa kryteria. Pierwsze z nich to szybkość parsera. W miarę coraz częstszego wykorzystywania dokumentów XML i zwiększania ich złożoności szybkość parsera zaczyna mieć istotny wpływ na ogólną wydajność aplikacji. Drugie kryterium to zgodność ze specyfikacją XML. Ponieważ to właśnie wydajność jest często ważniejsza niż niektóre rzadko wykorzystywane cechy XML-a, niektóre parsery nie są w stu procentach zgodne ze specyfikacją XML. Użytkownik musi więc wypośrodkować pomiędzy tymi dwoma kryteriami, biorąc pod uwagę konkretne zastosowanie. Ponadto niektóre parsery potrafią sprawdzać poprawność składni XML na podstawie definicji DTD, a inne nie. Jeśli tego wymaga dana aplikacja, musimy skorzystać z parsera posiadającego taką umiejętność. Poniżej przedstawiony jest spis najpopularniejszych parserów XML. Nie zamieszczono tutaj informacji, czy dany parser wyposażony jest w funkcję sprawdzania poprawności składni, ponieważ w niektórych przypadkach funkcja taka jest właśnie dodawana. Nie przedstawiono tutaj także oceny tych parserów, ale informacje przedstawione na wymienionych stronach WWW powinny wystarczająco ułatwić wybór: • Apache Xerces, http://xml.apache.org • IBM XML4J, http://alphaworks.ibm.com/tech/xml4j • James Clark's XP, http://www.jclark.com/xml/xp • OpenXML, http://www.openxml.org • Oracle XML Parser, http://technet.oracle.com/tech/xml • Sun Microsystems Project X, http://java.sun.com/products/xml
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 33
34
Rozdział 1. Wprowadzenie
• Tim Bray's Lark and Larval, http://www.textuality.com/Lark • Grupa W3C poinformowała, że zamierza opublikować parser sprawdzający poprawność na podstawie schematu. Parser będzie oprogramowaniem typu open source.
Na tej liście celowo nie umieszczono parsera Microsoftu. Wygląda na to, że firma ta nie zamierza teraz ani w przyszłości utrzymywać zgodności ze standardami W3C. Microsoft najwyraźniej opracowuje własną wersję XML-a. Ileż to już razy przerabialiśmy... W każdym razie trzeba mieć się na baczności, gdy sytuacja zmusi nas do wykorzystania parsera Microsoftu, MSXML.
Procesory Po przetworzeniu dokumentu XML niemal zawsze następuje jego przekształcenie (transformacja). Przekształcenie to, jak już wspomnieliśmy, wykonywane jest za pomocą XSLT. Podobnie jak w przetwarzaniu, również na tym etapie obróbki dokumentu XML możemy wybierać spośród wielu narzędzi. Znów dwoma podstawowymi kryteriami wyboru są szybkość przekształcania i zgodność ze specyfikacjami XSL i XSLT. W czasie pisnia tej książki standard XSL zyskał status ukończonego zalecenia W3C, a więc obsługa konstrukcji i opcji XSL bardzo gwałtownie się rozwija. Najlepszym źródłem informacji o danym procesorze jest wymieniona strona WWW — tam znajdziemy informacje dotyczące zgodności narzędzia ze specyfikacjami, tam też są zamieszczone testy porównawcze. • Apache Xalan, http://xml.apache.org • James Clarks's XT, http://www.jclark.com/xml/xt • Lotus XSL Processor, http://www.alphaworks.ibm.com/tech/LotusXSL • Oracle XSL Processor, http://technet.oracle.com/tech/xml • Keith Visco's XSL:P, http://www.clc-marketing.com/xslp • Michalel Kay's SAXON, http://users.iclway.co.uk/mhkay/saxon
Struktury publikacji Struktura publikacji (ang. publishing framework) to termin nieco mglisty, nie stanowiący formalnej definicji. Na potrzeby niniejszej książki strukturą publikacji standardu XML nazwiemy zestaw narzędzi XML wykonujących przetwarzanie, przekształcanie (transformację) oraz dodatkowe czynności na dokumentach XML w aplikacji. Przetwarzanie i transformacja są zazwyczaj wykonywane za pomocą wspomnianych wyżej narzędzi; struktura publikacji łączy zaś wszystkie te operacje w jedną całość z interfejsem API Javy i zapewnia standardowy interfejs całości. W bardziej zaawansowanych strukturach możliwe jest przetwarzanie zarówno statycznych dokumentów XML, jak i tych stworzonych w aplikacjach Javy. Niektóre udostępniają także edytory i mechanizmy do tworzenia komponentów, dzięki czemu wygenerowany XML zgodny jest z wymaganiami narzuconymi przez daną strukturę. Ponieważ nie istnieje żadna specyfikacja określająca zachowanie takich struktur, wymienione poniżej rozwiązania są bardzo różne. Każde posiada cechy, które sprawiają, że warto się mu przyjrzeć bliżej. Niektóre struktury są rozprowadzane na zasadzie oprogramowania open source (OSS),
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 34
Dlaczego warto korzystać z technologii XML?
35
są więc nie tylko ogólnie dostępne, ale także otwarte w tym sensie, że można sprawdzić, w jaki sposób dane funkcje zostały zaimplementowane. Kiedy później zajmiemy się budową poszczególnych komponentów aplikacji, wybierzemy taką strukturę, która najlepiej pasuje do danego zadania. Teraz jednak decyzję tę odkładamy, aby Czytelnik mógł sam zdecydować, co jest dla niego najlepsze. • Apache Cocoon, http://xml.apache.org • Enhydra Application Server, http://www.enhydra.org • Bluestone XML Server, http://www.bluestone.com/xml • SAXON, http://users.iclway.co.uk/mhkay/saxon
Edytory i środowiska IDE dla standardu XML Istnieje wiele potężnych parserów i procesorów XML. Tego samego nie można jednak powiedzieć o edytorach. Niestety, XML jest w tym aspekcie w podobnej sytuacji, co kilka lat temu HTML. Niewielka grupa specjalizowanych programistów „pisze XML” zazwyczaj w vi, emacsie lub Notatniku. Pojawiły się ostatnio edytory przeznaczone specjalnie dla XML-a, są to jednak jeszcze projekty niedojrzałe i ich praktyczna przydatność jest niewielka. Istotne postępy robi na tym polu IBM — dotychczasowe wyniki pracy tej firmy można zobaczyć na stronie http://alphaworks.ibm.com. Odsyłacze do najświeższego oprogramowania można też znaleźć w świetnym serwisie http:// www.xmlsoftware.com.
Przyszłość XML-a Aby dopełnić obraz XML-a, spróbujmy jeszcze przewidzieć, jak standard ten będzie wykorzystywany w przyszłości. XML często określa się mianem „technologii przyszłości”. W niejednej firmie nie zdecydowano się na korzystanie z tego standardu, twierdząc że to technologia jeszcze niedopracowana. Jednocześnie wszyscy przyznają, że jej znaczny wpływ na sposób tworzenia aplikacji jest już przesądzony. Po tym, jak omówiliśmy sposoby zastosowania XML-a, trudno zgodzić się z tezą o jego niedojrzałości. Ale twierdzenie, że standard ten zrewolucjonizuje proces tworzenia aplikacji jest jak najbardziej prawdziwe. Nawet ci, którzy nie korzystają jeszcze intensywnie z XML-a, są świadomi, że wkrótce będą to musieli robić — a to „wkrótce” jest z każdym dniem bliżej. Mimo całego tego zamieszania otaczającego technologię XML oraz wielkich nadziei, jakie ona ze sobą niesie, prawie niemożliwe jest przewidzenie, jakie znaczenie będzie ona miała za rok, czy nawet za pół roku. To trochę tak, jakbyśmy jakieś cztery lata temu próbowali przewidzieć przyszłość tego śmiesznego języka obiektowego zwanego Javą, przydatnego, owszem, do budowania apletów... Są jednak pewne tendencje w XML-u, które pozwalają domyślić się, jakie zmiany nastąpią w najbliższym czasie. Przyjrzyjmy się tym najbardziej rzucającym się w oczy.
Repozytoria konfiguracji Jak już wspomnieliśmy, XML jest coraz częściej wykorzystywany w konfiguracjach serwerów, ponieważ umożliwia niezwykle prostą reprezentację danych idealnie spełnia potrzeby plików konfiguracyjnych. Tradycyjnie pliki takie były dość zawiłe, trudne w użyciu i modyfikacji oraz specyficzne dla danego producenta. Spójrzmy na fragment pliku konfiguracyjnego serwera HTTP Apache (przykład 1.5).
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 35
Te dwa pliki konfiguracyjne mają całkowicie inną składnię. Choć w różnych usługach wykorzystywane będą zazwyczaj różne definicje DTD i nazwy elementów, to jednak XML umożliwia sformalizowanie i ustandaryzowanie formatowania plików, przyczyniając się do powstania uniwersalnego języka konfiguracyjnego. To są naprawdę dobre wiadomości dla administratorów sieci, systemów i dla programistów. Czytelnik może zauważyć, że pliki konfiguracyjne już omawialiśmy — dlaczego powracamy do tego tematu? Obecnie każdy serwer posiada lokalny plik (lub pliki) konfiguracyjne. Choć w niektórych serwerach ostatnio umożliwia się także odczytywanie konfiguracji poprzez usługi katalogowe, to jednak ta metoda przyswajana jest powoli i wymaga znajomości protokołu usług katalogowych, najczęściej LDAP (Lightweight Directory Access Protocol). Widać coraz silniejszą tendencję tworzenia repozytoriów XML zawierających konfiguracje (rysunek 1.6). Coraz chętniej korzysta się również z interfejsu Java Naming and Directory Interface™ dla standardu XML (usługa podobna do udostępniania plików). W takiej sytuacji XML może funkcjonować albo oddzielnie od
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 36
Dlaczego warto korzystać z technologii XML?
37
usług katalogowych, albo jako abstrakcyjna warstwa „na” usługach katalogowych, pozwalająca aplikacjom posiadającym jedynie parser XML uzyskiwać ustawienia konfiguracyjne. To znacznie prostsze rozwiązanie niż udostępnianie serwerom bibliotek LDAP. Ponadto, w miarę wzrostu liczby serwerów poprawnie interpretujących język XML, możliwość przechowywania konfiguracji w jednym, centralnym miejscu pozwala zapewnić wzajemną współpracę poszczególnych elementów systemu. Serwery HTTP będą mogły rozpoznawać, jakie mechanizmy serwletów są dostępne, i samodzielnie konfigurować połączenia. Serwery JavaBean mogą odnajdywać usługi katalogowe w sieci i rejestrować tam swoje „fasolki”, a także odkrywać bazy danych, które można wykorzystać do przechowywania obiektów. To tylko niektóre możliwości wiążące się z wprowadzaniem serwerów sieciowych (korzystających ze wspólnego repozytorium XML) w miejsce serwerów samodzielnych.
Rysunek 1.6. Repozytorium konfiguracji oparte na XML-u
XSP XSP to Extensible Server Pages, czyli rozszerzalne strony serwera. To jeszcze jeden akronim związany z XML-em, którego wprowadzenie może mieć ciekawe konsekwencje dla programowania w Javie. Na razie standard ten ma status projektu roboczego autorstwa Ricardo Rocha i Stefano Mazzocchiego, głównych programistów pracujących nad projektem Apache Cocoon. W czasie pisania tej książki projekt nie był jeszcze przyjęty formalnie przez W3C ani inną organizację, ale kiedy książka będzie w sprzedaży, sytuacja ta może już przedstawiać się inaczej. Mówiąc skrótowo, XSP to zewnętrzny interfejs struktury XML. Umożliwia tworzenie dynamicznych stron
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 37
38
Rozdział 1. Wprowadzenie
XML przetwarzanych i przekształcanych w ramach struktury. Strony takie umożliwiają współpracę pomiędzy aplikacjami. W systemie składowane są jako pliki statyczne. Czytelnicy zaznajomieni z elementami Javy uruchamianymi po stronie serwera zapewne odnoszą wrażenie, że jest to coś w rodzaju JSP, a przynajmniej „XML-owej” wersji JSP. To do pewnego stopnia prawda. XSP to interfejs do XML-a; stanowi alternatywny język skryptowy do tworzenia stron i całych serwisów WWW. W wielu aplikacjach biznesowych pisanych w Javie duży nacisk kładzie się na odseparowanie zawartości od aplikacji i logiki biznesowej. Podobną rolę spełnia XSP w aplikacjach opartych na XML-u. Choć taką separację warstw w ramach skompilowanego kodu umożliwia wiele dostępnych obecnie struktur XML-a, to jednak zmiany w formatowaniu samych danych zawartych w dokumencie XML wymagają zmian w kodzie Javy i rekompilacji. A do tego dochodzą jeszcze zmiany wynikające z prezentacji za pomocą odpowiedniego arkusza stylów XSL. W ramach interfejsu XSP można także zdefiniować proces opisujący transformacje XSLT zachodzące w dokumencie — transformacje zarówno programowe, jak i prezentacyjne. Przyjrzyjmy się przykładowemu dokumentowi XSP (opartemu na przykładzie z projektu roboczego XSP — przykład 1.7).
Przykład 1.7. Prosta strona XSP Prosta strona w XSP
Czołem. Otwarto mnie razy.
W takiej stronie mamy tylko ładnie sformatowany i w prosty sposób weryfikowany język XML. Nie ma w nim logiki programistycznej. Na tym właśnie polega różnica pomiędzy językami XSP a JSP — logika i kod programu zdefiniowane są w odpowiednim arkuszu logiki (ang. logicsheet), a nie w samej stronie XSP. Dzięki temu zachowujemy całkowitą niezależność od języka takiej strony oraz abstrakcję konstrukcji specyficznych dla danego języka w ramach arkusza logiki. Transformację znacznika oraz reszty strony może obsługiwać np. arkusz logiki zaprezentowany w przykładzie 1.8.
Przykład 1.8. Arkusz logiki XSP java.lang.* private static int licznik = 0; private synchronized int obecnaLiczba() {
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 38
Co dalej?
39 return ++licznik; }
obecnaLiczba()
Nie trzeba chyba szczegółowo wyjaśniać zasady działania powyższych przykładów. XSP oferuje pewne nowe konstrukcje, takie jak oraz , ale poza tym dokument wygląda jak standardowy arkusz stylów XSL. Znaczniki XSP są zrozumiałe — pozwalają na wprowadzenie wyniku działania kodu w Javie. Choć interfejs XSP jest obecnie dostępny jedynie jako część projektu Apache Cocoon, to jest to pomysł niezwykle starannie przemyślany. Umożliwi oddzielenie aplikacji „rozumiejących” XML od szczegółów prezentacji w sposób bardziej wydajny niż do tej pory. Upraszcza także wprowadzanie samego XML-a, tak jak strony JSP zachęcały programistów do poznawania Javy i przechodzenia do bardziej złożonych interfejsów. XSP może także przyczynić się do większego upowszechnienia XML-a. Więcej informacji o XSP oraz pełny projekt roboczy tego standardu znaleźć można pod adresem http://xml.apache.org/cocoon/xsp.html.
Co dalej? Po tym niezwykle szybkim omówieniu technologii XML oraz interfejsów API Javy umożliwiających jej obsługę gotowi jesteśmy do zagłębienia się w szczegóły. Następne dwa rozdziały poświęcimy omawianiu składni XML-a oraz sposobu korzystania z tego standardu w aplikacjach WWW. To umożliwi nam zrozumienie dokumentów XML, jakie będziemy potem tworzyć, formatować i przetwarzać z poziomu naszych aplikacji. W następnym rozdziale szczegółowo opiszemy sposób tworzenia dokumentu XML; powiemy także, co to znaczy, że dokument XML jest „poprawnie sformatowany”. Zanim zaczniemy, jeszcze jedna istotna uwaga — szczególnie ważna dla tych, którzy tylko przejrzeli niniejszy rozdział. Technologię XML od samego jej poczęcia otacza atmosfera niezrozumienia i niedoinformowania. Autor niniejszej książki zakłada, że Czytelnik nie miał jeszcze kontaktu z XML-em, a zatem nie jest obciążony tego typu uprzedzeniami (szczególnie tym mówiącym, że XML służy do prezentacji). Nie będziemy omawiali dokumentów XML pod kątem ich prezentacji ani przekształcania informacji — raczej pod kątem sposobu zapisu prostych danych. To charakterystyczne podejście może na początku wydawać się dziwne, ponieważ większość osób słysząc „XML” wciąż jednak ma na myśli prezentację. Ale jako programiści Javy będziemy traktować dokumenty XML jako dane i nic więcej. Większa część tej książki jest poświęcona czynnościom innym niż formatowanie dokumentów XML — ich przetwarzaniu i obróbce. Siła XML-a leży
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 39
40
Rozdział 1. Wprowadzenie
w możliwości przesyłania danych z systemu do sytemu, aplikacji do aplikacji, firmy do firmy. Pozbycie się błędnych założeń odnośnie XML-a pomoże Czytelnikowi czerpać większą przyjemność z czytania tej książki, a także umożliwi poznanie sposobów zastosowania XML-a, o których być może nawet nie myślał.
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 40
Co dalej?
41
C:\WINDOWS\Pulpit\Szymon\Java i XML\01-08.doc — strona 41
2 Tworzenie dokumentów XML Teraz, gdy już wiemy, czym jest XML, do jakich celów może służyć oraz przez jakie interfejsy programowe Javy jest obsługiwany, pora przenieść tę wiedzę na grunt praktyczny. Niniejsza książka nie jest w żadnym razie pełnym podręcznikiem składni języka — nie może nawet służyć jako leksykon — nie można jednak omawiać przetwarzania dokumentów XML nie posiadając umiejętności tworzenia. Ponadto stosowanie interfejsów Javy do obsługi XML-a wymaga dość dobrej znajomości składni i struktury tego języka, a także sposobów tworzenia dokumentów, ich zawężania i przekształcania. Zanim więc zajmiemy się interfejsami Javy służącymi do obsługi języka XML, zapoznamy się z tymi czynnościami. W niniejszym rozdziale przyjrzymy się składni dokumentów XML. Rozpoczniemy od najbardziej podstawowych konstrukcji i powiemy, co to jest „poprawnie skonstruowany” dokument XML. Opiszemy różne zasady tworzenia dokumentów XML oraz pułapki składni. Po lekturze rozdziału Czytelnik będzie potrafił stworzyć dokument nie tylko poprawny, ale także nadający się do praktycznego zastosowania. Wszystko to będzie stanowiło wstęp do napisania pierwszego programu w Javie (w następnym rozdziale) i zrozumienia, na czym polega przetwarzanie kodu XML oraz jak Java odwołuje się do tego procesu. Ci, którzy mieli już kiedyś do czynienia z książkami opisującymi składnię języka programowania, zdają sobie sprawę, że takie opisy bywają dość nudne. My spróbujemy podejść do sprawy nieco inaczej. Zamiast rozpoczynać od jedno- czy dwuwierszowego pliku XML i dodawać do niego kolejne elementy (co kończy się stworzeniem długiego, ale bezużytecznego przykładu), rozpoczniemy od pełnego, dość złożonego dokumentu XML. Plik, którego użyjemy jako przykładu, to fragment rzeczywistego dokumentu XML reprezentującego spis treści tej książki. Przyjrzymy mu się wiersz po wierszu i opiszemy poszczególne konstrukcje. Przy omawianiu składni w oparciu o proste przykłady rzadko pamięta się o tym, że rozwiązania proste stosowane są w rzeczywistych aplikacjach bardzo rzadko. Zamiast tego najczęściej trafiają się złożone pliki, niezrozumiałe dla osób „wychowanych” na prostych przykładach. Trzeba zatem przyzwyczaić się do plików XML zawierających wszelkie możliwe konstrukcje — i zaczniemy właśnie od takich praktycznych przykładów. Można mieć nadzieję, że dzięki temu nasze wprowadzenie będzie bliższe rzeczywistości. Zanim zaczniemy, jeszcze jedna uwaga. Rozdział ten nie pretenduje do miana materiału referencyjnego. Innymi słowy, nie przedstawiamy poszczególnych pojęć wraz z ich definicjami, nie jest
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 41
42
Rozdział 2. Tworzenie dokumentów XML
to też skrótowe wprowadzenie do standardu XML. Będziemy posuwać się kolejnymi krokami naprzód — definicje podawane będą w kontekście przykładów oraz przedstawionych konstrukcji. Warto natomiast podczas lektury mieć pod ręką dobry leksykon XML-a, co pozwoli uniknąć powtarzania przedstawionych tu wiadomości w dalszej części książki, omawiającej sprawy bardziej skomplikowane. Dobrym przykładem takiej pozycji jest książka Roberta Ecksteina XML. Leksykon kieszonkowy (Helion, Gliwice 2000).
Dokument XML Zgodnie z obietnicą zaczniemy od praktycznego dokumentu XML, stanowiącego fragment spisu treści tej książki. Spójrzmy na przykład 2.1.
Przykład 2.1. Plik XML Java i XML Wprowadzenie Co to jest? Jak z tego korzystać? Dlaczego z tego korzystać? Co dalej? Tworzenie dokumentów XML Dokument XML Nagłówek Zawartość Co dalej? Przetwarzanie kodu XML Przygotowujemy się Czytniki SAX Procedury obsługi zawartości Procedury obsługi błędów Lepszy sposób ładowania parsera "Pułapka!" Co dalej?
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 42
Nagłówek
43
Struktury publikacji WWW Wybór struktury Instalacja Korzystanie ze struktury publikacji XSP Cocoon 2.0 i dalej Co dalej? &HelionCopyright;
Nagłówek Pierwszy element dokumentu dotyczy samego języka XML. Dokument stworzony w tym języku można podzielić na dwie podstawowe części: nagłówek, który przekazuje parserowi i aplikacjom XML informacje o tym, jak dany dokument ma być obsługiwany, oraz zawartość, czyli same dane XML. Pozwoli nam to odróżnić treść dokumentu XML od instrukcji przekazywanych aplikacjom z jego wnętrza. Zaczniemy od pierwszych kilku linijek przykładu, do miejsca JavaXML: Ksiazka (bez niego). Te początkowe wiersze to właśnie nagłówek dokumentu. Termin „nagłówek” nie jest formalnym pojęciem zdefiniowanym w specyfikacji XML, ale jest szeroko stosowany. W tej książce również będziemy z niego korzystać.
Instrukcje XML Pierwszym elementem każdego dokumentu XML jest instrukcja XML. Instrukcje języka XML stanowią określony podzbiór instrukcji przetwarzania (PI), o których mówiliśmy w poprzednim rozdziale. Powiedziano tam, że instrukcje PI są zazwyczaj przekazywane przez parser do aplikacji wywołującej i dopiero tam obsługiwane. Ale instrukcje przetwarzające, w których jako element docelowy podano xml, są przeznaczone dla samego parsera. Określają one rodzaj używanej wersji XML-a, arkusz stylów lub inne informacje potrzebne parserowi do poprawnego przetworzenia danych XML. Oto instrukcja XML:
Podobnie jak wszystkie inne instrukcje PI, ma ona postać i w tym przypadku określa, że używana wersja standardu XML to 1.0, a dokument nie jest samodzielnym dokumentem XML. Zauważmy, że instrukcja to niekoniecznie jedna para klucz=warto••; w tym przypadku określamy zarówno wersję dokumentu, jak i to, czy wymaga on jeszcze obecności jakiegoś innego dokumentu lub dokumentów. Kiedy parser „dowiaduje się”, że dany dokument nie jest samodzielny (ang. standalone), wie, że do określenia jego poprawności będzie konieczne wykorzystanie zewnętrznej definicji DTD. Gdyby atrybut standalone miał wartość yes, parser nie musiałby odwoływać się do zewnętrznej definicji DTD. W początkowej instrukcji może też znaleźć się deklaracja sposobu kodowania dokumentu. Tutaj, jak również w przedstawionym powyżej przykładowym dokumencie XML, tłumacz dodał informację o kodowaniu (encoding="ISO-8859-2") — bez niej w czasie późniejszego przetwarzania dokumentu parser zgłaszałby błędy po napotkaniu znaków spoza standardowego zestawu ASCII (czyli np. polskich liter ą, ć, ś itp.).
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 43
44
Rozdział 2. Tworzenie dokumentów XML
Kolejną grupę stanowią instrukcje XML odwołujące się do arkuszy stylów. W naszym przykładzie mamy dwie takie instrukcje:
Zazwyczaj instrukcja PI rozpoczynająca się od xml-[nazwa] odwołuje się do technologii związanej z XML-em i określonej w części [nazwa]. W tym przypadku chodzi o arkusz stylów, toteż instrukcja przekazywana jest do mechanizmu XSLT, a nie do parsera XML-a. W pierwszym wierszu odwołujemy się do domyślnego arkusza stylów, podając jego położenie i typ. W wierszu drugim widzimy odwołanie do alternatywnego arkusza stylów. W tym przypadku podano także atrybut media, który informuje procesor, dla jakiego rodzaju klientów jest przeznaczony dany arkusz. Mechanizmy publikacji omówimy w rozdziale 9., Systemy publikacji WWW.
Deklaracje typu dokumentu Po początkowych instrukcjach XML następuje deklaracja DOCTYPE. Deklaracja ta dysponuje własną, odrębną składnią, ponieważ jest wykorzystywana tylko do jednego celu — określenia definicji DTD wykorzystywanej w danym dokumencie XML. Spójrzmy jeszcze raz na deklarację DOCTYPE w naszym przykładzie:
W tym przykładzie definicją DTD dla dokumentu jest plik JavaXML.dtd, znajdujący się w lokalnym systemie plików. Parsery XML zazwyczaj dopuszczają stosowanie zwykłego lub lewego ukośnika (odpowiednio, dla systemów Unix i Windows), co pozwala uzyskać przenośność ścieżek dostępu pomiędzy różnymi platformami. Pierwszym parametrem deklaracji DOCTYPE jest element główny dokumentu, o którym powiemy za chwilę — tymczasem wystarczy informacja, że określa on bieżący dokument XML. Następnym argumentem jest albo słowo SYSTEM, albo PUBLIC. Ponieważ wpisaliśmy SYSTEM, parser XML będzie oczekiwał, że w następnym argumencie znajdzie się położenie pliku DTD, określone za pomocą jednolitego identyfikatora zasobów (URI). Słowo SYSTEM nie oznacza jednak, że plik DTD musi się koniecznie znajdować w systemie lokalnym. Można też użyć następującego zapisu:
Specyfikacja XML pozwala umieścić w tym miejscu dowolny poprawny identyfikator URI, a więc adres URL jest do przyjęcia. Użycie słowa PUBLIC oznacza, że definicja DTD, do której się odwołujemy, jest upubliczniona i mogą z niej korzystać wszyscy. W tym przypadku przed podaniem URI konieczne jest określenie nazwy publicznej. Poniższą deklarację można znaleźć na początku niektórych plików HTML, takich jak strona główna konsorcjum W3C, dostępna pod adresem http://www.w3c.org/:
Po słowie kluczowym PUBLIC następuje nazwa określająca publiczną definicję DTD wykorzystywaną w danym dokumencie. Parser XML najpierw stara się zlokalizować definicję DTD za pomocą jej nazwy, a dopiero kiedy to się nie uda — za pomocą dodatkowo podanego identyfikatora URI. Składnia nazwy publicznej definicji DTD jest szczegółowo opisana w specyfikacji XML, ale to na razie nie będzie nas interesować. Definicjom DTD i ich formatowi przyjrzymy się w rozdziale 4., Zawężanie XML.
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 44
Zawartość dokumentu
45
Zawartość dokumentu Skoro nagłówek mamy już za sobą, możemy przejść do zawartości dokumentu XML. Składają się na nią elementy, atrybuty i opisywane przez nie dane tekstowe.
Element główny Element główny (ang. root element) to najwyższy w hierarchii element dokumentu XML. Jego definicję stanowią zawsze pierwszy znacznik otwierający i ostatni znacznik zamykający w dokumencie. Element ten stanowi punkt wyjścia dla parsera lub aplikacji XML, umożliwiając im rozpoznanie początku i końca dokumentu. W naszym przykładzie elementem głównym jest :
Znacznik ten, w połączeniu z odpowiadającym mu znacznikiem zamykającym, otacza całą pozostałą zawartość dokumentu XML. Specyfikacja XML zezwala na obecność tylko jednego elementu głównego w dokumencie. Innymi słowy, element taki musi „obejmować” wszystkie inne elementy. Poza tym jednym wymogiem element główny nie różni się od innych elementów XML. Warto o tym pamiętać, ponieważ dokumenty XML mogą zawierać inne dokumenty XML lub odwoływać się do nich. W takich przypadkach element główny dokumentu, do którego się odwołujemy, staje się elementem wewnętrznym dokumentu, z którego nastąpiło odwołanie i musi zostać normalnie obsłużony przez parser XML. Dzięki temu, że elementy główne zdefiniowano jako standardowe elementy XML (nie posiadające specjalnych właściwości czy zachowań), dołączanie innych dokumentów XML przebiega bezproblemowo.
Identyfikacja elementów za pomocą przestrzeni nazw Nie będziemy tutaj zbytnio zagłębiać się w temat przestrzeni nazw, ale wypada zdawać sobie sprawę ze sposobu ich użycia w elemencie głównym. Jak można było zauważyć, wszystkie nazwy elementów XML mają przedrostek JavaXML. Być może później zajdzie potrzeba dołączenia do przykładowego dokumentu fragmentów innych książek wydawnictwa O’Reilly. Ponieważ każda z nich może także posiadać znaczniki , lub , dokument musi zostać skonstruowany tak, aby wyeliminować możliwość kolizji przestrzeni nazw z innymi dokumentami. Specyfikacja przestrzeni nazw w języku XML rozwiązuje ten problem w bardzo prosty sposób. Ponieważ nasz dokument odpowiada konkretnej książce, której nie będzie odpowiadał żaden inny dokument XML, można użyć przedrostka w rodzaju JavaXML, powodującego przypisanie elementu do danej przestrzeni nazw. Specyfikacja przestrzeni nazw wymaga przypisania danemu przedrostkowi niepowtarzalnego identyfikatora URI, tak aby możliwe było odróżnienie elementów w danej przestrzeni nazw od elementów w innych przestrzeniach. Zaleca się użycie do tego celu adresu URL i tak właśnie robimy w naszym przykładzie (http://www.oreilly.com/ catalog/javaxml — strona WWW tej książki):
Do zdefiniowanej w ten sposób przestrzeni nazw można odwoływać się z dowolnego elementu w danym dokumencie XML. W naszym przypadku przestrzeń wykorzystujemy we wszystkich elementach, wszystkie bowiem do niej należą. Poprawnym sposobem związania elementu z prze-
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 45
46
Rozdział 2. Tworzenie dokumentów XML
strzenią nazw jest podanie jej identyfikatora jako przedrostka oddzielonego dwukropkiem od właściwej nazwy elementu: Wprowadzenie Co to jest? Jak z tego korzystać? Dlaczego z tego korzystać? Co dalej?
Każdy z tych elementów jest traktowany przez parser XML jako część przestrzeni nazw http:// www.oreilly.com/catalog/javaxml/, dzięki czemu nie wystąpią kolizje z innymi elementami o nazwach Rozdzial, Naglowek czy Temat, zawartymi w innych przestrzeniach nazw. W tym samym dokumencie można umieszczać wiele deklaracji przestrzeni nazw — wszystkie za pomocą jednego elementu:
Powyższa deklaracja jest poprawna, jednak przy wykorzystywaniu wielu przestrzeni nazw w jednym dokumencie trzeba zachować dużą ostrożność. Często korzyści z takiego rozwiązania mogą zostać zniwelowane przez komplikację i konieczność umieszczania dodatkowego tekstu w dokumencie. Zazwyczaj pojedyncza przestrzeń nazw umożliwia stworzenie jednego, „czystego” dokumentu XML z jednoczesnym uniknięciem kolizji; jedynym istotnym wyjątkiem jest przypadek, gdy wykorzystywana jest inna specyfikacja XML (np. XML Schema) i gdy trzeba dla niej użyć osobnej przestrzeni nazw. Na koniec jeszcze jedno interesujące (i może nieco skomplikowane) zagadnienie. Specyfikacja XML Schema, dokładniej omówiona w rozdziale 4., wymaga określenia schematu dokumentu XML w sposób bardzo przypominający zestaw deklaracji przestrzeni nazw (przykład 2.2).
Wystąpiło tutaj kilka zjawisk, które należy dobrze zrozumieć. Po pierwsze, dla danego egzemplarza (ang. instance) schematu XML (XML Schema) definiowana jest — i kojarzona z określonym identyfikatorem URI — przestrzeń nazw. Przestrzeń ta, o skrótowej nazwie xsi, służy do poda-
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 46
Zawartość dokumentu
47
wania w dokumencie XML informacji o danym schemacie — dokładnie tak, jak robimy to tutaj. Dlatego w pierwszym wierszu udostępniamy elementy naszego egzemplarza schematu XML do wykorzystania w całym dokumencie. W następnym wierszu definiujemy przestrzeń nazw samego dokumentu XML. Ponieważ nie są w nim wykorzystywane żadne jawne przestrzenie nazw (inaczej niż w poprzednim przykładzie), deklarujemy domyślną przestrzeń nazw. Specyfikacja przestrzeni nazw XML mówi, że każdy element dokumentu XML musi należeć do jakiejś przestrzeni nazw; domyślna przestrzeń nazw to ta, z którą skojarzony jest element w przypadku, gdy nie podano żadnej innej przestrzeni. Oznacza to, że wszystkie elementy bez przedrostka przestrzeni nazw (czyli w tym przykładzie wszystkie) kojarzone są z tą właśnie przestrzenią domyślną. Przy takim zdefiniowaniu przestrzeni nazw dokumentu oraz schematu XML możemy już zrobić to, co chcieliśmy, czyli skojarzyć schemat z dokumentem. Służy do tego atrybut schemaLocation, należący do przestrzeni nazw XML Schema. Przed tym atrybutem stawiamy jego przestrzeń nazw (xsi), którą przed chwilą zdefiniowaliśmy. Argument atrybutu składa się właściwie z dwóch identyfikatorów URI: pierwszy określa przestrzeń nazw skojarzoną ze schematem, a drugi jest identyfikatorem schematu, do którego się odwołujemy. W naszym przykładzie pierwszy identyfikator to domyślna przestrzeń nazw, którą właśnie zadeklarowaliśmy, a drugi to plik mySchema.xsd w lokalnym systemie plików. Jak każdy inny atrybut XML, para ta umieszczana jest wewnątrz cudzysłowów. I oto mamy w dokumencie odwołanie do schematu! Mówiąc poważniej, zagadnienie to nie jest takie proste i wciąż stanowi jeden z najtrudniejszych dla użytkowników aspektów używania przestrzeni nazw i schematu XML. Do całego opisanego wyżej mechanizmu wrócimy wkrótce, na razie zaś wystarczy zrozumieć, że przestrzenie nazw umożliwiają wykorzystanie elementów z różnych grup, przy czym elementy te cały czas pozostają nieodłącznym fragmentem grup, z których pochodzą.
Elementy danych Do tej pory unikaliśmy definiowania pojęcia „element”. Teraz nadeszła pora, aby wyjaśnić je dokładnie. Element opatrzony jest dowolną nazwą i musi zawierać się w nawiasach kątowych. W naszym przykładowym dokumencie obecne są elementy różnego rodzaju: Struktury publikacji WWW
Pierwsza zasada tworzenia elementów mówi, że nazwa elementu musi rozpoczynać się literą lub podkreśleniem, po którym może wystąpić dowolna liczba liter, cyfr, podkreśleń, łączników lub kropek. Nazwy nie mogą zawierać spacji — poniższy przykład nie jest poprawnym elementem XML:
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 47
48
Rozdział 2. Tworzenie dokumentów XML
W nazwach elementów XML rozróżniana jest wielkość liter. Mówiąc ogólnie, zastosowanie tych samych zasad, których używa się przy tworzeniu nazw zmiennych Javy, pozwala uzyskać poprawne nazwy elementów XML. Nie powinno się skracać nazw typu „Obiekt telekomunikacyjny” do postaci (bo staną się niezrozumiałe), z drugiej strony nie powinno się też stosować elementów typu , bo niepotrzebnie zaśmiecają dokument. Warto pamiętać, że dokumenty XML będą najprawdopodobniej oglądane przez innych programistów, a więc dobrze jest używać znaczących, „samodokumentujących się” nazw. Każdy otwarty element musi zostać zamknięty. I nie ma tutaj wyjątków, tak jak to jest w innych językach znaczników, np. w HTML-u. Znacznik końcowy składa się z ukośnika i nazwy elementu, np. . Pomiędzy znacznikiem otwierającym a zamykającym może znajdować się dowolna ilość danych tekstowych lub innych elementów. Nie można jednak zmieniać kolejności zagnieżdżania — pierwszy otwarty element musi zostać zamknięty jako ostatni. Jeśli któraś z tych zasad nie zostanie spełniona, dokument XML będzie niepoprawnie skonstruowany. Poprawnie skonstruowany (ang. well-formed) dokument to taki, w którym przestrzega się zasad składni języka XML, a wszystkie elementy i atrybuty znajdują się we właściwych miejscach. Jednakże poprawnie skonstruowany dokument niekoniecznie musi być poprawny (ang. valid), tzn. nie musi być zgodny z ograniczeniami narzuconymi przez DTD lub schemat. Pomiędzy dokumentem poprawnie skonstruowanym a poprawnym jest duża różnica; w tym rozdziale zajmujemy się tylko poprawnością konstrukcji, natomiast reguły określające poprawność dokumentu przedstawimy w rozdziale 4. Oto przykład dokumentu niepoprawnie skonstruowanego:
Kolejność zagnieżdżania nie jest prawidłowa, ponieważ po znaczniku otwierającym nie znajduje się znacznik zamykający . Ale po poprawieniu tego błędu składniowego nie możemy jeszcze być pewni, że cały dokument jest poprawny. To istotna różnica, do której wrócimy w rozdziale 4. Powyższy przykład niepoprawnie sformatowanego dokumentu może się wydawać banalny, ale z drugiej strony warto zauważyć, że w HTML-u byłby to zapis poprawny — i często pojawia się w dużych tabelach wewnątrz dokumentów zapisanych w tym języku. Innymi słowy, HTML i wiele innych języków znaczników nie wymaga tworzenia poprawnie sformatowanych dokumentów w takim sensie, w jakim jest to rozumiane w definicji XML. Ścisłe przestrzeganie zasad zagnieżdżania obowiązujących w XML-u umożliwia dużo szybsze przetwarzanie i sprawniejszą obsługę zapisanych w nim dokumentów. Ostatnia zasada, jaką omówimy, dotyczy dość oryginalnych elementów pustych. Powiedzieliśmy już, że znaczniki XML muszą występować parami; na kompletny element XML składają się znaczniki otwierający i zamykający. Są jednak przypadki, w których element występuje w dokumencie samodzielnie, informując np. o tym, że rozdział nie jest ukończony; są też elementy posiadające atrybuty, ale nie zawierające danych tekstowych — np. obrazek w HTML-u. Elementy takie należałoby zapisywać w następujący sposób:
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 48
Zawartość dokumentu
49
Zapis ten jest niezbyt rozsądny i powoduje zwiększenie bałaganu w dokumencie. Specyfikacja XML dopuszcza jego uproszczenie poprzez zawarcie znacznika otwierającego i zamykającego w jednym elemencie:
Rozwiązuje to problem elegancji zapisu, a jednocześnie nie narusza zasady, że każdy element XML musi mieć odpowiednik zamykający — tutaj jeden element jest jednocześnie otwierającym i zamykającym.
Atrybuty elementów Oprócz tekstu zawartego pomiędzy znacznikami, element może także posiadać atrybuty. Atrybuty, wraz z odpowiadającymi im wartościami, zawarte są w deklaracji otwierającej elementu (która może być także deklaracją zamykającą!). Np. częścią znacznika JavaXML:Rozdzial był atrybut określający tematykę rozdziału:
W powyższym przykładzie tematyka to nazwa atrybutu, a jego wartościami są, odpowiednio, XML i Java. Nazwy atrybutów podlegają takim samym zasadom jak nazwy elementów XML, a ich wartości muszą znajdować się w cudzysłowach. Dopuszczalne są tu również apostrofy, ale standardowo używa się cudzysłowów — odpowiada to zresztą konwencji przyjętej w Javie. Apostrofy i cudzysłowy można także wykorzystywać wewnątrz wartości atrybutu — jeśli znajduje się ona w cudzysłowach, to wewnątrz można użyć apostrofów, i odwrotnie. Nie jest to jednak metoda zalecana, jako że parsery i procesory XML często automatycznie przekształcają wszystkie cudzysłowy otaczające wartości atrybutów na apostrofy (lub odwrotnie), co może spowodować nieprzewidywalne skutki. Ważne jest nie tylko to, jak należy używać atrybutów, ale także gdzie ich używać. XML umożliwia tak rozmaite formatowanie danych, że rzadko kiedy nie jest możliwe wyrażenie atrybutu przez element, lub odwrotnie — elementu przez atrybut. Nie istnieje żadna specyfikacja ani ogólnie przyjęty standard mówiący o tym, kiedy używać atrybutu, a kiedy elementu, ale można tutaj podać pewną radę praktyczną: elementów używa się do opisywania danych przeznaczonych do prezentacji, a atrybutów — do opisywania danych systemowych. Dane, które mają zostać przedstawione klientowi albo aplikacji, najprawdopodobniej zostaną opisane za pomocą elementu. Mogą one być później traktowane jako dane aplikacji, które można w prosty sposób przeszukiwać i wykorzystywać. Jako przykład może tu posłużyć tytuł rozdziału książki, cena mebla czy adres URL firmowej witryny WWW. Jeśli jednak dane służą do grupowania lub za ich pomocą przekazujemy aplikacji informację o sposobie obsłużenia innej porcji danych, czy też wreszcie nie są bezpośrednio widziane przez klienta lub aplikację XML-ową, najprawdopodobniej będą miały postać atrybutu. Przykładami „dobrych kandydatów” na atrybuty mogą być części książki — sama część może być elementem posiadającym własny tytuł, ale grupowanie znajdujących się w niej rozdziałów da się w prosty sposób opisać atrybutem czesc w elemencie JavaXML:Rozdzial. Atrybut taki umożliwia proste indeksowanie części, ale nie będzie prezentowany bezpośrednio użytkownikowi. Innym dobrym przykładem jest sytuacja, w której klient zapłacił zadatek za dany
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 49
50
Rozdział 2. Tworzenie dokumentów XML
towar (np. krzesło czy stół), „rezerwując” go w ten sposób dla siebie. Poinformowana o tym aplikacja XML mogłaby wygenerować broszurę lub ulotkę, w której taki towar nie byłby już zawarty. I znów — klient nie widziałby bezpośrednio tej informacji, ale dane zostałyby wykorzystane przy obsłudze i przetwarzaniu dokumentu XML. Jeśli po tym wszystkim nadal jeszcze nie wiadomo, czego użyć, zawsze można postąpić asekuracyjnie i zastosować element. Można też wyobrazić sobie inne sposoby reprezentacji przedstawionych przykładów. Zamiast np. korzystać z atrybutu opisującego część, można zagnieździć elementy JavaXML:Rozdzial w elemencie JavaXML:czesc; w drugim przykładzie użycie pustego znacznika pozwoliłoby na sprawniejsze oznakowanie „zarezerwowanych” mebli. W języku XML rzadko mamy do czynienia z jednym tylko sposobem reprezentowania danych. Najczęściej to aplikacja i sposób wykorzystania danych determinują sposób postępowania. Nie będziemy więc pisać o tworzeniu zapisów w XML-u (byłoby to trudne), ale o ich używaniu — i właśnie przez praktyczne używanie Czytelnik będzie mógł poznać sposoby obsługi i użycia różnych reprezentacji danych. Ta wiedza umożliwia Czytelnikowi samodzielne podejmowanie decyzji o sposobie formatowania dokumentów XML.
„Stałe” języka XML Nie wspomnieliśmy jeszcze o sekwencjach unikowych i odwoływaniu się do innych, stałych wartości. Dla przykładu, typową reprezentacją ścieżki do katalogu instalacyjnego jest zapis lub . W obu przypadkach tekst taki zostałby zamieniony przez użytkownika na nazwę wybranego katalogu instalacyjnego. W rozdziale opisującym aplikacje WWW jest przedstawiony przykład, w którym konieczne jest podanie szczegółowych informacji o instalacji i sposobie korzystania z aplikacji Apache Cocoon. Może zatem zajść potrzeba użycia takiej reprezentacji danych w elemencie: Instalacja Cocoon Plik Cocoon.properties znajduje się w katalogu /bin.
Kłopot polega na tym, iż parser XML spróbuje obsłużyć takie dane jako znacznik XML i zakomunikuje o błędzie, ponieważ nie znajdzie znacznika zamykającego. Jest to typowy problem związany z każdą sytuacją, w której konieczne jest zastosowanie nawiasów kątowych. Można go rozwiązać za pomocą encji (ang. entity reference). Encja to specjalny typ danych XML służący do odwoływania się do innych rodzajów danych. Składa się ona z niepowtarzalnej nazwy poprzedzonej znakiem ampersand (&) i zakończonej średnikiem, tj. &[nazwa-encji];. Po napotkaniu encji parser XML podstawia pod nią odpowiednią wartość i nie przetwarza jej dalej. W języku XML zdefiniowano pięć encji, które pomogą rozwiązać powyższy problem: < to otwierający nawias kątowy (lub symbol „mniejsze niż”), > to zamykający nawias kątowy (lub symbol „większe niż”), & to sam znak ampersand, " to cudzysłów, a ' — apostrof. Za pomocą tego rodzaju encji możemy przedstawić odwołanie do katalogu instalacyjnego w następujący sposób: Instalacja Cocoon Plik Cocoon.properties znajduje się w katalogu <sciezka-doCocoon>/bin.
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 50
Co dalej?
51
Po przetworzeniu tego dokumentu pogrubiony zapis zostanie zinterpretowany jako i dokument zostanie zaakceptowany jako poprawnie sformatowany. Należy także pamiętać, że encje mogą być definiowane przez użytkownika, co pozwala tworzyć swoiste zapisy skrótowe. W przedstawionym na początku rozdziału dokumencie XML znajduje się odwołanie do zewnętrznego, współużytkowanego tekstu noty o prawach autorskich. Ponieważ ten sam dokument wykorzystywany jest w wielu książkach wydawnictwa, nie chcemy zamieszczać go w całości w dokumencie XML. Jeśli jednak prawa autorskie ulegną zmianie, nasz dokument powinien te zmiany odzwierciedlić. Być może Czytelnik zauważył, że składnia wykorzystana w przykładowym dokumencie XML wygląda jak predefiniowana encja: &HelionCopyright;
Co prawda dopiero w części omawiającej definicje DTD wyjaśnimy, w jaki sposób XML ustala, do czego ma się odwołać po napotkaniu encji &HelionCopyright;, ale już teraz należy zauważyć, że encje służą do wielu innych zadań, nie tylko do reprezentowania nietypowych znaków w danych.
Dane nieprzetwarzane Ostatnia konstrukcja XML, jaką omówimy, to sekcja CDATA. Stosowana jest ona wtedy, gdy do aplikacji wywołującej trzeba przekazać dużą ilość danych nieprzetworzonych przez parser XML. Z tego sposobu można skorzystać wtedy, gdy dane zawierają wiele nietypowych znaków, których znaczenie należałoby znosić za pomocą encji, albo też gdy zależy nam na utrzymaniu układu tekstu. W dokumencie XML sekcja CDATA wygląda następująco: Zainstaluj Cocoon do "/usr/lib/cocoon" Znajdź odpowiedni plik properties. Pobierz program Ant z adresu "http://jakarta.apache.org" -----> skorzystaj z CVS
W tym przykładzie zawartość sekcji CDATA nie musi być kodowana za pomocą encji lub innych mechanizmów nakazujących parserowi specjalne postępowanie z nietypowymi znakami — przekaże on je w niezmienionej postaci do aplikacji. Poznaliśmy już najważniejsze składniki dokumentów XML. Każdy z nich został omówiony niejako „przy okazji”, ale już teraz Czytelnik powinien potrafić rozpoznać znaczniki XML i ich przeznaczenie. W następnych rozdziałach książki poznamy kolejne właściwości tych konstrukcji, wykonując ćwiczenia praktyczne — ta metoda nauki jest najlepsza.
Co dalej? Po tym wstępie do tworzenia dokumentów XML możemy rozpocząć pisanie pierwszego programu w Javie. W następnym rozdziale przyjrzymy się prostemu interfejsowi Simple API for XML (SAX). Zaczniemy od przetworzenia naszego dokumentu za pomocą prostego programu, a następnie poznamy sposoby obsługi instrukcji przetwarzania, elementów, atrybutów i innych konstrukcji języka XML w procesie przetwarzania dokumentu. Autor zaprezentuje kod napisany w Javie wykonujący poszczególne czynności, zaczynając od prostego programu drukującego dokument XML. W kolejnym rozdziale rozpocznie się intensywny proces nauki manipulowania składnikami dokumentu XML oraz ich stosowania w aplikacjach Javy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 51
52
Rozdział 2. Tworzenie dokumentów XML
C:\WINDOWS\Pulpit\Szymon\Java i XML\02-08.doc — strona 52
3 Przetwarzanie kodu XML Dwa rozdziały solidnego wprowadzenia za nami. Możemy rozpocząć kodowanie! Poznaliśmy już wszystkie akronimy składające się na świat XML-a, zagłębiliśmy się w sam język XML i poznaliśmy jego strukturę. W tym rozdziale rozpoczniemy programowanie w Javie od przetworzenia dokumentu XML i uzyskania dostępu do przetworzonych danych z poziomu kodu w Javie. Najpierw będziemy musieli pobrać i przetworzyć dokument. W czasie przetwarzania dokumentu staje się on dostępny dla aplikacji wykorzystującej parser. To właśnie jest aplikacja „obsługująca XML” (w angielskim używa się określenia XML-aware, czyli „wiedząca o XML-u”). To wszystko wydaje się zbyt proste, żeby było prawdziwe — ale tak właśnie jest. W tym rozdziale poznamy szczegóły dotyczące przetwarzania dokumentu XML. Omówimy sposoby korzystania z parsera wewnątrz aplikacji i przekazywania parserowi danych. Potem omówimy wywołania wsteczne dostępne w czasie przetwarzania. Są to miejsca, w których możliwe jest wstawienie kodu specyficznego dla aplikacji i w których może nastąpić obróbka danych. Oprócz omówienia sposobu pracy parserów, poznamy również interfejs Simple API for XML (SAX). To właśnie SAX udostępnia wywołania wsteczne. Interfejsy dostępne w pakiecie SAX staną się istotnym elementem naszego warsztatu. Klasy SAX są niewielkie i nieliczne, ale w całym naszym omówieniu XML-a będziemy się opierali właśnie na tych klasach. Dobre zrozumienie ich działania jest niezbędne do sprawnego korzystania z XML-a w programach Javy.
Przygotowujemy się Są pewne rzeczy, które należy przygotować jeszcze przed rozpoczęciem programowania. Po pierwsze, trzeba zaopatrzyć się w parser XML. Napisanie parsera XML to ogromne zadanie; istnieje kilka doskonałych projektów zmierzających do zbudowania takiego parsera. Nie będziemy opisywali tutaj, jak pisze się parsery XML, ale w jaki sposób aplikacje korzystają z funkcji udostępnianych przez te parsery. Do manipulacji danymi XML wykorzystamy istniejące narzędzia. Po wybraniu parsera musimy zaopatrzyć się w klasy SAX. Nie jest trudno je znaleźć; klasy te są warunkiem przetwarzania XML-a z poziomu Javy. Oczywiście potrzebny jest również dokument XML do przetworzenia.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 53
54
Rozdział 3. Przetwarzanie kodu XML
Zaopatrujemy się w parser Najpierw należy zaopatrzyć się w odpowiedni parser (rodzaje parserów zostały pokrótce przedstawione w rozdziale 1.). Aby parser działał ze wszystkimi przykładami z książki, należy sprawdzić, czy jest zgodny ze specyfikacją XML. Ponieważ dostępnych jest wiele parserów, a w środowisku programistów XML następują gwałtowne zmiany, opisanie poziomu zgodności różnych parserów ze specyfikacją wykracza poza możliwości tej książki. Powinniśmy zasięgnąć informacji na stronie producenta parsera i odwiedzić wymienione wcześniej witryny. Zgodnie z duchem społeczności wolnego oprogramowania (open source), we wszystkich przykładach tej książki wykorzystany zostanie parser Apache Xerces. Jest on dostępny w postaci źródłowej i binarnej pod adresem http://xml.apache.org. Parser ten został napisany w C i w Javie i jest używany przez liczną grupę programistów. Poza tym, jeśli korzystamy z parsera objętego zasadą wolnego oprogramowania, mamy możliwość wysłania autorom pytań lub powiadomień o błędach — to przyczynia się do podwyższania jakości produktu. Aby zapisać się na ogólną listę adresową dotyczącą parsera Xerces, wystarczy wysłać pusty e-mail pod adres [email protected]. Tam uzyskamy odpowiedzi na pytania dotyczące samego parsera oraz uzyskamy pomoc w przypadku wystąpienia problemów nie ujętych w tej książce. Oczywiście, przykłady z książki będą poprawnie obsłużone przez dowolny parser wykorzystujący opisywaną tutaj implementację SAX. Po dokonaniu wyboru i pobraniu parsera XML należy upewnić się, że nasze środowisko programistyczne — czy to IDE (Integrated Development Environment), czy wiersz poleceń — ma dostęp do parsera poprzez ścieżkę klas. To podstawowy wymóg działania wszystkich dalszych przykładów.
Zaopatrujemy się w klasy i interfejsy SAX Po zdobyciu parsera musimy zaopatrzyć się w klasy SAX. Klasy te są niemal zawsze dołączone do parserów i Xerces nie stanowi tutaj wyjątku. Jeśli tak właśnie jest w naszym przypadku, nie powinniśmy pobierać klas oddzielnie, ponieważ parser na pewno wyposażony jest w najświeższą obsługiwaną wersję klas. W czasie pisania tej książki zkończono prace nad SAX 2.0. Właśnie z klas w tej wersji korzystamy w niniejszej książce i powinna być w nie wyposażona najświeższa wersja Apache Xerces. Jeśli nie jesteśmy pewni, czy posiadamy klasy SAX, wystarczy spojrzeć na plik jar lub strukturę klas wykorzystywaną przez parser. Klasy SAX znajdują się w pakietach struktury org.xml.sax. Najświeższa wersja zawiera w katalogu głównym 17 klas oraz 9 klas org.xml.sax.helpers i 2 klasy org.xml.sax.ext. Jeśli brakuje którejś z tych klas, trzeba skontaktować się z producentem i sprawdzić, czy zostały one zawarte w dystrybucji. Niektóre mogą być pominięte, jeśli nie są w całości obsługiwane1. Tyle samo klas istnieje w interfejsie SAX 2.0; mniej klas pojawi się w tych katalogach, gdy obsługiwany jest tylko SAX 1.0. Warto także pobrać albo zaznaczyć zakładką przeglądarki dokumenty SAX API Javadoc na stronach WWW. Dokumentacja ta bardzo przydaje się podczas korzystania z klas SAX, a struktura Javadoc zapewnia standardowy i prosty sposób znajdowania dodatkowych informacji o klasach.
1
Obsługa klas SAX w całości jest bardzo istotną cechą parsera. Oczywiście, można korzystać z dowolnego parsera, ale jeśli nie obsługuje on w całości SAX 2.0, wiele przykładów z książki nie będzie działało. Ponadto oznacza to, że parser nie uwzględnia nowości w technologii XML. Bez względu na powód, warto zapoznać się z parserem Xerces.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 54
Czytniki SAX
55
Dokumentacja mieści się pod adresem http://www.megginson.com/SAX/SAX2/javadoc/index.html. Dokumentację Javadoc można także wygenerować ze źródeł SAX-a poprzez kod zawarty w parserze lub poprzez pobranie pełnych źródeł z adresu http://www.megginson.com/SAX/SAX2.
Przygotowujemy dokument XML Zawsze należy mieć pod ręką dokument XML do przetworzenia. Dane uzyskiwane w przykładach wynikają z użycia dokumentu opisanego w rozdziale 2. Dokument ten należy zachować jako plik contents.xml na dysku lokalnym. Zalecamy pracę właśnie na tym pliku. Można wpisać go ręcznie albo pobrać ze strony WWW tej książki, http://www.oreilly.com/catalog/javaxml. Warto jednak poświęcić chwilę czasu i wpisać go ręcznie, gdyż jest to praktyczna nauka składni XML-a. Oprócz pobrania lub stworzenia pliku XML, będziemy musieli poczynić w nim kilka niewielkich zmian. Ponieważ nie zostały jeszcze podane informacje o sposobie zawężania i przekształcania dokumentu, w tym rozdziale nasze programy będą wyłącznie przetwarzały („parsowały”) dokument. Aby zapobiec błędom, trzeba usunąć z dokumentu odwołania do zewnętrznej definicji DTD zawężającej XML oraz do arkuszy XSL powodujących jego przekształcenie. Wystarczy opatrzyć komentarzami te dwa wiersze dokumentu XML oraz instrukcję przetwarzania wysyłaną do modułu Cocoon i żądającą przekształcenia:
Po opatrzeniu tych wierszy komentarzami należy zanotować pełną ścieżkę dostępu do dokumentu XML. Trzeba ją będzie przekazać programom w tym i w dalszych rozdziałach. Na koniec należy opatrzyć komentarzami odwołanie do zewnętrznej encji OReillyCopyright, ponieważ powodowałaby ona ładowanie pliku z informacjami o prawach autorskich. Nie posiadając definicji DTD opisującej sposób przetworzenia tej encji, otrzymalibyśmy błędy. W następnym rozdziale zostanie przedstawione zastosowanie tej encji.
Czytniki SAX Po informacjach wstępnych czas przejść do programowania. Nasz pierwszy program pobierze plik jako argument wiersza poleceń i przetworzy go. Zbudujemy wywołania wsteczne do procesu przetwarzania, dzięki czemu zdarzenia zachodzące w czasie tego procesu będą wyświetlane i umożliwią lepszą obserwację całego mechanizmu.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 55
56
Rozdział 3. Przetwarzanie kodu XML
Najpierw należy uzyskać egzemplarz (ang. instance) klasy zgodny z interfejsem SAX org.xml. sax.XMLReader. Interfejs ten definiuje sposób przetwarzania i pozwala na ustawienie funkcji i właściwości, które zostaną omówione w rozdziale 5., Sprawdzanie poprawności składni XML-a. Informacja dla osób znających już SAX 1.0 — interfejs ten zastępuje dotychczasowy org.xml. sax.Parser.
Instalacja czytnika Interfejs udostępniany przez SAX powinien być zaimplementowany we wszystkich parserach XML zgodnych z SAX-em. Dzięki temu SAX wie, jakie metody dostępne są dla wywołań wstecznych i jakie można użyć z poziomu aplikacji. Na przykład główna klasa parsera SAX w Xerces, org.apache.xerces.parsers.SAXParser, implementuje interfejs org.xml.sax.XMLReader. Jeśli mamy dostęp do źródeł parsera, to zobaczymy, że ten sam interfejs zaimplementowano w głównej klasie parsera SAX. Każdy parser musi posiadać jedną (a czasem więcej) klas, które implementują ten interfejs. I właśnie egzemplarz tej klasy musimy stworzyć w celu przetworzenia danych XML: XMLReader parser = new SAXParser(); // Tu robimy coś z parserem parser.parse(uri);
Osoby, które po raz pierwszy stykają się z interfejsem SAX, mogą być zaskoczone brakiem zmiennej egzemplarza o nazwie reader czy XMLReader (reader to po angielsku „czytnik”). Rzeczywiście, wydawałoby się, że tak powinny nazywać się te komponenty, ale w klasach SAX 1.0 zdefiniowano główny interfejs przetwarzający jako Parser i wiele zmiennych wywodzących się z tamtego kodu odziedziczyło właśnie nazwę parser. Tamta postać interfejsu została już teraz zaniechana, ponieważ duża liczba poczynionych zmian wymagała nowych przestrzeni nazw, funkcji i właściwości, ale konwencje nazewnicze wciąż obowiązują, a parser dobrze oddaje przeznaczenie tego egzemplarza. Mając to na uwadze, przyjrzyjmy się niewielkiemu programowi uruchamiającemu i tworzącemu egzemplarz parsera SAX (przykład 3.1). Program ten nie przetwarza faktycznie dokumentu, ale tworzy szkielet, w ramach którego będziemy mogli wykonać pozostałe ćwiczenia rozdziału. Faktyczne przetwarzanie rozpoczniemy w następnym rozdziale.
Przykład 3.1. Przykład użycia parsera SAX import org.xml.sax.XMLReader; // Tutaj importujemy implementację czytnika XML (XML Reader) import org.apache.xerces.parsers.SAXParser; /** * SAXParserDemo pobiera plik XML i przetwarza je * za pomocą SAX, wyświetlając wywołania wsteczne. * * @author * Brett McLaughlin * @version 1.0 */ public class SAXParserDemo { /**
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 56
Czytniki SAX
57
*
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi SAX; * wyświetlamy zdarzenia zachodzące w cyklu przetwarzania. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); } /** *
* Tu obsługujemy wiersz poleceń tego programu demonstracyjnego. *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java SAXParserDemo [XML URI]"); System.exit(0); } String uri = args[0]; SAXParserDemo parserDemo = new SAXParserDemo(); parserDemo.performDemo(uri); } }
Czytelnik powinien umieć załadować i skompilować powyższy program, o ile tylko przygotował się w opisany wcześniej sposób i klasy SAX znajdują się w ścieżce dostępu do klas. Ten prosty program nie potrafi jeszcze zbyt wiele; jeśli uruchomimy go, podając wymyśloną nazwę pliku lub URI jako argument, powinien „pobrzęczeć” dyskiem i wyświetlić komunikat „Przetwarzanie pliku XML”. Wynika to stąd, że jedynie stworzyliśmy instancję parsera, a nie zażądaliśmy jeszcze przetworzenia dokumentu XML. Jeśli wynikły kłopoty z kompilacją powyższego pliku źródłowego, najprawdopodobniej problem tkwi w ścieżce dostępu do klas środowiska programistycznego lub systemu. Najpierw należy upewnić się, że zainstalowany został parser Apache Xerces (lub inny). W przypadku Xerces należy po prostu pobrać odpowiedni plik jar. Po rozpakowaniu archiwum uzyskujemy plik xerces.jar — właśnie on zawiera skompilowane klasy dla tego programu. Po dodaniu tego archiwum do ścieżki dostępu do klas nie powinno być problemów z kompilacją powyższego przykładu.
Przetwarzanie dokumentu Kiedy parser jest już załadowany i gotowy do wykorzystania, można przekazać mu do przetworzenia dokument. Tradycyjnie służy do tego metoda parse(), wchodząca w skład klasy org.xml.sax.XMLReader. Metoda ta przyjmuje albo wejście org.xml.sax.InputSource, albo zwykły identyfikator URI. Na razie nie będziemy zajmowali się wejściem InputSource i skorzystamy z URI. Identyfikator taki może być adresem sieciowym, ale my
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 57
58
Rozdział 3. Przetwarzanie kodu XML
użyjemy pełnej ścieżki dostępu do dokumentu, który przygotowaliśmy wcześniej. Gdybyśmy jednak chcieli skorzystać z dokumentów umieszczonych w sieci, powinniśmy pamiętać o tym, że aplikacja musi umieć znaleźć dany adres URL (tzn. musi być zapewnione połączenie z siecią). Do programu dodajemy więc metodę parse() oraz dwie procedury obsługi błędów. Ponieważ dokument musi zostać załadowany, czy to lokalnie, czy przez sieć, może wystąpić wyjątek java.io.IOException, który musimy przechwycić. Ponadto w czasie przetwarzania może zostać zgłoszony wyjątek org.xmlsax.SAXException (problem przy przetwarzaniu), a więc musimy dodać jeszcze dwie istotne instrukcje, kilka linijek kodu i już mamy gotową do użycia aplikację przetwarzającą XML: import java.io.IOException; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; // Tutaj importujemy implementację czytnika XML (XML Reader) import org.apache.xerces.parsers.SAXParser; /** * SAXParserDemo pobiera plik XML i przetwarza je * za pomocą SAX, wyświetlając wywołania wsteczne. * * @author * Brett McLaughlin * @version 1.0 */ public class SAXParserDemo { /** *
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi SAX; * wyświetlamy zdarzenia zachodzące w cyklu przetwarzania. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); try { // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } } public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java SAXParserDemo [XML URI]");
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 58
Procedury obsługi zawartości
59
System.exit(0); } String uri = args[0]; SAXParserDemo parserDemo = new SAXParserDemo(); parserDemo.performDemo(uri); } }
Tak zmodyfikowany przykład kompilujemy i wykonujemy. Pierwszym argumentem programu powinna być pełna ścieżka dostępu do przetwarzanego pliku: D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: D:\prod\JavaXML\contents\contents.xml
Widząc taki nieciekawy wynik działania programu Czytelnik może zacząć wątpić, czy w ogóle cokolwiek się stało. Jednak dokument XML jest rzeczywiście przetwarzany, a gdybyśmy podali błędny identyfikator URI pliku, parser zgłosi wyjątek i poinformuje, że nie mógł przetworzyć pliku. Nie stworzyliśmy jednak jeszcze żadnych wywołań wstecznych, powodujących że SAX informuje w czasie przetwarzania, co się dzieje w danej chwili. Bez nich dokument jest przetwarzany w sposób niewidoczny dla użytkownika i bez interwencji aplikacji. Oczywiście, my mamy zamiar interweniować w ten proces, a więc musimy stworzyć metody wywołań wstecznych. To właśnie ta „interwencja” jest najważniejszym aspektem korzystania z parsera SAX. Wywołania wsteczne parsera umożliwiają podjęcie działania w czasie trwania programu. Przetwarzanie przestaje być takie nieciekawe jak powyżej — aplikacja zaczyna reagować na dane, elementy, atrybuty i strukturę przetwarzanego dokumentu, a w tym czasie współdziała jeszcze z innymi programami i klientami.
Korzystanie z InputSource Zamiast korzystania z pełnego identyfikatora URI, metodę parse() można także wywołać z argumentem w postaci org.xml.sax.InputSource. Właściwie o tej klasie niewiele można powiedzieć — to raczej klasa osłonowa (ang. wrapper) i pomocnicza. Klasa InputSource po prostu kapsułkuje informacje o pojedynczym obiekcie. W naszym przykładzie nie na wiele się to przyda, ale w sytuacjach, gdzie identyfikator systemowy, identyfikator publiczny lub strumień mogą być dowiązane do jednego identyfikatora URI, kapsułkowanie za pomocą InputSource może się okazać bardzo przydatne. Klasa posiada metody akcesora i mutatora do obsługi identyfikatorów systemowego i publicznego, kodowania znaków, strumienia bajtów (java.io.InputStream) oraz strumienia znaków (java.io.Reader). Jeśli w ten sposób przekażemy argument do parse(), to SAX gwarantuje również, że parser nigdy nie zmodyfikuje InputSource. Dzięki temu wiemy na pewno, że po wykorzystaniu parsera lub aplikacji obsługującej XML dane wejściowe pozostaną niezmienione. Wiele aplikacji opisywanych w dalszych częściach książki będzie wykorzystywało właśnie klasy InputSource, a nie specyficznego URI.
Procedury obsługi zawartości Aby nasza aplikacja mogła zrobić cokolwiek pożytecznego z przetwarzanymi właśnie danymi XML, w parserze SAX musimy zarejestrować procedury obsługi (ang. handlers). Procedura obsługi to po prostu grupa wywołań wstecznych zdefiniowanych w ramach interfejsu SAX i umożliwiających wywoływanie kodu aplikacji w przypadku zajścia konkretnych zdarzeń w czasie przetwarzania dokumentu. Trzeba zdać sobie sprawę z tego, że wywołania te będą następowały w czasie przetwarzania dokumentu, a nie po jego przetworzeniu. To między innymi dlatego SAX jest tak
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 59
60
Rozdział 3. Przetwarzanie kodu XML
potężnym interfejsem — umożliwia obsługę dokumentu sekwencyjnie, bez konieczności wczytywania go całego do pamięci. Ograniczenie takie posiada model Document Object Model (DOM), który zostanie omówiony w dalszej kolejności. W interfejsie SAX 2.0 istnieją cztery podstawowe procedury obsługi: org.xml.sax.ContentHandler, org.xml.sax.ErrorHandler, org.xml.sax.DTDHandler oraz org. xml.sax.EntityResolver. W tym rozdziale omówimy procedurę ContentHandler, umożliwiającą obsługę standardowych zdarzeń związanych z danymi dokumentu XML. Rozpoczęte zostanie również omawianie procedury ErrorHandler, za pośrednictwem której parser zgłasza znalezione w dokumencie błędy. Procedura DTDHandler zostanie omówiona w rozdziale 5. Procedura EntityResolver omawiana jest w różnych miejscach książki; teraz wystarczy zrozumieć, że działa ona dokładnie tak jak pozostałe i służy do tłumaczenia encji zewnętrznych wstawionych do dokumentu XML. Każdy z tych interfejsów może zostać zaimplementowany w klasach aplikacji wykonujących specyficzne zadania. Klasy implementacyjne rejestrowane są w parserze metodami setContentHandler(), setErrorHandler(), setDTDHandler() i setEntityResolver(). Następnie parser wykonuje wywołania wsteczne tych metod w razie uruchomienia konkretnego programu obsługi. W naszym przykładzie zaimplementujemy interfejs ContentHandler. W interfejsie tym zdefiniowano szereg istotnych metod cyklu przetwarzania, na które nasza aplikacja może reagować. Przede wszystkim musimy dodać odpowiednie instrukcje import do pliku źródłowego (w tym klasę i interfejs org.xml.sax.Locator i org.xml.sax.Attributes — oraz nową klasę implementującą te metody wywołań wstecznych. Ta nowa klasa zostanie dodana na końcu pliku źródłowego SAXParserDemo.java: import java.io.IOException; import import import import import
// Tutaj importujemy implementację czytnika XML (XML Reader). import org.apache.xerces.parsers.SAXParser; /** * SAXParserDemo pobiera pliki XML i przetwarza je * za pomocą SAX, wyświetlając wywołania wsteczne. * * @author Brett McLaughlin * @version 1.0 */ public class SAXParserDemo { /** *
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi SAX; * wyświetlamy zdarzenia zachodzące w cyklu przetwarzania. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n");
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 60
* Tu obsługujemy wiersz poleceń tego programu demonstracyjnego. *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java SAXParserDemo [XML URI]"); System.exit(0); } String uri = args[0]; SAXParserDemo parserDemo = new SAXParserDemo(); parserDemo.performDemo(uri); } } /** * MyContentHandler implementuje interfejs * ContentHandler i definiuje sposób zachowania * wywołań wstecznych SAX powiązanych z zawartością * dokumentu XML. */ class MyContentHandler implements ContentHandler {
SAX
/** Zmienna locator będzie zawierała informacje o położeniu */ private Locator locator; /** *
* Odwołania do Locator umożliwią uzyskanie * informacji o miejscu, w którym wystąpiło wywołanie wsteczne. *
* * @param locator Locator -- obiekt dowiązany do * procesu wywołań wstecznych. */
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 61
62
Rozdział 3. Przetwarzanie kodu XML public void setDocumentLocator(Locator locator) { } /** *
* Początek przetwarzanego dokumentu -- to dzieje się przed wszystkimi * odwołaniami wszystkich procedur obsługi SAX oprócz * {@link #setDocumentLocator}. *
* * @throws SAXException jeśli coś pójdzie nie tak */ public void startDocument() throws SAXException { } /** *
* Koniec przetwarzania dokumentu -- to dzieje się po wszystkich * wywołaniach wstecznych wszystkich procedur obsługi. SAX.. *
* * @throws SAXException jeśli coś pójdzie nie tak */ public void endDocument() throws SAXException { } /** *
* To oznaczać będzie, że napotkano instrukcję przetwarzania (nie na deklarację XML). *
* * @param target String obiekt docelowy instrukcji PI * @param data String zawiera wszystkie dane wysłane do PI. * Zazwyczaj ma to postać jednej lub więcej par * atrybut - wartość. * @throws SAXException jeśli coś pójdzie nie tak. */ public void processingInstruction(String target, String data) throws SAXException { } /** *
* To oznacza początek odwzorowywania przedrostka przestrzeni nazw XML. * Zazwyczaj powinno się pojawić wewnątrz elementu głównego dokumentu XML, * ale nie jest to reguła (może pojawić się w dowolnym miejscu). * Odwzorowanie przedrostka danego elementu uruchamia wywołanie wsteczne * przed odwołaniem odnoszącym się do samego elementu * ({@link #startElement}). *
* * @param prefix String przedrostek dla odnalezionej * przestrzeni nazw. * @param uri String URI dla odnalezionej * przestrzeni nazw. * @throws SAXException jeśli coś pójdzie nie tak. */ public void startPrefixMapping(String prefix, String uri) { }
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 62
Procedury obsługi zawartości /** *
* To oznacza koniec odwzorowania przedrostka, kiedy nie jest już dostępna * przestrzeń nazw określona w odwołaniu * {@link #startPrefixMapping}. *
* * @param prefix String znalezionej przestrzeni nazw. * @throws SAXException jeśli coś pójdzie nie tak. */ public void endPrefixMapping(String prefix) { } /** *
* Komunikat o pojawieniu się faktycznego elementu. Podawane są atrybuty * elementu, za wyjątkiem atrybutów specyficznych dla słownika XML, * takich jak: * xmlns:[namespace prefix] i * xsi:schemaLocation. *
* * @param namespaceURI String URI przestrzeni nazw, z którą * skojarzony jest ten element lub pusty * String. * @param localName String nazwa elementu (bez * przedrostka przestrzeni nazw, jeśli taki istnieje) * @param rawName String Wersja XML 1.0 nazwy elementu: * [namespace prefix]:[localName]. * @param atts Attributes -- lista atrybutów tego elementu * @throws SAXException jeśli coś pójdzie nie tak. */ public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) throws SAXException { } /** *
* Oznacza, że osiągnięto koniec elementu. * ([element name]>). Należy zauważyć, że parser * nie rozróżnia pomiędzy elementami pustymi i niepustymi, * a więc to będzie się odbywało identycznie w obu przypadkach. *
* * @param namespaceURI String URI przestrzeni nazw, z jaką skojarzony jest ten element * @param localName String nazwa elementu bez przedrostka * @param rawName String nazwa elementu w postaci XML. 1.0 * @throws SAXException jeśli coś pójdzie nie tak. */ public void endElement(String namespaceURI, String localName, String rawName) throws SAXException { } /** *
* Tutaj wyświetlamy dane tekstowe (zawarte wewnątrz elementu). *
* * @param ch char[] tablica znaków zawartych w elemencie.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 63
63
64
Rozdział 3. Przetwarzanie kodu XML * @param start int indeks w tablicy, w którym zaczynają się dane. * @param end int indeks w tablicy, w którym kończą się dane. * @throws SAXException jeśli coś pójdzie nie tak. */ public void characters(char[] ch, int start, int end) throws SAXException { } /** *
* Tutaj informujemy o znakach białych, ignorowanych w oryginalnym dokumencie. * Zazwyczaj procedura taka uruchamiana jest jedynie wtedy, gdy w procesie * przetwarzania odbywa się też sprawdzanie poprawności. *
* * @param ch char[] tablica znaków zawartych w elemencie. * @param start int indeks w tablicy, w którym zaczynają się dane. * @param end int indeks w tablicy, w którym kończą się dane. * @throws SAXException jeśli coś pójdzie nie tak. */ public void ignorableWhitespace(char[] ch, int start, int end) throws SAXException { } /** *
* Tutaj pokazujemy encję pominiętą przez parser. Zdarzenie powinno * pojawiać się jedynie w parserach nie sprawdzających poprawności; * jego zachowanie zależy od konkretnej implementacji. *
* * @param name String nazwa pomijanej encji. * @throws SAXException gdy coś pójdzie nie tak. */ public void skippedEntity(String name) throws SAXException { } }
Dodaliśmy puste implementacje wszystkich metod zdefiniowanych w ramach interfejsu ContentHandler i nasz plik źródłowy kompiluje się. Oczywiście, takie puste implementacje nie umożliwiają obserwacji całego procesu, więc teraz przyjrzymy się po kolei wszystkim wymaganym metodom.
Lokalizator dokumentu Pierwsza metoda, jaką należy zdefiniować, to ta ustawiająca org.xml.sax.Locator dla dowolnego zdarzenia. W przypadku wystąpienia wywołania wstecznego klasa implementująca procedurę obsługi niejednokrotnie musi uzyskać informację o miejscu w pliku XML przetwarzanym przez parser. Dzięki temu aplikacja będzie mogła podjąć decyzję odnośnie danego zdarzenia i miejsca, w którym ono wystąpiło. Klasa Locator udostępnia szereg przydatnych metod, takich jak getLineNumber() i getColumnNumber(), zwracających bieżące miejsce w dokumencie XML. Ponieważ położenie to odnosi się tylko do bieżącego cyklu przetwarzania, klasy Locator powinno się używać wyłącznie w zakresie implementacji ContentHandler. Ponieważ nam
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 64
Procedury obsługi zawartości
65
może ona przydać się później, zachowujemy udostępniony egzemplarz klasy Locator do zmiennej składowej i drukujemy komunikat, informujący, że nastąpiło wywołanie wsteczne. Dzięki temu poznamy kolejność występowania zdarzeń SAX: /** Zmienna locator będzie zawierała informacje o położeniu */ private Locator locator; /** *
* Odwołania do Locator umożliwią uzyskanie * informacji o miejscu, w którym wystąpiło wywołanie wsteczne. *
* * @param locator Locator -- obiekt dowiązany do * procesu wywołań wstecznych. */ public void setDocumentLocator(Locator locator) { System.out.println(" * setDocumentLocator() została wywołana"); // Zachowujemy do ewentualnego wykorzystania w przyszłości. this.locator = locator; }
Później, jeśli konieczne będzie uzyskanie informacji o miejscu zdarzenia, do metody tej można dodać bardziej szczegółowe instrukcje. Jeśli jednak użytkownik chce pokazać, gdzie w dokumencie pojawiają się dane zdarzenia (np. numer wiersza, w którym wystąpił element), musi przypisać ten Locator zmiennej składowej do późniejszego wykorzystania w klasie.
Początek i koniec dokumentu Każdy proces ma początek i koniec. Oba te zdarzenia powinny wystąpić raz — pierwsze przed wszystkimi innymi zdarzeniami, a drugie po. Ten oczywisty fakt ma w aplikacjach krytyczne znaczenie — dokładnie informuje, kiedy przetwarzanie rozpoczyna się i kiedy kończy. SAX udostępnia metody wywołań wstecznych dla obu tych zdarzeń: startDocument() i endDocument(). Pierwsza metoda, startDocument(), wywoływana jest przed wszelkimi innymi wywołaniami wstecznymi, również przed tymi znajdującymi się w innych procedurach obsługi SAX, np. DTDHandler. Innymi słowy, startDocument() to nie tylko pierwsza metoda wywoływana wewnątrz ContentHandler, ale także pierwsza metoda w całym procesie przetwarzania (nie licząc metody setDocumentLocator(),wspomnianej przed chwilą). Dzięki temu wiemy, kiedy przetwarzanie rozpoczyna się, a nasza aplikacja może wykonać procedury, które muszą być uruchomione jeszcze przed tym przetwarzaniem. Druga metoda, endDocument(), jest zawsze wywoływana jako ostatnia, również bez względu na procedurę obsługi. Obejmuje to także sytuacje, w których proces przetwarzania zostaje zatrzymany w wyniku napotkania błędów. Błędy zostaną omówione w dalszej części książki; teraz Czytelnik powinien jednak wiedzieć, że są dwa ich rodzaje: błędy naprawialne i nienaprawialne. W przypadku wystąpienia błędu nienaprawialnego wywoływana jest metoda procedury ErrorHandler, po czym przetwarzanie kończy się wywołaniem metody endDocument(). W naszym przykładzie, kiedy obie te metody zostaną wywołane, wyświetlimy odpowiednie komunikaty na konsoli. /** *
* Początek przetwarzanego dokumentu -- to dzieje się przed wszystkimi * odwołaniami wszystkich procedur obsługi SAX oprócz * {@link #setDocumentLocator}.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 65
66
Rozdział 3. Przetwarzanie kodu XML *
* * @throws SAXException jeśli coś pójdzie nie tak. */ public void startDocument() throws SAXException { System.out.println("Rozpoczyna się przetwarzanie..."); } /** *
* Koniec przetwarzania dokumentu -- to dzieje się po wszystkich * wywołaniach wstecznych wszystkich procedur obsługi SAX. *
* * @throws SAXException jeśli coś pójdzie nie tak. */ public void endDocument() throws SAXException { System.out.println("...Przetwarzanie skończone."); }
Oba te wywołania wsteczne mogą zgłosić wyjątek SAXException. Jest to jedyny typ wyjątków zgłaszany przez SAX. Za pośrednictwem wyjątków udostępniany jest kolejny standardowy interfejs do procesu przetwarzania. Jednakże wyjątki te często pośredniczą w obsłudze innych wyjątków, wyraźnie wskazujących na to, z jakim problemem mamy do czynienia. Na przykład, jeśli plik XML jest przetwarzany za pośrednictwem sieci (poprzez adres URL), a połączenie nagle zostało przerwane, to zgłoszony zostaje IOException. Ale aplikacja korzystająca z klas SAX nie musi umieć przechwytywać tego wyjątku, ponieważ w ogóle nie musi wiedzieć, gdzie zlokalizowano zasób XML. Aplikacja może obsługiwać tylko jeden wyjątek — SAXException. Ten pierwszy wyjątek jest przechwytywany przez parser SAX i ponownie zgłaszany, tym razem jako SAXException; pierwotny wyjątek jest „kapsułkowany” w nowym. Dzięki temu wystarczy, żeby aplikacja przechwytywała jeden wyjątek, poprzez który dostarczane są także szczegółowe informacje o naturze problemu, jaki wystąpił w procesie przetwarzania. Klasa SAXException udostępnia metodę getException(), zwracającą właściwy wyjątek.
Instrukcje przetwarzania Instrukcje przetwarzania (PI) w dokumencie XML są elementami dość wyjątkowymi. Nie są postrzegane jako elementy samego XML-a i zamiast natychmiastowej obsługi, przekazywane są aplikacji wywołującej. Z tego względu do ich obsługi SAX udostępnia specjalne wywołanie wsteczne. Metoda ta otrzymuje obiekt docelowy (ang. target) instrukcji oraz dane przekazane do PI. Załóżmy, że życzymy sobie wyświetlenia na ekranie informacji w momencie pojawienia się wywołania: /** *
* To oznaczać będzie, że napotkano instrukcję przetwarzania (nie * deklarację XML). *
* * @param target String obiekt docelowy instrukcji PI * @param data String zawiera wszystkie dane wysłane do PI. * Zazwyczaj ma to postać jednej lub więcej par * atrybut - wartość. * @throws SAXException jeśli coś pójdzie nie tak. */ public void processingInstruction(String target, String data) throws SAXException {
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 66
Procedury obsługi zawartości
67
System.out.println("PI: Obiekt docelowy:" + target + " i dane:" + data); }
W rzeczywistej aplikacji, korzystającej z danych XML, w tym miejscu program mógłby otrzymać instrukcje do ustawienia wartości zmiennych lub wykonania metod związanych z tą konkretną aplikacją. Na przykład struktura publikacji Apache Cocoon mogłaby ustawić znaczniki związane z przekształcaniem danych po ich przetworzeniu lub wyświetlić skrót XML jako określony typ zawartości. Metoda ta, podobnie jak inne wywołania wsteczne SAX, zgłasza SAXException w razie wystąpienia błędu. Podczas omawiania instrukcji przetwarzania wspomnieliśmy także o deklaracji XML. Ta specjalna instrukcja przetwarzania udostępnia wersję, opcjonalne informacje o kodowaniu oraz o tym, czy dany dokument jest dokumentem samodzielnym:
Instrukcja ta przeznaczona jest specjalnie dla parsera XML. Umożliwia zgłoszenie błędu (np. o nieobsługiwanej wersji) na samym początku przetwarzania. Ponieważ instrukcja ta obsługiwana jest tylko przez parser, nie powoduje wywołania processingInstruction(). Należy uważać, aby nie stworzyć kodu oczekującego tej instrukcji lub informacji o wersji, ponieważ aplikacja nigdy nie otrzyma wywołania wstecznego do tej instrukcji przetwarzania. Właściwie to tylko parser powinien „interesować się” kodowaniem i wersją dokumentu XML, ponieważ elementy związane są z samym procesem przetwarzania. Kiedy już dane dokumentu zostaną udostępnione poprzez interfejs API Javy, szczegóły te stają się dla aplikacji nieistotne.
Wywołania związane z przestrzenią nazw Sądząc po tym, ile czasu poświęciliśmy przestrzeniom nazw w XML-u (oraz po stopniu skomplikowania tej problematyki), można przypuszczać, że jest to pojęcie dość istotne i że ma duży wpływ na przetwarzanie i obsługę danych XML. Oprócz schematu XML, przestrzenie nazw XML to z pewnością najważniejsza cecha dodana do języka XML od czasu opublikowania oryginalnego zalecenia XML 1.0. W interfejsie SAX 2.0 obsługę przestrzeni nazw wprowadzono na poziomie elementów. Dzięki temu możliwe jest rozróżnienie pomiędzy przestrzenią nazwy elementu (opisaną przedrostkiem i odpowiednim identyfikatorem URI) a lokalną nazwą elementu. W tym przypadku, pojęciem lokalna nazwa określamy nazwę elementu bez przedrostka. Na przykład lokalną nazwą dla JavaXML:Ksiazka jest po prostu Ksiazka. Przedrostek przestrzeni nazw to JavaXML, a identyfikator URI przestrzeni nazw (w naszym przykładzie) to http://www.oreilly.com/ catalog/javaxml. Istnieją dwa wywołania wsteczne do obsługi przestrzeni nazw (choć wywołania związane z elementami mogą także z nich korzystać). Wywołania te następują wtedy, gdy parser natrafi na początek i koniec odwzorowania przedrostków (ang. prefix mapping). Sam termin nie jest jeszcze znany, ale to pojęcie nie wprowadza nic nowego. Odwzorowanie przedrostków to po prostu element, w którym za pomocą atrybutu xmlns deklarowana jest przestrzeń nazw. Jest to często element główny (w którym może występować wiele różnych odwzorowań), ale nic nie stoi na przeszkodzie, aby tę rolę spełniał dowolny inny element dokumentu XML, jawnie deklarujący przestrzeń nazw. Na przykład: Tu jakieś dane
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 67
68
Rozdział 3. Przetwarzanie kodu XML
W tym przypadku przestrzeń nazw deklarowana jest jawnie, na głębokości kilku poziomów zagnieżdżenia elementów. Wywołanie startPrefixMapping() otrzymuje przedrostek przestrzeni nazw oraz identyfikator URI skojarzony z tym przedrostkiem. Odwzorowanie uznawane jest za „zamknięte” lub „zakończone”, gdy zamknięty zostaje element deklarujący to odwzorowanie. Jedyny „haczyk” tego wywołania polega na tym, że zachowuje się ono niezupełnie sekwencyjnie — tak jak to ma zazwyczaj miejsce w interfejsie SAX; odwzorowanie przedrostków odbywa się bezpośrednio przed wywołaniem związanym z elementem deklarującym przestrzeń nazw. Oto przykład wywołania: /** *
* To oznacza początek odwzorowania przedrostka przestrzeni nazw XML. * Zazwyczaj powinno się pojawić wewnątrz elementu głównego dokumentu XML, * ale nie jest to reguła (może pojawić się w dowolnym miejscu). * Odwzorowanie przedrostka danego elementu uruchamia wywołanie wsteczne * przed odwołaniem odnoszącym się do samego elementu * ({@link #startElement}). *
* * @param prefix String przedrostek dla odnalezionej * przestrzeni nazw * @param uri String URI dla odnalezionej * przestrzeni nazw * @throws SAXException jeśli coś pójdzie nie tak */ public void startPrefixMapping(String prefix, String uri) { System.out.println("Początek odwzorowania dla przedrostka " + prefix + " odwzorowanego dla URI " + uri); }
W naszym dokumencie jedynym zadeklarowanym odwzorowaniem jest atrybut elementu głównego. Oznacza to, że powinniśmy oczekiwać tego wywołania przed wywołaniem związanym z pierwszym elementem (o którym za chwilę), ale już po wywołaniu startDocument() oraz po wszelkich instrukcjach PI, jakie znajdują się na początku dokumentu. Drugie z tej pary wywołań związanych z przestrzeniami nazw oznacza napotkanie końca odwzorowania i pojawia się bezpośrednio po znaczniku zamykającym elementu, w którym zadeklarowano odwzorowanie: /** *
* To oznacza koniec odwzorowania przedrostka, kiedy nie jest już dostępna * przestrzeń nazw określona w odwołaniu * {@link #startPrefixMapping}. *
* * @param prefix String znalezionej przestrzeni nazw * @throws SAXException jeśli coś pójdzie nie tak */ public void endPrefixMapping(String prefix) { System.out.println("Koniec odwzorowania dla przedrostka " + prefix); }
W przypadku pokazanego powyżej fragmentu dokumentu XML można oczekiwać następującego komunikatu po napotkaniu znacznika element2:
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 68
Procedury obsługi zawartości
69
Początek odwzorowania dla przedrostka mojaPrzestrzenNazw odwzorowanego dla URI http://mojUrl.pl
W ten sposób można poznać odwzorowywany przedrostek oraz skojarzony z nim identyfikator URI.
Wywołania związane z elementami Teraz najprawdopodobniej przygotowani już jesteśmy na faktyczne pobranie danych z dokumentu XML. To prawda, że ponad połowa wywołań wstecznych SAX nie ma nic wspólnego z elementami, atrybutami i danymi XML. Wynika to stąd, że proces przetwarzania nie polega po prostu na przekazaniu aplikacji danych XML; aplikacja otrzymuje także instrukcje PI, dzięki którym wie, jakie działanie ma zostać podjęte; aplikacja „dowiaduje się” także, kiedy przetwarzanie rozpoczyna się i kończy, a nawet gdzie znajdują się białe znaki, które można zignorować. Jeśli niektóre z tych wywołań wydają się Czytelnikowi pozbawione sensu, to należy uzbroić się w cierpliwość — jeszcze w tym rozdziale niektóre zagadnienia zostaną wyjaśnione; więcej wiadomości na ten temat znaleźć można w rozdziale 5. Oczywiście, istnieją wywołania SAX służące do uzyskiwania dostępu do danych XML zawartych w dokumentach. Pierwsze trzy zdarzenia, jakie zostaną omówione, to początek i koniec elementu oraz wywołanie characters(). Informują one, kiedy przetwarzany jest element, jakie dane zawiera oraz kiedy parser napotkał znacznik zamykający elementu. Pierwsze z wywołań, startElement(), informuje aplikację o elemencie XML i ewentualnych jego atrybutach. Parametry wywołania to nazwa elementu (w różnych postaciach) oraz egzemplarz klasy org.xml.sax.Attributes (Czytelnik powinien przypomnieć sobie opisywaną wcześniej instrukcję importującą). Ta klasa pomocnicza zawiera referencje do wszystkich atrybutów elementu. Umożliwia proste przetwarzanie kolejnych atrybutów elementu w postaci podobnej do Vector. Oprócz możliwości odwołania się do atrybutu za pomocą indeksu (kiedy przetwarzamy wszystkie atrybuty po kolei), możliwe jest również odwołanie się poprzez nazwę. Oczywiście, w tej chwili Czytelnik powinien już być ostrożniejszy, słysząc słowo „nazwa” związane z elementem lub atrybutem XML — może ono znaczyć różne rzeczy. W tym przypadku można użyć albo pełnej nazwy atrybutu (z przedrostkiem przestrzeni nazw, o ile taki tam jest) — to nazwiemy „surową” nazwą; albo połączenia nazwy lokalnej i identyfikatora URI, jeśli wykorzystano przestrzeń nazw. Istnieją także metody pomocnicze, takie jak getURI(int index) i getLocalName(int index), dzięki którym można uzyskać dodatkowe informacje o przestrzeni nazw związanej z danym atrybutem. Cały interfejs Attributes stanowi więc wszechstronne źródło danych o atrybutach elementu. Jak wspomnieliśmy, nie tylko atrybuty, ale również nazwa samego elementu może przyjmować różne formy. I znów chodzi tu o przestrzeń nazw w XML-u. Najpierw dostarczany jest identyfikator URI przestrzeni nazw danego elementu. Dzięki temu element umieszczany jest w poprawnym kontekście i odpowiedniej relacji do pozostałych przestrzeni nazw w dokumencie. Następnie podawana jest lokalna nazwa elementu, czyli fragment bez przedrostka. Oprócz tego (na potrzeby zgodności wstecz), podawana jest „surowa” nazwa elementu. Chodzi tu o niezmodyfikowaną nazwę elementu, zawierającą ewentualny przedrostek przestrzeni nazw — czyli to, co umieszczone było w dokumencie XML; dla naszego elementu Ksiazka byłaby to nazwa JavaXML: Ksiazka. Obecność tych trzech typów nazw umożliwia opisanie dowolnego elementu, z przestrzenią nazw, czy też bez niej. Skoro wiemy już, jak udostępnia się element i jego atrybuty, spójrzmy na implementację wywołania wstecznego SAX, wyświetlającą informację na ekranie. W tym przykładzie sprawdzamy, czy nazwa elementu posiada skojarzony z nią identyfikator URI przestrzeni nazw; jeśli tak — drukujemy przestrzeń nazw; jeśli nie — drukujemy komunikat, informujący, że z elementem nie skojarzono żadnej przestrzeni nazw: /**
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 69
70
Rozdział 3. Przetwarzanie kodu XML *
* Komunikat o pojawieniu się faktycznego elementu. Podawane są atrybuty * elementu, za wyjątkiem atrybutów specyficznych dla słownika XML, * takich jak: * xmlns:[namespace prefix] i * xsi:schemaLocation. *
* * @param namespaceURI String URI przestrzeni nazw, z którą * skojarzony jest ten element lub pusty. * String * @param localName String nazwa elementu (bez * przedrostka przestrzeni nazw, jeśli taki istnieje) * @param rawName String Wersja XML 1.0 nazwy elementu: * [namespace prefix]:[localName] * @param atts Attributes -- lista atrybutów tego elementu. * @throws SAXException jeśli coś pójdzie nie tak. */ public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) throws SAXException { System.out.print("startElement: " + localName); if (!namespaceURI.equals("")) { System.out.println(" w przestrzeni nazw " + namespaceURI + " (" + rawName + ")"); } else { System.out.println(" nie posiada skojarzonej przestrzeni nazw"); } for (int i=0; i java SAXParserDemo D:\prod\JavaXML\contents\contents.xml
Polecenie to powinno zwrócić dużo danych wyjściowych. Jeśli Czytelnik korzysta z systemu Windows, może się okazać konieczne zwiększenie bufora dla okna DOS-owego w takim stopniu, aby możliwe było przewinięcie ekranu i obejrzenie wszystkich danych wyjściowych. Dane te powinny mieć postać podobną do tych przedstawionych w przykładzie 3.22.
Przykład 3.2. Dane wyjściowe zwracane przez program SAXParserDemo Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana. Rozpoczyna się przetwarzanie... Początek odwzorowania dla przedrostka JavaXML odwzorowanego dla URI. http://www.oreilly.com/catalog/javaxml/ startElement: Ksiazka w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Ksiazka) znaki: startElement: Tytul w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Tytul) znaki: Java i XML endElement: Tytul znaki: startElement: Spis w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Spis) znaki: startElement: Rozdzial w przestrzeni nazw. http://www.oreilly.com/catalog/javaxml/ (JavaXML:Rozdzial) Atrybut: tematyka=XML znaki: startElement: Naglowek w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Naglowek) znaki: Wprowadzenie endElement: Naglowek
2
W tym oraz innych przykładach danych zwracanych przez programy możliwe jest, że zostały dodatkowo przełamane wiersze w celu poprawnego umieszczenia wydruku w książce. O ile jednak same dane tekstowe się zgadzają, to na pewno wszystko jest w porządku!
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 75
76
Rozdział 3. Przetwarzanie kodu XML znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podRozdzialy=7 znaki: Co to jest? endElement: Temat znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podRozdzialy=3 znaki: Jak z tego korzystać? endElement: Temat znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podRozdzialy=4 znaki: Dlaczego z tego korzysta? endElement: Temat znaki: startElement: Temat w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat) Atrybut: podrozdzialy=0 znaki: Co dalej? endElement: Temat ...
Takich danych pojawi się na wyjściu sporo, ponieważ przetwarzany dokument XML ma wiele elementów. Wyraźnie widać, że parser sekwencyjnie przetwarza każdy element, atrybuty elementu, dane w nim zawarte, elementy zagnieżdżone oraz znacznik końcowy elementu. Proces powtarza się dla każdego elementu. W naszym przykładzie został zastosowany parser Apache Xerces w wersji nie sprawdzającej poprawności dokumentu, a więc białe znaki są zgłaszane za pomocą wywołań characters(). W następnych dwóch rozdziałach zostaną omówione zagadnienia związane ze sprawdzaniem poprawności i wówczas będzie można porównać wynik działania. Teraz wiemy już, w jaki sposób parser zgodny z interfejsem SAX przetwarza dokument XML. Powinniśmy również rozumieć wywołania pojawiające się w czasie procesu przetwarzania oraz to, w jaki sposób można z nich uzyskać informacje potrzebne aplikacji. W następnych dwóch rozdziałach zostaną omówione zagadnienia związane ze sprawdzaniem poprawności dokumentu XML za pomocą dodatkowych klas SAX służących do obsługi definicji DTD. Teraz sprawdzimy, jakie błędy mogą się pojawić, gdy dokument nie będzie poprawny, co dzieje się, gdy dokument XML nie jest poprawny i jakie błędy mogą z tego wynikać.
Procedury obsługi błędów Oprócz interfejsu ContentHandler, SAX udostępnia również interfejs ErrorHandler, służący do obsługi sytuacji awaryjnych zaistniałych w czasie przetwarzania. Klasa ta działa podobnie jak omówiona wcześniej procedura obsługi dokumentu, ale zdefiniowano w niej jedynie trzy
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 76
Procedury obsługi błędów
77
wywołania wsteczne. Przy pomocy tych trzech metod SAX obsługuje i powiadamia o wszystkich możliwych błędach. Każda metoda otrzymuje informacje o błędzie lub ostrzeżeniu poprzez klasę SAXParseException. Obiekt ten zawiera numer wiersza, w którym wystąpił błąd, identyfikator URI przetwarzanego dokumentu (dokument przetwarzany lub zewnętrzne odwołanie wewnątrz tego dokumentu) oraz zwykłe informacje o błędzie, takie jak komunikat i dane ze śledzenia stosu. Ponadto każda metoda może zgłosić SAXException. Na początku może się to wydawać nieco dziwne — program obsługi wyjątków zgłaszający wyjątek? Należy jednak pamiętać, że każda z procedur obsługi błędów otrzymuje wyjątki związane z przetwarzaniem. Może to być ostrzeżenie, które nie powinno przerwać procesu przetwarzania, lub błąd, który należy rozwiązać, aby to przetwarzanie mogło być kontynuowane. Jednak wywołanie takie może także wykonywać operacje wejścia-wyjścia lub inne, które mogą spowodować zgłoszenie wyjątku — i wyjątek ten należy przekazać aż „na samą górę”, do aplikacji. Służy do tego właśnie wyjątek SAXException. Załóżmy, że procedura obsługi błędów otrzymuje powiadomienia o błędzie i zapisuje je do pliku dziennika błędów. Metoda taka musi być w stanie albo dopisać informacje do pliku istniejącego, albo stworzyć nowy plik. Gdyby w czasie przetwarzania dokumentu XML wystąpiło ostrzeżenie, także „dowiadywałaby” się o nim ta metoda. Ostrzeżenie miałoby na celu wywołanie odpowiedniej procedury i nie przerywałoby przetwarzania. Jednakże, jeśli procedura obsługi błędów nie byłaby w stanie zapisywać do pliku dziennika, musiałaby zakomunikować parserowi i aplikacji, że przetwarzanie ma zostać przerwane. Efekt taki można uzyskać poprzez przechwycenie wszystkich wyjątków wejścia-wyjścia i ponowne zgłoszenie ich aplikacji wywołującej, powodując przerwanie przetwarzania. Powyższy przykład wyjaśnia, dlaczego procedury obsługi błędów muszą mieć możliwość zgłaszania wyjątków (patrz przykład 3.3).
Przykład 3.3. Procedura obsługi, która może zgłosić SAXException public void warning(SAXParseException exception) throws SAXException {
}
try { FileWriter fw = new FileWriter("error.log"); BufferedWriter bw = new BufferedWriter(fw); bw.write("Ostrzeżenie: " + exception.getMessage() + "\n"); bw.flush(); bw.close(); fw.close(); } catch (Exception e) { throws new SAXException("Brak możliwości zapisu do pliku dziennika", e); }
Teraz można już zdefiniować szkielet procedury obsługi błędów i zarejestrować ją w parserze w taki sam sposób, jak w przypadku procedury obsługi zawartości. Najpierw trzeba zaimportować klasę SAXParseException i ErrorHandler: import import import import import import import import
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 77
78
Rozdział 3. Przetwarzanie kodu XML
Teraz w tym samym pliku z programem w Javie (znów na dole, po klasie MyContentHandler) należy zaimplementować interfejs ErrorHandler, zdefiniowany w ramach SAX-a. Podobnie jak przy omawianiu klasy ContentHandler, poniżej są przedstawione puste implementacje, które wkrótce zostaną wypełnione treścią: /** * MyErrorHandler implementuje interfejs SAX * ErrorHandler i definiuje zachowanie wywołań * wstecznych powiązanych z błędami w XML-u. */ class MyErrorHandler implements ErrorHandler { /** *
* Powiadomienie o ostrzeżeniu; żadne zasady XML nie zostały "złamane", * ale wydaje się, że czegoś brakuje lub coś jest wpisane niepoprawnie. *
* * @param exception SAXParseException -- wyjątek, jaki nastąpił. * @throws SAXException gdy coś pójdzie nie tak. */ public void warning(SAXParseException exception) throws SAXException { } /** *
* Tutaj komunikujemy o błędzie, jeśli taki się pojawi; błąd oznacza, że * złamano regułę i zazwyczaj pojawia się w czasie sprawdzania składni; * przetwarzanie może jeszcze być kontynuowane. *
* * @param exception SAXParseException -- wyjątek, który nastąpił. * @throws SAXException jeśli coś pójdzie nie tak. */ public void error(SAXParseException exception) throws SAXException { } /** *
* Tutaj komunikujemy, że nastąpił błąd krytyczny; oznacza to, że * złamano regułę w ten sposób, że dalsze przetwarzanie * nie jest możliwe albo jest bezcelowe. *
* * @param exception SAXParseException -- wyjątek, który nastąpił. * @throws SAXException jeśli coś pójdzie nie tak. */ public void fatalError(SAXParseException exception) throws SAXException { } }
Aby procedura obsługi błędów mogła zostać użyta, należy ją zarejestrować w parserze SAX. Służy do tego metoda setErrorHandler() interfejsu XMLReader w naszej przykładowej metodzie
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 78
Procedury obsługi błędów
79
demo(). Metoda setErrorHandler() pobiera jako jedyny parametr interfejs ErrorHandler lub jego implementację: // Stwórz egzemplarze procedur obsługi ContentHandler contentHandler = new MyContentHandler(); ErrorHandler errorHandler = new MyErrorHandler(); try { // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } ...
A teraz spróbujmy sprawić, żeby na ekranie pojawiały się komunikaty w przypadku wywołania tych metod.
Ostrzeżenia Za każdym razem, gdy pojawi się ostrzeżenie (wynikające ze specyfikacji XML 1.0), metoda ta wywoływana jest w zarejestrowanej procedurze obsługi błędów. Są różne sytuacje powodujące wygenerowanie ostrzeżenia; wszystkie związane są z definicjami DTD i poprawnością składniową dokumentu. Zostaną one omówione w następnych dwóch rozdziałach. Tymczasem zdefiniujemy prostą metodę wyświetlającą numer wiersza, identyfikator URI i komunikat w przypadku wystąpienia ostrzeżenia. Ponieważ chcemy, aby w razie ostrzeżenia przetwarzanie zostało przerwane, zgłaszamy wyjątek SAXException i pozwalamy aplikacji nadrzędnej elegancko zakończyć działanie i zwolnić zasoby: /** *
* Powiadomienie o ostrzeżeniu; żadne zasady języka XML nie zostały złamane, * ale wydaje się, że czegoś brakuje lub coś jest wpisane niepoprawnie. *
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 79
80
Rozdział 3. Przetwarzanie kodu XML "
Komunikat: " + exception.getMessage()); throw new SAXException("Napotkano ostrzeżenie"); }
Błędy niekrytyczne Błędy występujące w czasie przetwarzania, które można naprawić, ale które stanowią pogwałcenie fragmentu specyfikacji XML, postrzegane są jako błędy niekrytyczne. Procedura obsługi błędów powinna je przynajmniej odnotować w pliku dziennika, bo zazwyczaj są na tyle poważne, że zasługują na zwrócenie uwagi użytkownika lub administratora aplikacji; jednak nie są aż tak istotne, by przerwać jej działanie. Tak jak w przypadku ostrzeżeń, większość błędów niekrytycznych związanych jest z poprawnością dokumentu, która zostanie omówiona w odpowiednich rozdziałach. Podobnie jak w przypadku ostrzeżeń, nasza prosta procedura obsługi błędów wyświetli informacje przesłane do wywołania wstecznego i zakończy proces przetwarzania: /** *
* Tutaj komunikujemy o błędzie, jeśli taki się pojawi; błąd oznacza, że * złamano regułę i zazwyczaj pojawia się w czasie sprawdzania składni; * przetwarzanie może jeszcze być kontynuowane. *
Błędy krytyczne Błędy krytyczne to te, które wymuszają zatrzymanie działania parsera. Zazwyczaj wynikają z niepoprawnego sformatowania dokumentu i ich pojawienie się oznacza, że albo dalsze przetwarzanie nie ma sensu, albo jest technicznie niemożliwe. Procedura obsługi błędu powinna niemal zawsze poinformować użytkownika lub administratora o wystąpieniu takiego błędu; pozostawienie aplikacji samej sobie może grozić całkowitym jej zawieszeniem. W naszym przykładzie będziemy emulowali zachowanie dwóch pozostałych metod i zatrzymamy przetwarzanie, wyświetlając komunikat o błędzie na ekranie: /** *
* Tutaj komunikujemy, że nastąpił błąd krytyczny; oznacza to, że * złamano regułę w ten sposób, że dalsze przetwarzanie * nie jest możliwe albo jest bezcelowe. *
*
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 80
Po zakodowaniu procedury obsługi tego trzeciego typu błędu powinno być możliwe przekompilowanie pliku źródłowego i powtórne uruchomienie go na naszym dokumencie XML. Uzyskany wynik nie powinien różnić się od poprzedniego, bo dokument XML nie zawiera błędów, o których aplikacja mogłaby poinformować. W kolejnych podrozdziałach zostaną omówione błędy w dokumentach XML, w których nie jest wykonywane sprawdzanie poprawności.
Rozbijanie danych Skoro posiadamy już procedury obsługi błędów, to warto zobaczyć, jak one działają. Jak już to było wspomniane, większość ostrzeżeń i błędów niekrytycznych związanych jest z poprawnością składni dokumentu — zagadnienia te zostaną omówione w kilku następnych rozdziałach. Jest jednak jeden błąd niekrytyczny, który pojawia się w dokumentach XML nie sprawdzonych pod względem poprawności. Chodzi tutaj o wersję języka XML zgłaszaną przez dokument. Aby zobaczyć, jak ten błąd jest zgłaszany, zmieńmy nasz przykładowy dokument w następujący sposób:
Teraz należy uruchomić przykładowy program na takim zmienionym pliku XML. Wynik powinien być zbliżony do przedstawionego w przykładzie 3.4.
Przykład 3.4. Program SAXParserDemo wyświetlający błąd D:\prod\JavaXML>java SAXParserDemo D:\prod\JavaXML\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... **Przetwarzanie błędu** Wiersz: 1 URI: file:/D:/prod/JavaXML/contents/contents.xml Komunikat: XML version "1.2" is not supported. Błąd w przetwarzaniu: Napotkano błąd
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 81
82
Rozdział 3. Przetwarzanie kodu XML
Kiedy parser XML operuje na dokumencie, którego wersja języka XML została określona jako nowsza niż te obsługiwane przez parser, zgłaszany jest błąd niekrytyczny, zgodnie ze specyfikacją XML 1.0. W ten sposób aplikacja zostaje poinformowana o tym, że nowe funkcje, które mogły zostać użyte w dokumencie, nie muszą być rozpoznawane przez parser w tej wersji. Przetwarzanie może być kontynuowane, a więc taki błąd nie jest postrzegany jako krytyczny. Jednakże może on mieć istotny wpływ na dokument (np. nowa składnia może powodować dalsze błędy), a więc ma wyższą rangę niż ostrzeżenie. To dlatego wywoływana jest metoda error(), powodująca wysłanie komunikatu informującego o błędzie i zatrzymanie przetwarzania w naszym przykładowym programie. Wszystkie inne istotne ostrzeżenia będą omawiane w następnych dwóch rozdziałach; wciąż istnieją jednak błędy krytyczne, które mogą się pojawiać w dokumencie nie sprawdzonym pod kątem poprawności. Są one związane z niepoprawnym formatowaniem dokumentu. Parsery XML nie potrafią naprawiać takich dokumentów, a więc błąd w składni powoduje zatrzymanie procesu przetwarzania. Najprostszym sposobem demonstracji tego zagadnienia jest wprowadzenie błędów do dokumentu XML. Ponownie zmieńmy deklarację wersji na 1.0 i wprowadźmy następujące zmiany w naszym dokumencie: Java i XML
Taki dokument nie jest poprawnie sformatowany. Uruchamiamy program SAXParserDemo na tak zmodyfikowanym pliku. Wynik przestawiony jest w przykładzie 3.5.
Przykład 3.5. Program SAXParserDemo wyświetlający błąd krytyczny D:\prod\JavaXML>java SAXParserDemo D:\prod\JavaXML\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... startElement: Ksiazka w przestrzeni nazw http://www.oreilly.com/catalog/javaxml/ (JavaXML:Ksiazka) znaki: **Przetwarzanie błędu krytycznego** Wiersz: 13 URI: file:/D:/prod/JavaXML/contents/contents.xml Komunikat: The element type "JavaXML:Ksiazka" must be terminated by the matching end-tag "".
Parser zgłasza niepoprawne zakończenie elementu JavaXML:Ksiazka. Aby zrozumieć komunikat o błędzie, należy zdać sobie sprawę z faktu, że parser „widzi” znak ukośnika przed elementem JavaXML:Tytul i zakłada, że element, który musi zostać zamknięty, to JavaXML:
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 82
Lepszy sposób ładowania parsera
83
Ksiazka — czyli ten, który jest obecnie „otwarty”. Kiedy znajduje znacznik zamykający elementu JavaXML:Tytul, zgłasza niepoprawność tego znacznika jako zamknięcia otwartego elementu JavaXML:Ksiazka. Uważna analiza omawianej procedury obsługi błędów pozwala rozumieć, na jakie problemy można natknąć się w czasie przetwarzania i jak można ich unikać. W rozdziale 5. ponownie zajmiemy się procedurami obsługi błędów i przyjrzymy się problemom zgłaszanym przez parser wykonujący sprawdzanie poprawności składni.
Lepszy sposób ładowania parsera Choć potrafimy już przetwarzać dokumenty XML z wykorzystaniem interfejsu SAX, w naszym kodzie wciąż znajduje się pewien ewidentny błąd. Przeanalizujmy powtórnie proces tworzenia egzemplarza klasy XMLReader: try { // Stwórz egzemplarz parsera XMLReader parser = new SAXParser(); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Czy powyższy fragment nie budzi żadnych wątpliwości? Wskazówką może być jeszcze inny wiersz naszego kodu: // Tutaj importujemy implementację czytnika XML (XML Reader) import org.apache.xerces.parsers.SAXParser;
Jawnie importujemy implementację klasy XMLReader danego producenta, a następnie bezpośrednio tworzymy egzemplarz tej implementacji. Problem nie wynika z trudności tego zadania, ale z faktu, że gubimy jedną z najważniejszych cech Javy — przenośność. Takiego kodu nie da się uruchomić, a nawet skompilować na platformie nie wykorzystującej parsera Apache Xerces. A przecież możliwe jest nawet, że w zaktualizowanej wersji Xerces nazwa używanej klasy może ulec zmianie! A zatem nasz kod nie jest przenośny. W związku z tym zaleca się stworzenie egzemplarza klasy o nazwie klasy pobranej z implementacji. W ten sposób w kodzie źródłowym możliwe jest zastosowanie rozwiązania polegającego na zmianie prostego parametru String. Wszystko to jest możliwe w interfejsie SAX 2.0, a klasa, która udostępni potrzebną nam metodę, nosi nazwę org.xml.sax.helpers.XMLReaderFactory: /** * Próba utworzenia egzemplarza czytnika XML z nazwy klasy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 83
84
Rozdział 3. Przetwarzanie kodu XML * *
Po otrzymaniu nazwy klasy metoda ta usiłuje załadować * i stworzyć egzemplarz klasy jako czytnika XML.
* * @return Nowy czytnik XML. * @exception org.xml.sax.SAXException Jeśli klasa nie może zostać * załadowana; nie można stworzyć egzemplarza lub * wykonać rzutowania na XMLReader. * @see #createXMLReader() */ public static XMLReader createXMLReader (String className) throws SAXException { // Implementacja }
Ta statyczna metoda pobiera nazwę klasy parsera do załadowania, a zwraca wersję tej klasy w postaci egzemplarza rzutowanego na interfejs XMLReader (zakładając, że faktycznie implementowany jest XMLReader). Ewentualne problemy są zgłaszane do programu wywołującego za pośrednictwem SAXException. Teraz wystarczy dodać jedną instrukcję importującą, usunąć odwołanie do parsera konkretnego producenta, wprowadzić powyższe zmiany i już można przekompilować nasz kod źródłowy: import java.io.IOException; import import import import import import import import
// To usuwamy // import org.apache.xerces.parsers.SAXParser;
No i znów mamy przenośny kod! Aby jeszcze bardziej rozszerzyć przenośność, warto przechowywać nazwę klasy parsera w pliku właściwości. Umożliwia to ładowanie klasy „na gorąco”, a kod
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 84
Uwaga! Pułapka!
85
można przenosić pomiędzy platformami bez konieczności rekompilacji — zmianie ulega jedynie plik właściwości. Tak przebiega proces odczytywania plików właściwości w programie: try { // Stwórz egzemplarz parsera XMLReader parser = XMLReaderFactory.createXMLReader( PropertiesReader().getInstance() .getProperty("parserClass")); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Klasa narzędziowa PropertiesReader posłużyła tutaj do odczytania pliku właściwości i zwrócenia wartości odpowiadającej kluczowi parserClass (wartość ta będzie zawierała nazwę klasy parsera, którą należy wykorzystać dla danej platformy. W naszych przykładach byłby to łańcuch org.apache.xerces.SAXParser. Oczywiście, można byłoby także skorzystać z systemowych właściwości Javy, ale nie są one odpowiednie dla rozproszonych aplikacji WWW przedstawionych w tej książce (obsługiwane są z poziomu wiersza poleceń). Często aplikacje rozproszone uruchamiane są jako całość, a nie indywidualnie, a więc określenie właściwości systemowych dla jednego tylko komponentu byłoby trudne.
Uwaga! Pułapka! Programista w trakcie pracy może napotkać wiele pułapek. Czytelnik powinien wiedzieć, jak unikać typowych błędów programistycznych związanych z używaniem interfejsu SAX; w dalszych rozdziałach zostaną także omówione pułapki związane z innymi interfejsami API.
Mój parser nie obsługuje interfejsu SAX 2.0. Co robić? Nie załamywać się. Przede wszystkim zawsze można zmienić parser na inny — utrzymanie zgodności z najnowszą wersją SAX należy do obowiązku producenta parsera. Jeśli producent nie nadąża za standardami, to znaczy, że w naszym parserze mogą też istnieć inne błędy. Istnieją jednak przypadki, kiedy zmuszeni jesteśmy korzystać z danego parsera (np. z powodu konieczności obsługi zastanego kodu lub aplikacji). Jednak nawet w takich sytuacjach nie wszystko jest stracone. SAX 2.0 wyposażony jest w klasę pomocniczą org.xml.sax.helpers.ParserAdapter, która powoduje, że implementacja klasy Parser w SAX 1.0 zachowuje się jak implementacja XMLReader w interfejsie SAX 2.0. To poręczne narzędzie pobiera implementację Parser z wersji 1.0 jako parametr wejściowy i dalej może być używane zamiast tej implementacji. Umożliwia ustawienie ContentHandler i poprawnie obsługuje wszystkie wywołania związane z przestrzenią nazw. Jedyna funkcja, do jakiej nie będziemy mieli dostępu, to obsługa pomijania encji
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 85
86
Rozdział 3. Przetwarzanie kodu XML
(nie była ona po prostu dostępna w implementacji 1.0 w jakiejkolwiek formie, więc nie może być emulowana). Sposób użycia tej klasy przedstawiony jest w przykładzie 3.6.
Przykład 3.6. Korzystanie z klasy Parser z SAX 1.0 jako klasy XMLReader z 2.0. try { // Zarejestruj parser w SAX Parser parser = ParserFactory.makeParser( "org.apache.xerces.parsers.SAXParser"); ParserAdapter myParser = new ParserAdapter(parser); // Zarejestruj procedurę obsługi zawartości myParser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów myParser.setErrorHandler(errorHandler); // Przetwórz dokument myParser.parse(uri); } catch (ClassNotFoundException e) { System.out.println( "Nie znaleziono klasy parsera."); } catch (IllegalAccessException e) { System.out.println( "Niewystarczające przywileje do załadowania klasy parsera."); } catch (InstantiationException e) { System.out.println( "Niemożliwe utworzenie egzemplarza klasy parsera."); } catch (ClassCastException e) { System.out.println( "Parser nie ma zaimplementowanego org.xml.sax.Parser"); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Jeśli Czytelnik dopiero zaczyna poznawać interfejs SAX i ma kłopoty ze zrozumieniem tego przykładu, nie powinien się martwić — w przypadku korzystania z najnowszej i najlepszej wersji SAX (2.0) prawdopodobnie nigdy nie będzie trzeba używać kodu podobnego do powyższego. Przydatny jest on tylko tam, gdzie konieczne jest korzystanie z parsera 1.0.
SAX XMLReader — wielokrotne użycie a współbieżność Jedną z najciekawszych cech Javy jest fakt, że w niezwykle prosty sposób można wielokrotnie używać tych samych obiektów. Cechę tę posiadają również parsery SAX. Po utworzeniu egzemplarza XMLReader możliwe jest ciągłe jego używanie i przekazywanie mu wielu (nawet setek) dokumentów. Kolejne dokumenty lub źródła InputSources przekazywane są parserowi, zatem może być on wykorzystany do różnych zadań. Parsery nie są jednak współbieżne. Kiedy już rozpoczął się proces przetwarzania, parsera nie będziemy w stanie wykorzystać ponownie dopóty, dopóki to pierwsze przetwarzanie nie zostanie zakończone. Dla tych z Czytelników, którzy lubią korzystać z algorytmów rekurencyjnych, to właśnie może stanowić pułapkę. Kiedy spróbujemy użyć parsera wtedy, gdy ten jest akurat w trakcie przetwarzania innego dokumentu, zgłoszony zostanie raczej niemiły SAXException i całe przetwarzanie zostanie zatrzymane. Jaki z tego wy-
C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 86
Uwaga! Pułapka!
87
nika wniosek? Dokumenty należy przetwarzać jeden po drugim, albo — godząc się na wszelkie tego skutki — tworzyć więcej egzemplarzy parsera.
Locator w złym miejscu Kolejną niebezpieczną (a z pozoru niewinną) cechą zdarzeń SAX jest fakt udostępniania egzemplarza Locator poprzez wywołanie metody setDocumentLocator(). W ten sposób aplikacja poznaje źródło zdarzenia SAX, co umożliwia podjęcie decyzji o dalszym przetwarzaniu oraz sposobie reagowania na inne zdarzenia. Jednakże to miejsce jest określone poprawnie jedynie na czas istnienia egzemplarza ContentHandler. Po ukończeniu przetwarzania Locator nie jest już poprawny (szczególnie wtedy, gdy rozpoczyna się następne przetwarzanie). Błędem popełnianym przez nowicjuszy jest przechowywanie referencji do obiektu Locator wewnątrz zmiennej należącej do klasy spoza wywołania wstecznego: public void setDocumentLocator(Locator locator) { // Zachowanie Locator w klasie poza ContentHandler mojaInnaKlasa.setLocator(locator); } ... public metodaInnejKlasy() { // Próba użycia poza ContentHandler System.out.println(locator.getLineNumber()); }
To bardzo zły pomysł — Locator traci znaczenie, bo jesteśmy już poza implementacją ContentHandler. Często korzystanie z takiej zmiennej powoduje nie tylko otrzymywanie przez aplikację nieprawidłowych informacji, ale również uszkodzenie dokumentu XML. Innymi słowy, obiektu tego należy używać lokalnie, a nie globalnie. W naszej implementacji ContentHandler otrzymany Locator zachowaliśmy do zmiennej. Następnie moglibyśmy jej użyć w poprawny sposób (np. do podania numerów wierszy, w których napotkaliśmy poszczególne elementy): public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) throws SAXException { System.out.print("startElement: " + localName + " w wierszu " + locator.getLineNumber()); if (!namespaceURI.equals("")) { System.out.println(" w przestrzeni nazw " + namespaceURI + " (" + rawName + ")"); } else { System.out.println(" nie posiada skojarzonej przestrzeni nazw"); } for (int i=0; i
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 94
Definicje typu dokumentu
95
Oto moja książka Rozdział pierwszy A tu inna część
W powyższym fragmencie zostały użyte wyłącznie elementy dozwolone w DTD, a jednak jego struktura nie jest poprawna. Wynika to stąd, że nasza definicja DTD nie określa, w jaki sposób mogą być zagnieżdżane elementy i które z nich mogą zawierać dane tekstowe.
Zagnieżdżanie elementów Jedną z najważniejszych cech struktury dokumentu XML jest zagnieżdżanie elementów. Naszą wcześniejszą tabelkę rozszerzymy teraz o informację o dozwolonym zagnieżdżaniu elementów. W ten sposób powstanie hierarchia elementów, którą następnie zdefiniujemy w DTD. Spójrzmy na tabelę 4.2.
Tabela 4.2. Hierarchia elementów Nazwa elementu
Dozwolone zagnieżdżone elementy
JavaXML:Ksiazka
JavaXML:Tytul JavaXML:Spis JavaXML:Copyright JavaXML:Tytul brak JavaXML:Spis JavaXML:Rozdzial JavaXML:PodzialSekcji JavaXML:Rozdzial JavaXML:Naglowek JavaXML:Temat JavaXML:Naglowek brak JavaXML:Temat brak JavaXML:PodzialSekcj brak i JavaXML:Copyright brak
Znaczenie element główny tytuł dokumentowanej książki oznacza spis treści książki rozdział książki nagłówek (tytuł) rozdziału tematyka poruszana w rozdziale podział pomiędzy rozdziałami oznaczający nową część książki informacje o prawach autorskich
Teraz możemy już określić sposób zagnieżdżania w DTD. Robimy to za pomocą następującej konstrukcji:
W tym przypadku typem elementu staje się lista elementów rozdzielonych przecinkami. Istotna jest kolejność elementów, z czego wynika dalsze zawężenie dokumentu XML. W ten sposób gwarantujemy, że np. element copyright znajdzie się na końcu książki, albo że tytuł występuje przed spisem. Teraz możemy naszą definicję DTD wzbogacić o opis zagnieżdżania (przykład 4.6).
Przykład 4.6. Definicja DTD określająca hierarchię elementów
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 95
96
Rozdział 4. Zawężanie danych XML JavaXML:Spis (JavaXML:Rozdzial, JavaXML:PodzialSekcji)> JavaXML:Rozdzial (JavaXML:Naglowek, JavaXML:Temat)> JavaXML:Naglowek ANY> JavaXML:Temat ANY> JavaXML:PodzialSekcji ANY> JavaXML:Copyright ANY>
Choć niektóre elementy — te, które zawierają przetwarzane dane — nie uległy zmianie, stworzyliśmy hierarchię, która nadaje znaczenie zawężonemu dokumentowi. Wcześniejszy bezsensowny przykład teraz traktowany byłby już jako niepoprawny. Wciąż jednak jest tutaj sporo problemów związanych z dozwolonymi typami danych w pozostałych elementach.
Przetwarzane dane Typ elementu wykorzystywany do określania danych tekstowych to #PCDATA. Nazwa tego typu wywodzi się ze słów Parsed Character Data (przetwarzane dane tekstowe). Za jego pomocą oznaczamy elementy zawierające dane tekstowe, które mają być w zwyczajny sposób traktowane przez parser XML. Zastosowanie tego typu wyklucza jednak możliwość obecności w elemencie innych, zagnieżdżonych elementów. Tego typu sytuacje zostaną przedstawione dalej. Tymczasem możemy już zmienić elementy określające tytuł, nagłówek i temat — w nich mogą być zawarte dane tekstowe (przykład 4.7).
Przykład 4.7. Definicja DTD opisująca hierarchię elementów i elementy zawierające dane tekstowe JavaXML:Spis (JavaXML:Rozdzial, JavaXML:PodzialSekcji)> JavaXML:Rozdzial (JavaXML:Naglowek, JavaXML:Temat)> JavaXML:Naglowek (#PCDATA)> JavaXML:Temat (#PCDATA)> JavaXML:PodzialSekcji ANY> JavaXML:Copyright ANY>
Elementy puste Oprócz elementów zawierających dane tekstowe oraz tych, które zawierają inne elementy, mamy jeden element, JavaXML:PodzialSekcji, który nie ma zawierać żadnych danych. Oczywiście, można by określić, że element ten zawiera przetwarzane dane tekstowe i nigdy ich tam nie wstawiać, ale nie tak należy korzystać z zawężeń. Lepiej jawnie określić, że element ma być pusty — w ten sposób zapobiegamy przypadkowym błędom. Tym razem odpowiednie słowo kluczowe to EMPTY. Nie musi ono pojawiać się w nawiasach, ponieważ określa pewien typ i nie może być łączone z innymi elementami, na co — jak wkrótce zobaczymy — pozwalają właśnie nawiasy. Wprowadźmy więc kolejną poprawkę do definicji DTD (przykład 4.8).
Przykład 4.8. Definicja DTD z określonym elementem EMPTY
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 96
Encje Ostatnim elementem, który należy uściślić, jest JavaXML:Copyright. Jak już to było wspomniane, element ten faktycznie zawiera encję odwołującą się do innego pliku, który w tym miejscu ma zostać zawarty. Kiedy dokument XML „zobaczy” &OReillyCopyright;, będzie usiłował odnaleźć encję OReillyCopyright w DTD, która w naszym przypadku powinna stanowić odwołanie do zewnętrznego pliku. Plik ten zawiera informacje o prawach autorskich wszystkich książek udokumentowanych za pomocą języka XML. Zadaniem DTD jest określenie miejsca położenia pliku oraz sposobu uzyskania dostępu do niego. Tutaj zakładamy, że plik z prawami autorskimi znajduje się na dysku lokalnym i że do niego właśnie będziemy się odwoływali. Encje określane są w ramach definicji DTD za pomocą następującego zapisu:
Zauważmy, że umożliwia się podanie zestawu znaków podstawianych (zamiast pliku zewnętrznego). To właśnie w ten sposób określane są „sekwencje unikowe” w dokumentach XML: ...
Tak więc jeśli nasza informacja o prawach autorskich byłaby krótka, moglibyśmy napisać:
Jednakże my zamierzamy użyć dłuższego tekstu, a więc wygodniej będzie pobrać go z zewnętrznego pliku. W ten sposób możemy z niego korzystać w wielu dokumentach bez konieczności duplikowania danych w poszczególnych definicjach DTD. Wymaga to podania identyfikatora zasobu systemowego, dlatego korzystamy tutaj z następującego zapisu:
Tak jak w przypadku dokumentu XML, URI może być zarówno zasobem lokalnym, jak i sieciowym. W naszym przypadku odwołujemy się do pliku znajdującego się na serwerze zewnętrznym, więc musimy zastosować adres URL:
Dzięki takiemu zapisowi parser XML rozpozna referencję OReillyCopyright w dokumencie i odpowiednio przetłumaczy ją w czasie przetwarzania. Z tego właśnie powodu musieliśmy ten fragment oznaczyć jako komentarz w rozdziale 3. W następnym rozdziale usuniemy symbole komentarza i zobaczymy, w jaki sposób parser sprawdzający poprawność składni obsłuży tę encję z wykorzystaniem definicji DTD.
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 97
98
Rozdział 4. Zawężanie danych XML
Na koniec w elemencie zawierającym encję musimy podać, że oczekujemy w tym miejscu przetwarzanych danych tekstowych:
Powtórz to, a... Ostatnią ważną konstrukcją, jaką omówimy w związku z definicjami DTD, jest opis grupowania, wielokrotnego występowania i dopuszczalnych kombinacji w ramach elementu. Innymi słowy, chodzi tu np. o sytuacje, w których element X może wystąpić tylko raz, lub też po elemencie Y musi wystąpić element Z. To niezwykle istotne konstrukcje DTD. Domyślnie, jeśli nie podaliśmy w elemencie specjalnych modyfikatorów, element może wystąpić dokładnie raz:
W tym przypadku ZagniezdzonyElement ma wystąpić tylko raz, a po nim koniecznie musi znajdować się InnyElement. Jeśli struktura dokumentu XML jest inna, to dokument ten nie jest poprawny. Aby zmienić to domyślne zawężenie, można zastosować specjalne modyfikatory.
Zero, jeden lub więcej Najczęściej do elementu dodawany jest operator rekurencji. Umożliwia on określenie, czy dany element ma się pojawić zero lub więcej razy, jeden lub więcej razy, wcale lub (ustawienie domyślne) dokładnie raz. W tabeli 4.3 przedstawione są operatory rekurencyjne i ich znaczenia.
Tabela 4.3. Operatory rekurencyjne Operator
Opis
[Domyślnie] ? + *
musi wystąpić dokładnie raz musi wystąpić raz albo wcale musi wystąpić przynajmniej raz (1 ... n razy) może wystąpić dowolną liczbę razy (0 ... n razy)
Modyfikatory dodawane są na końcu nazwy elementu. W naszym poprzednim przykładzie, aby zezwolić na jedno lub więcej wystąpień elementu ZagniezdzonyElement, a potem na tylko jedno (lub wcale) wystąpienie elementu InnyElement, należałoby użyć następującej konstrukcji:
Wtedy taki zapis XML byłby zupełnie poprawny: Jeden Dwa
W definicji DTD, którą tworzymy, z taką sytuacją mamy do czynienia w przypadku elementu JavaXML:Rozdzial. Chcemy, aby nagłówek rozdziału (JavaXML:Naglowek) mógł wystąpić raz lub wcale, chcemy również pozwolić na wystąpienie jednego lub więcej elementów JavaXML:Temat. Wykorzystujemy modyfikatory rekurencyjne:
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 98
Definicje typu dokumentu
99
Ta prosta zmiana czyni naszą reprezentację rozdziału w XML bardziej realistyczną. Musimy także dokonać zmian w definicji elementu JavaXML:Spis. Może się w nim pojawić jeden lub więcej rozdziałów, a potem, ewentualnie, podział sekcji. Podział jest opcjonalny, bo książka może się składać wyłącznie z rozdziałów. Rekurencję rozdziałów i podziałów sekcji zapiszemy następująco:
Wciąż jednak definicja DTD „nie wie”, że po elemencie JavaXML:PodzialSekcji może wystąpić więcej rozdziałów. Chcemy, aby cała ta struktura mogła pojawić się kilkakrotnie. Po rozdziale podział sekcji, potem znów rozdziały i znów podziały sekcji. Musimy zastosować grupowanie.
Grupowanie Grupowanie umożliwia rozwiązywanie problemów takich jak ten z zagnieżdżaniem elementów wewnątrz JavaXML:Spis. Często rekurencja dotyczy bloku elementów, a nie jednego elementu. Dlatego operatory rekurencji można dodawać także do grup elementów. Grupę elementów tworzymy poprzez ujęcie elementów w nawiasy. Jeśli w tej chwili Czytelnikowi przypomniały się zajęcia z języka LISP, to niech się nie zamartwia — w naszych przykładach wszystko będzie dosyć proste, a liczba nawiasów nie wymknie się spod kontroli. Oczywiście, zagnieżdżanie nawiasów jest dozwolone. Tak więc, aby stworzyć grupę elementów, zastosujemy następującą konstrukcję:
Do każdej grupy (a nie tylko do poszczególnych jej elementów) można dopisać modyfikator. W opisywanym scenariuszu musimy tak zmienić zapis, aby możliwe było wielokrotne wystąpienie grupy zawierającej elementy rozdziału i podziału sekcji:
Teraz dozwolone są już różne kombinacje — kilka rozdziałów plus jeden podział sekcji i to wszystko powtórzone wiele razy lub też nie powtórzone wcale. Możliwy jest także przypadek, w którym wystąpią same tylko rozdziały, bez podziałów sekcji. Jednakże tutaj sprawa nie jest zupełnie jasna (według naszego opisu w DTD). Lepiej byłoby określić, że pojawić się może jeden lub więcej rozdziałów lub taka struktura. Zachowanie DTD się nie zmieni, ale z pewnością definicja będzie bardziej czytelna. Aby to uzyskać, należy wprowadzić pojęcie „lub”.
To lub tamto W definicjach DTD możliwe jest stosowanie pojęcia „lub”, oznaczanego symbolem kreski pionowej. Symbol ten jest często wykorzystywany w połączeniu z grupowaniem. Częstym, choć niekoniecznie udanym przykładem użycia operatora „lub” jest umożliwienie wystąpienia wewnątrz elementu pewnego innego elementu (elementów) lub danych:
W przypadku tej definicji DTD oba poniższe fragmenty będą poprawne: Jeden Dwa Dane tekstowe
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 99
100
Rozdział 4. Zawężanie danych XML
Nie zaleca się jednak stosowania takiego rozwiązania, gdyż wówczas staje się niejasne znaczenie elementu zamykającego. Zazwyczaj element powinien zawierać dane tekstowe, dane przetwarzane lub inne elementy — ale nie wszystko razem. W naszym dokumencie zależy nam na bardziej przejrzystej reprezentacji elementu JavaXML: Spis. Zastosujemy następujące rozwiązanie:
Teraz jest już jasne, że może pojawić się tutaj albo pewna liczba rozdziałów, albo rozdziały, po których następuje podział sekcji. W ten sposób tworzy się przejrzyście udokumentowaną definicję, a jednocześnie zapewnia odpowiednie zawężenie dokumentu XML. Teraz nasze elementy XML są już poprawnie określone i zawężone. Definicja przedstawiona w przykładzie 4.9 powinna działać bezproblemowo na naszym przykładowym dokumencie. Pozostały nam jeszcze definicje argumentów, które omawiamy poniżej.
Przykład 4.9. Opis elementów w definicji DTD
Definiowanie atrybutów Po tym dość dokładnym omówieniu sposobu definiowania elementów możemy przejść do definiowania atrybutów. Ponieważ tutaj nie będziemy mieli do czynienia z zagnieżdżaniem, czynność ta jest nieco prostsza niż opisywanie elementów. Nie będziemy też potrzebowali operatorów rekurencji, ponieważ o tym, czy obecność danego atrybutu jest wymagana, mówi określone słowo kluczowe. Definicje atrybutów przyjmują następującą formę:
Pierwsze dwa parametry, nazwa elementu i atrybutu, nie powinny sprawiać trudności. Za pomocą jednej konstrukcji ATTLIST dowolnemu elementowi możemy przypisać wiele atrybutów. Taki schemat jak powyżej zostanie dodany teraz do naszej definicji DTD, dzięki czemu lepiej będzie przedstawiona struktura opisywania atrybutów. Najlepiej definicje atrybutów dodawać zaraz po opisie elementu (to kolejny krok w kierunku utworzenia samodokumentującego się opisu DTD). Spójrzmy na przykład 4.10.
Przykład 4.10. Definicja DTD opisująca elementy i zawierająca ogólny szkielet opisów atrybutów
Teraz zdefiniujmy typy dozwolone dla poszczególnych atrybutów.
Typy atrybutów Wartością wielu atrybutów będą dane tekstowe. To najprostszy typ wartości atrybutu, ale też najmniej zawężony. Typ ten określamy za pomocą słowa kluczowego CDATA (Character Data — dane tekstowe). Jest to ta sama konstrukcja CDATA, która w samym dokumencie XML reprezentuje dane wchodzące w skład „sekwencji unikowej”. Typ ten jest zazwyczaj używany wtedy, gdy atrybut może przyjąć dowolną wartość i służy jako komentarz lub dodatkowa informacja o danym elemencie. Wkrótce Czytelnik przekona się, że lepszym rozwiązaniem jest zdefiniowanie zestawu wartości dozwolonych dla atrybutu danego elementu. W naszym dokumencie typem tekstowym jest atrybut xmlns. Co prawda xmlns to słowo kluczowe XML oznaczające deklarację przestrzeni nazw, ale wciąż jest to atrybut, którego poprawność ma zostać sprawdzona — i musi zostać opisany w definicji DTD. Atrybut podRozdzialy elementu JavaXML:Temat przyjmuje także wartość tekstową:
Następny typ atrybutu (jeden z najczęściej używanych) to wyliczenie. Umożliwia określenie konkretnych wartości atrybutu. Wartości nie wymienione w wyliczeniu powodują, że dokument XML jest niepoprawny. Rozwiązanie to przydaje się wszędzie tam, gdzie zestaw wartości atrybutu zna-
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 101
102
Rozdział 4. Zawężanie danych XML
my już w czasie tworzenia dokumentu i umożliwia on bardzo ścisłe zawężenie definicji elementu. Taki typ ma nasz atrybut tematyka, ponieważ dozwolone są dla niego jedynie wartości „Java” lub „XML”. Wartości dozwolone umieszczane są w nawiasach i rozdzielane znakiem operatora „lub”, podobnie jak przy określaniu zagnieżdżania elementów:
Być albo nie być ... W definicji atrybutu należy jeszcze określić, czy atrybut ten jest w danym elemencie wymagany. Służą do tego trzy słowa kluczowe: #IMPLIED, #REQUIRED lub #FIXED. Atrybut niejawny (ang. implied) nie jest wymagany. Takim modyfikatorem można określić atrybut podRozdzialy, jako że nie jest on wymagany do stworzenia poprawnego dokumentu:
Jeśli chodzi o atrybut xmlns, to chcemy zagwarantować, żeby autor zawartości zawsze określał przestrzeń nazw dla danej książki. W przeciwnym razie nasze przedrostki określające przestrzeń nazw na nic by się nie zdały. W takim przypadku należy zastosować słowo #REQUIRED (wymagany). Gdyby atrybut ten nie został zawarty w elemencie JavaXML:Ksiazka, dokument nie byłby poprawny:
Ostatnie słowo kluczowe, #FIXED, nie jest zbyt często używane w aplikacjach. Najczęściej stosuje się je w systemach bazowych; określa ono, że użytkownik nie może nigdy zmienić wartości danego atrybutu. Format jest tutaj następujący:
Ponieważ atrybut ten nie przydaje się w aplikacjach dynamicznych (nie jest możliwa jego zmiana), nie będziemy się nim tutaj zajmować. Do omówienia pozostał jeszcze atrybut tematyka. Wymieniliśmy wszystkie możliwe wartości, jakie może przyjmować, ale ponieważ w tej książce koncentrujemy się głównie na Javie, chcielibyśmy, aby autor nie musiał jawnie definiować atrybutu jako „Java” w tych rozdziałach, w których rzeczywiście Java jest głównym tematem. W książce składającej się z dwudziestu czy trzydziestu rozdziałów ciągłe wpisywanie atrybutu może okazać się męczące. Wyobraźmy sobie listę książek w bibliotece naukowej, w której jako temat każdej książki należałoby podać „nauka”! Duplikowanie danych wydłuża proces, a więc wymaganie podawania atrybutu za każdym razem nie jest dobrym rozwiązaniem. Chcemy więc przypisać atrybutowi pewną domyślną wartość, kiedy żadna wartość nie zostanie podana przez autora (i nie załatwia tego słowo #IMPLIED, które powoduje, że atrybutowi nie zostaje przypisana żadna wartość). Domyślną wartość można określić
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 102
Definicje typu dokumentu
103
poprzez wpisanie jej w cudzysłowach zamiast wpisania słowa kluczowego modyfikatora. Jeśli typem atrybutu jest wyliczanie (enumeration), wartość ta powinna być jednym z wymienionych łańcuchów znaków. Zdefiniujmy więc nasz atrybut tematyka:
Nasza definicja DTD jest już gotowa! Składnia definicji DTD może wydawać się nieco dziwaczna, ale mam nadzieję, że Czytelnikowi nie sprawiło trudności śledzenie kolejnych etapów definiowania elementów, atrybutów i encji. Nie omówiliśmy oczywiście wszystkich zagadnień związanych z definicjami DTD, ponieważ niniejsza książka nie traktuje o samym XML-u, ale o Javie i XML-u. Mam jednak nadzieję, że przykładowa definicja DTD jest zrozumiała i że Czytelnik będzie potrafił stworzyć proste definicje dla własnych dokumentów. Zanim przejdziemy do omawiania schematów, spójrzmy raz jeszcze na gotową definicję DTD (przykład 4.11).
Przykład 4.11. Gotowa definicja DTD
Po dokładnym przyjrzeniu się tej definicji DTD można uznać, że jest ona zbyt złożona. Struktura definicji DTD rządzących organizacją plików XML zupełnie nie przypomina samych plików XML. Struktura DTD jest odmienna od struktury schematu, arkusza stylu XSL i niemal każdego innego dokumentu związanego z językiem XML. Definicje DTD opracowano jako część specyfikacji XML 1.0. Niektóre decyzje projektowe podjęte w ramach tej specyfikacji odbiły się piętnem na użytkownikach i programistach korzystających z XML. Podstawy definicji DTD w standardzie XML oparte są w dużej części na SGML-u — specyfikacji o wiele starszej. Jednakże struktura definicji DTD z czasów SGML-a stanowi niezbyt dobry wybór w odniesieniu do XML-a. Te różnice strukturalne stara się naprawić standard XML Schema, który skoncentrowany jest właśnie na XML-u i nie narusza stylu poprawnego programowania. Schematy XML Schema zostaną wkrótce omówione. Najprawdopodobniej schematy całkowicie zastąpią definicje DTD, ale zastępowanie to przebiega powoli — w wielu aplikacjach XML został już wdrożony w systemach produkcyjnych, w których są używane dokumenty zawężane definicjami DTD. To dlatego właśnie zrozumienie definicji DTD jest tak istotne — nawet jeśli mają one odejść do lamusa.
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 103
104
Rozdział 4. Zawężanie danych XML
Czego brak w definicji DTD? Co ciekawe, aż całą sekcję poświęcić trzeba sprawom, które nie zostały ujęte w definicji DTD. W DTD podać trzeba wszystkie elementy i zdefiniować wszystkie atrybuty dokumentu XML, ale nie muszą się tam znaleźć instrukcje przetwarzania (PI). Co więcej, w ogóle nie ma możliwości określenia instrukcji PI, podobnie jak deklaracji XML znajdującej się na samym początku dokumentu XML. Definicja DTD rozpoczyna się wraz z pierwszym wystąpieniem pierwszego elementu w pliku XML. Takie podejście może się wydawać naturalne — po co określać, że dokument ma zawierać taką, a nie inną instrukcję przetwarzani?. Odpowiedzią jest przenośność. Są powody, dla których określanie instrukcji przetwarzania przydałoby się w definicjach DTD. Na przykład możliwa jest sytuacja, w której autor zawartości chciałby mieć gwarancję, że jego dokument XML zostanie zawsze przekształcony — a więc wymaga instrukcji opisującej arkusz stylu xml-stylesheet. Ale który arkusz chcemy wykorzystać? To trzeba również określić. A jaki mechanizm ma wykonać przekształcenia? Cocoon? Servlet Jamesa Clarka? Inna struktura? To wszystko również trzeba zdefiniować. Jednak kiedy wszystko zostanie dokładnie określone, dokument utraci przenośność. Będzie mógł być wykorzystany już tylko do jednego konkretnego celu i w jednej specyficznej strukturze publikacji. Nie będzie go łatwo przenieść na inną platformę, strukturę czy aplikację. Dlatego instrukcje PI i deklaracje XML pozostają niezawężone w ramach definicji DTD. Zajmujemy się tylko elementami i atrybutami dokumentu, począwszy od elementu głównego.
XML Schema XML Schema to nowy projekt roboczy grupy W3C, uwzględniający dotychczasowe problemy i ograniczenia definicji DTD. Umożliwia on bardziej precyzyjną reprezentację zawężeń struktury XML i, co istotne, wykorzystuje w tym celu sam język XML. Schematy to właściwie dokumenty XML — dobrze sformatowane i poprawne. Mogą więc być obsługiwane przez parsery i inne aplikacje „znające XML” w sposób podobny jak zwykłe dane XML. Nie trzeba stosować specjalnych technik, jak przy obsłudze definicji DTD. Ponieważ XML Schema to jeszcze „młoda” i niepełna specyfikacja, zostanie omówiona pokrótce. Szczegóły implementacji mogą w każdej chwili ulec zmianie; jeśli więc Czytelnik ma problemy z omawianymi przykładami, może przejrzeć najnowszą wersję projektu pod adresami http://www.w3.org/TR/xmlschema-1/ i http://www.w3.org/TR/xmlschema-2/. Należy także pamiętać, że wiele parserów XML nie obsługuje schematów XML, bądź też obsługuje je tylko częściowo. Stopień obsługi XML Schema opisany jest w instrukcji parsera. Istnieje różnica pomiędzy dokumentem poprawnym a dokumentem poprawnym z punktu widzenia schematu. XML Schema nie stanowi części specyfikacji XML 1.0, a więc dokument zgodny z danym schematem może być niepoprawny. Tylko dokument XML zgodny z definicją DTD określoną w deklaracji DOCTYPE może być uważany za poprawny. W związku z tym w środowisku użytkowników XML-a rodzą się wątpliwości dotyczące sposobu obsługi sprawdzania poprawności poprzez XML Schema. Ponadto, nawet jeśli dokument posiada odpowiadający mu schemat, to nie znaczy jeszcze, że jego poprawność będzie sprawdzana — ponieważ XML Schema nie jest opisany w specyfikacji XML 1.0, aplikacja lub parser wcale nie muszą dokonywać takiego sprawdzania (bez względu na poziom obsługi schematów przez parser). Dlatego zawsze trzeba sprawdzić, czy parser będzie sprawdzał poprawność dokumentu, oraz jak obsługuje schematy. Na potrzeby przejrzystości opisu będziemy zakładać, że sprawdzanie poprawności to jedno pojęcie, obejmujące
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 104
XML Schema
105
zarówno schematy, jak i definicje DTD; znaczenie tego pojęcia będzie zaś musiało być interpretowane w odpowiednim kontekście. Na ewentualne dwuznaczności zawsze będziemy zwracali uwagę. Najważniejsze przy budowaniu schematu jest to, że w trakcie tego procesu powstaje po prostu nowy dokument XML. W przeciwieństwie do definicji DTD, korzystających z zupełnie odmiennego formatu opisu elementów i definiowania atrybutów, schemat jest zwyczajnym dokumentem XML. Składnia nie będzie więc znacząco odbiegała od tej opisanej w rozdziale 2. Co ciekawe, sam schemat XML jest zawężony za pomocą definicji DTD. Jeśli to wydaje się nieco osobliwe, to należy przypomnieć, że przed powstaniem XML Schema jedynym sposobem zawężania dokumentów było właśnie zastosowanie definicji DTD. Aby poprzez XML Schema można było sprawdzać poprawność, trzeba najpierw określić zawężenia samego schematu, korzystając z „zewnętrznego mechanizmu”. A tym zewnętrznym mechanizmem musi być definicja DTD. Jednak już po stworzeniu tej początkowej definicji wszystkie inne dokumenty XML w ogóle nie będą korzystały z DTD. Ta nieco dziwna kolej rzeczy jest zjawiskiem dość pospolitym w ewoluującym świecie specyfikacji — nowe wersje powstają na bazie starych.
Przestrzeń nazw schematu Można oczekiwać, że dokumenty XML Schema rozpoczynają się standardową deklaracją XML, po czym następuje odwołanie do przestrzeni nazw schematu. I tak jest w rzeczywistości. Ponadto istnieją standardy nazywania elementu głównego. Przyjęło się, że element główny nosi nazwę schema. Przy tworzeniu elementu głównego buduje się również definicje przestrzeni nazw — podobnie jak to robiliśmy w naszym przykładowym dokumencie XML. Przede wszystkim konieczne jest podanie domyślnej deklaracji przestrzeni nazw:
Jak to zostało przedstawione w rozdziale 2., pominięcie identyfikatora po atrybucie xmlns powoduje zastosowanie w dokumencie domyślnej przestrzeni nazw. W naszym wcześniejszym dokumencie XML definicja przestrzeni nazw odpowiadała konkretnie przestrzeni JavaXML:
W ten sposób informowaliśmy parser XML, że wszystkie dokumenty z przedrostkiem JavaXML należą do tej konkretnej przestrzeni nazw, skojarzonej z podanym adresem URL. W naszym dokumencie XML dotyczyło to wszystkich elementów, bo wszystkie opatrzone były tym przedrostkiem. Ale przecież mogły tam pojawić się także elementy bez takich przedrostków. Elementy takie nie są, oczywiście, skazane na niebyt — one również muszą zostać przypisane jakiejś przestrzeni nazw. Przypisuje się im przestrzeń domyślną, która nie jest zdefiniowana w dokumencie. Można zdefiniować ją za pomocą dodatkowej deklaracji przestrzeni nazw w elemencie głównym:
Spowodowałoby to, że dowolny element nie poprzedzony przedrostkiem JavaXML lub innym kojarzony byłby z domyślną przestrzenią nazw, identyfikowaną za pomocą adresu http://www.jakisInnyUrl.com. Tak więc w następującym fragmencie dokumentu elementy Ksiazka, Spis i Tytul skojarzono z przestrzenią JavaXML, zaś element1 i element2 z domyślną przestrzenią nazw: Mój tytuł
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 105
106
Rozdział 4. Zawężanie danych XML
Ponieważ w naszym schemacie opisujemy inny dokument, wszystkie elementy związane z konstrukcjami samego XML Schema powinny należeć do domyślnej przestrzeni nazw — i dlatego podajemy na początku jej deklarację. Jednakże konstrukcje te oddziaływają na przestrzeń nazw w zawężanym dokumencie XML. Innymi słowy, konstrukcje XML Schema stanowią część przestrzeni nazw XML Schema, ale wykorzystywane są do zawężenia elementów w innych przestrzeniach nazw — w tych, które opisują dokument lub dokumenty XML opisywane przez ten schemat. W naszym przykładzie dotyczyłoby to przestrzeni nazw JavaXML. Tak więc do elementu schema trzeba dodać następującą deklarację:
Teraz jeszcze należy „poinformować” schemat, że przedmiotem zawężania jest druga przestrzeń nazw. W tym celu należy zastosować atrybut targetNamespace (docelowa przestrzeń nazw), którego działanie jest zgodne z nazwą:
W ten sposób otrzymaliśmy definicje dwóch przestrzeni nazw (domyślna oraz JavaXML) oraz określiliśmy przedmiot zawężania w postaci drugiej przestrzeni nazw (JavaXML). A skoro jest już zdefiniowany element główny, można rozpocząć nakładanie zawężeń na tę przestrzeń nazw. Należy pamiętać jeszcze o jednej rzeczy: w świecie protokołu HTTP i serwerów WWW możliwe jest, że adres URL skojarzony z daną przestrzenią nazw może być prawdziwym adresem; w naszym przypadku, gdybyśmy do przeglądarki wpisali http://www.oreilly.com/catalog/javaxml, otrzymalibyśmy odpowiedź HTML. Ale zwracany dokument tak naprawdę nie jest wykorzystywany; adres URL wcale nie musi być osiągalny — służy wyłącznie jako element kojarzony z przestrzenią nazw. Nie zawsze jest to łatwo zrozumieć, dlatego Czytelnik nie powinien zaprzątać sobie głowy tym, na co wskazuje podany identyfikator URI; ważniejsze jest skoncentrowanie się na deklarowanej przestrzeni nazw oraz na sposobie, w jaki wykorzystywana jest ona w dokumencie. Uwaga! Zagadnienia omawiane w dalszej części rozdziału nie są proste, więc Czytelnik nie powinien przejmować się ewentualnymi trudnościami z ich zrozumieniem. Koncepcje związane ze schematami XML Schema nie są „łatwe i przyjemne”, a cała ta specyfikacja jest dopiero w fazie rozwoju. Wielu autorów zawartości korzysta z technologii XML Schema, my zaś staramy się tutaj zrozumieć ją — to subtelna, ale istotna różnica. Dzięki zrozumieniu zagadnień związanych z XML Schema Czytelnik będzie potrafił bardziej efektywnie projektować dokumenty XML i tworzyć lepsze aplikacje. Szczególnie złożoną sprawą jest sposób wykorzystania w schematach definicji DTD i przestrzeni nazw; na szczęście większość pozostałych konstrukcji nie jest już tak skomplikowana. Tak więc warto teraz zrelaksować się, napić kawy i uważnie, bez pośpiechu kontynuować lekturę. To na pewno zaowocuje w przyszłości.
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 106
XML Schema
107
Określanie elementów Zagadnienie określania elementów zostało już poruszone przy omawianiu definicji DTD. W schematach określanie elementów okaże się nieco bardziej logiczne. Proces ten dość dokładnie odzwierciedla strukturę deklaracji w Javie (plus kilka dodatkowych opcji). Do określania elementów posłużymy się elementem... element:
[Nazwa elementu] określa element w zawężanym dokumencie XML. Jednakże w przeciwieństwie do definicji DTD, nie powinno być tutaj przedrostka przestrzeni nazw. Czytelnik winien przypomnieć sobie uwagi na temat docelowej przestrzeni nazw. Zostało tam powiedziane, że przestrzenią docelową (ang. target) jest JavaXML, a więc specyfikacje wszystkich elementów, a także typy zdefiniowane przez użytkownika, są kojarzone i przypisywane właśnie tej docelowej przestrzeni nazw. To przyczynia się do utworzenia bardziej przejrzystego dokumentu XML Schema, ponieważ najpierw definiowane są wszystkie elementy, a potem narzuca się przestrzeń nazw. Konstrukcja [Typ elementu] to albo wstępnie zdefiniowany typ danych w schemacie, albo typ zdefiniowany przez użytkownika. W tabeli 4.4 zestawiono typy danych obsługiwane przez bieżącą wersję standardu XML Schema.
Tabela 4.4. Typy danych w XML Schema Typ
Podtypy
Znaczenie
string
łańcuchy znaków
boolean float double decimal timeInstant
NMTOKEN, language brak brak brak integer brak
timeDuration recurringInstant
brak date, time
binary uri
brak enumeration
binarna wartość logiczna (prawda lub fałsz) 32-bitowy typ zmiennoprzecinkowy 64-bitowy typ zmiennoprzecinkowy standardowy zapis dziesiętny, dodatni i ujemny połączenie daty i czasu, wskazujące konkretny moment w czasie czas trwania specyficzny czas, powtarzający się przez okres timeDuration dane binarne jednolity identyfikator zasobów (URI).
W naszych przykładach wykorzystamy tylko niektóre typy. Widać jednak od razu, że wybór jest tutaj większy niż w DTD.
Zaczynamy od dołu Możliwe jest także tworzenie złożonych typów danych, definiowanych przez użytkownika. Typy takie budowane są z kombinacji elementów. Na przykład można określić, że typ Ksiazka składa się z elementu Tytul, Spis i Copyright (zwróćmy uwagę, że przy opisywaniu elementów nie jest już podawany przedrostek przestrzeni nazw — w schematach najpierw odczytywana jest tylko nazwa elementu, dopiero potem definiuje się odpowiednią przestrzeń). Te zaś elementy mogą
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 107
108
Rozdział 4. Zawężanie danych XML
znów być typami zdefiniowanymi przez użytkownika, składającymi się z dalszych elementów. Tak powstaje piramida hierarchii; na jej spodzie znajdują się elementy o podstawowych typach XML Schema; wyżej — warstwy elementów zdefiniowanych przez użytkownika; na samej górze — element główny. Taka struktura wymusza określony sposób działania — najlepiej zaczynać budowanie schematu od określenia elementów znajdujących się u podstaw tej hierarchii — czyli tych, które można zdefiniować jako standardowe typy XML Schema. Sytuacja jest więc tutaj nieco inna niż w definicjach DTD, gdzie zazwyczaj postępuje się zgodnie z kolejnością elementów w dokumencie XML. Ale dzięki temu budowanie schematu jest prostsze. Jeśli spojrzymy na nasz dokument XML, to możemy określić, które elementy stanowią typy „prymitywne” — (patrz tabela 4.5).
Tabela 4.5. Elementy „prymitywne” Nazwa elementu
Typ
Tytul Naglowek Temat
string string string
Teraz elementy te można wpisać do schematu (przykład 4.12). Aby budowany schemat nie utracił przejrzystości, zostaną w nim pominięte deklaracje XML i DOCTYPE; znajdą się one w wersji ostatecznej, ale teraz tylko zmniejszałyby czytelność przykładów. Przykład 4.12. Schemat z elementami „prymitywnymi”
Zbyt proste? Cóż, to jest proste. Po zdefiniowaniu tych „podstawowych”, czy też „prymitywnych” elementów możemy przejść do budowania elementów bardziej złożonych.
Typy danych definiowane przez użytkownika Podobnie jak w przypadku elementów niepodzielnych, elementy bardziej złożone należy budować także „od dołu” piramidy. Niemal zawsze oznacza to, że programista rozpoczyna tworzenie od elementów najgłębiej zagnieżdżonych i posuwa się „w górę”, aż do elementu głównego. W naszym przypadku najbardziej zagnieżdżone są Naglowek i Temat. Ponieważ elementy te już zostały określone jako prymitywne, można przenieść się o poziom wyżej — do elementu Rozdzial. To właśnie będzie pierwszy element zdefiniowany przez użytkownika. W definicji musi zostać zawarta informacja, że składa się on z jednego elementu Naglowek oraz jednego lub więcej elementów Temat. Typy złożone definiujemy za pomocą elementu complexType:
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 108
XML Schema
109
...
Naszemu elementowi przypisujemy nowy typ. Dla elementu Rozdzial stworzymy typ TypRozdzial: ...
Powstały w ten sposób typ staje się, oczywiście, częścią docelowej przestrzeni nazw JavaXML. Tak więc aby przypisać ten typ naszemu elementowi Rozdzial, użyjemy następującej specyfikacji dokumentu:
Teraz, jakąkolwiek strukturę określimy wewnątrz typu TypRozdzial, będzie ona zawężała element Rozdzial. Należy zauważyć, że typ elementu określany jest jako JavaXML:TypRozdzial, a nie po prostu jako TypRozdzial. Typ ten był tworzony w ramach docelowej przestrzeni nazw, JavaXML. Jednak elementy, jakie wykorzystujemy w schemacie (element, complexType itd.), nie mają przedrostka przestrzeni nazw, ponieważ należą do przestrzeni domyślnej schematu. Gdybyśmy więc próbowali określić typ jako TypRozdzial, parser przeszukałby przestrzeń domyślną (tę odnoszącą się do schematu), a nie znajdując tam naszego elementu, zgłosiłby wyjątek. Aby parser „wiedział”, gdzie szukać definicji typu, należy wskazać poprawną przestrzeń nazw — w tym przypadku JavaXML. Ogólną strukturę Czytelnik już zna, pora zająć się szczegółami. W przypadku tego elementu trzeba zdefiniować w schemacie dwa elementy, które są w tym typie zagnieżdżane. Ponieważ już zostały określone dwa zagnieżdżane elementy (prymitywne Naglowek i Temat), należy się do nich dowołać w nowym typie:
Atrybut ref informuje parser XML, że definicja danego elementu znajduje się w innej części schematu. Tak jak przy określaniu typu i tutaj również trzeba wskazać parserowi przestrzeń nazw, w jakiej znajdują się elementy (zazwyczaj jest to przestrzeń docelowa). Ale można tutaj dopatrzyć się pewnej nadmiarowości i „przegadania”. Dwa elementy definiujemy jako prymitywy, a potem się do nich odwołujemy — to daje cztery linijki. Elementy te jednak nie są wykorzystywane nigdzie indziej w naszym dokumencie, a więc czy nie logiczniej byłoby zdefiniować element od razu wewnątrz typu? Nie trzeba byłoby wtedy odwoływać się do elementu i wymuszać na czytających nasz schemat przeszukiwanie dokumentu w celu odnalezienia elementu wykorzystanego tylko raz. Tak można zrobić. Specyfikacja elementu może zostać zagnieżdżona w typie zdefiniowanym przez użytkownika, a więc nieco przerobimy teraz nasz schemat — tak, aby był bardziej czytelny:
Oprócz usunięcia niepotrzebnych wierszy, pozbyliśmy się także niepotrzebnych odwołań do przestrzeni nazw JavaXML. To na pewno zwiększy czytelność, szczególnie wśród odbiorców nie
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 109
110
Rozdział 4. Zawężanie danych XML
mających doświadczenia w pracy z XML-em. Znając sposób tworzenia typów definiowanych przez użytkownika, możemy teraz określić pozostałe elementy, tak jak w przykładzie 4.13.
Przykład 4.13.
Dzięki temu każdy użyty element XML zostanie zdefiniowany, a schemat będzie czytelny. Nie znaczy to jednak, że wszystkie problemy zostały rozwiązane.
Typy niejawne i pusta zawartość Do tej pory korzystaliśmy wyłącznie z typów nazwanych, często określanych mianem jawnych. Typ jawny to taki, w którym podano nazwę typu; element wykorzystujący ten typ zazwyczaj znajduje się w innym miejscu pliku. To bardzo obiektowe podejście — ten sam typ jawny może być zastosowany dla różnych elementów. Jednakże istnieją sytuacje, kiedy taka struktura jest rozwiązaniem na wyrost; innymi słowy, typ jest tak specyficzny względem elementu, że jego nazywanie nie jest potrzebne. W naszym przykładzie definicję elementu Rozdzial moglibyśmy skonsolidować poprzez zdefiniowanie typu w samej definicji elementu. Służą do tego typy niejawne, zwane także nienazwanymi:
Zastosowanie typu niejawnego powoduje, że schemat jest jeszcze bardziej czytelny. Jednakże żaden inny element nie może być tego samego typu, co zdefiniowany w ten sposób typ niejawny — chyba że zdefiniowano kolejny typ niejawny. Innymi słowy, typów niejawnych używamy tylko wtedy, gdy jesteśmy zupełnie pewni, że typ ten nie będzie wykorzystywany przez wiele elementów.
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 110
XML Schema
111
Typy niejawne mogą także służyć do podawania informacji o definiowanych elementach. Na przykład, do tej pory definiowaliśmy element PodzialSekcji jako string. Faktycznie nie jest to określenie zbyt precyzyjne, ponieważ element ten ma być pusty. Aby określić element jako pusty, użyjemy typu niejawnego:
To może wydawać się dziwne — dlaczego nie można po prostu przypisać temu elementowi „pustego” (ang. empty) typu danych? Czy twórcy specyfikacji XML Schema coś przeoczyli? Wręcz przeciwnie — we wcześniejszych wersjach specyfikacji istniał typ danych empty, ale go usunięto. Zrobiono tak, aby wymóc definicje typu elementu. Aby to zrozumieć, zauważmy, że większość pustych elementów może przyjmować atrybuty do określania danych:
W takich przypadkach określenie elementu jako empty nie pozwoliłoby na intuicyjne określenie, jakie atrybuty są w nim dozwolone. Sprawa jest natomiast prosta, jeśli użyjemy typu elementu:
Sposoby definiowania atrybutów zostaną omówione dalej. Tymczasem warto pamiętać, że typy niejawne pomagają budować schemat w sposób bardziej intuicyjny oraz umożliwiają definiowanie właściwości elementu — np. tego, że jest pusty.
Ile? Oprócz powyższych, w definicji elementu należy określić rodzaj rekurencji (lub zdefiniować jej brak). Schemat zachowuje się tutaj podobnie jak DTD — jeśli elementu nie opatrzono modyfikatorami, może pojawić się tylko raz. Jak już wcześniej wspomniano, nie zawsze akurat tak musi być. Nasza książka może mieć wiele rozdziałów, ale może nie mieć podziału sekcji; niektóre rozdziały mogą mieć nagłówki, a inne nie. Musimy umieć zdefiniować te wszystkie szczegóły w naszym schemacie. Podobnie jak w definicji DTD, służy do tego odpowiedni mechanizm. Ale w przeciwieństwie do DTD stworzono intuicyjny zestaw atrybutów (zamiast nieco zagadkowych operatorów rekurencji typu ?, + czy *). W XML Schema wykorzystuje się atrybuty minOccurs i maxOccurs:
Jeśli atrybuty te nie zostaną określone, przyjmują domyślną wartość 1 — czyli jeden dozwolony element na definicję. Jeśli maksymalna wartość nie jest określona, wykorzystuje się symbol wieloznaczny. Konstrukcje te pozwalają w prosty sposób narzucić zawężenia związane z rekurencją (przykład 4.14).
Przykład 4.14. Schemat XML z definicjami elementów
Zdefiniowaliśmy jeden element główny, Ksiazka, typu TypKsiazka. Element ten zawiera trzy bezpośrednio potomne elementy: Tytul, Spis i Copyright. Z tych trzech dwa są prymitywnymi łańcuchami XML, a trzeci (Spis) to kolejny typ zdefiniowany przez użytkownika, TypSpis. Typ ten zawiera z kolei element potomny Rozdzial, który może pojawić się raz lub więcej razy, oraz element potomny PodzialSekcji, który wcale nie musi się pojawić. Element Rozdzial zawiera dwa elementy zagnieżdżone, Naglowek i Temat. Każdy z nich jest prymitywnym łańcuchem XML; Naglowek może pojawić się zero lub więcej razy, a Temat jeden lub więcej razy. Element PodzialSekcji może wystąpić zero lub więcej razy i jest elementem pustym. Teraz w schemacie zdefiniowane są już wszystkie elementy — pozostaje dodać atrybuty.
Definiowanie atrybutów Proces definiowania atrybutów jest o wiele prostszy od definiowania elementów, przede wszystkim dlatego, że nie trzeba zwracać uwagi na tyle spraw, co w przypadku elementów. Domyślnie atrybut nie musi występować, a zagnieżdżanie w ogóle atrybutów nie dotyczy. Choć istnieje wiele zaawansowanych konstrukcji, których można użyć do zawężania atrybutów, my przyjrzymy się tylko tym podstawowym, koniecznym do zawężenia omawianego dokumentu XML. W razie potrzeby zastosowania tych trudniejszych konstrukcji Czytelnik powinien zajrzeć do specyfikacji XML Schema.
Czego brakuje? Przy zawężaniu atrybutów na potrzeby dokumentu XML wchodzą w grę pewne pominięcia; wszystkie mają związek z różnymi definicjami przestrzeni nazw opisywanego dokumentu. Dokument XML, jak to już było powiedziane, musi zawierać definicje przestrzeni nazw w odniesieniu do schematu oraz definicje przestrzeni nazw „na własne potrzeby” — tj. odnoszące się do jego zawartości. Definicje te tworzy się za pomocą atrybutu xmlns:[PrzestrzenNazw] elementu głównego. Atrybuty te nie powinny być definiowane w schemacie. Próba zdefiniowania każdej dozwolonej przestrzeni nazw dałaby bardzo zagmatwany schemat. Położenie deklaracji przestrzeni nazw nie musi być stałe; można ją przemieszczać, o ile tylko pozostaje dostępna dla wszystkich odpowiednich elementów. Dlatego w schemacie zezwala się na pominięcie definicji atrybutów przestrzeni nazw.
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 112
XML Schema
113
Pora przypomnieć sobie wiadomości dotyczące definicji DTD. Aby zezwolić na deklaracje przestrzeni nazw w dokumencie XML, trzeba było wstawić taką definicję atrybutu jak poniżej:
Aby można było korzystać z DTD, wystarczyło określić przestrzeń nazw w danym dokumencie XML, bo definicje DTD nie posiadają „świadomości” istnienia przestrzeni nazw w XML. W schematach XML Schema sprawa jest trochę bardziej skomplikowana. Czytelnik wie już, że istnieją trzy różne atrybuty służące do określenia schematu dla dokumentu:
Tak, tekstu jest więcej niż w przypadku bliźniaczej konstrukcji w DTD, a jednak powyższy zapis jest prostszy w zrozumieniu, szczególnie dla nowych użytkowników standardu XML. Po wprowadzeniu tej zmiany nasz schemat jest już gotowy; zawarto w nim wszystkie zawężenia, które są zamieszczone w stworzonej wcześniej definicji DTD. Spójrzmy na przykład 4.16.
Przykład 4.16. Gotowy dokument XML Schema
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 115
116
Rozdział 4. Zawężanie danych XML
Co dalej? W niniejszym rozdziale Czytelnik poznał dwa sposoby zawężania dokumentów XML: zawężanie z wykorzystaniem definicji DTD oraz sposób nowszy — zawężanie z wykorzystaniem XML Schema. Czytelnik zapewne dostrzegł już, jak ważne jest zawężanie dokumentów, szczególnie jeśli mają one być wykorzystane przez aplikacje. Jeśli aplikacja nie rozpoznaje typu informacji zawartych w dokumencie, manipulacja i przetwarzanie takich danych stają się o wiele trudniejsze. W następnym rozdziale zostaną omówione klasy interfejsu SAX. Czytelnik dowie się, jak z programu w Javie uzyskać dostęp do definicji DTD oraz schematów. Do parsera dodamy przykładowy program, który zbudowaliśmy w rozdziale 3. — będzie on odczytywał zawężenia dokumentu i zwracał błędy, jeśli dokument XML nie będzie poprawny; będzie również wykorzystywał odwołania wsteczne dostępne w procesie sprawdzania poprawności składni.
C:\WINDOWS\Pulpit\Szymon\Java i XML\04-08.doc — strona 116
5 Sprawdzanie poprawności składni XML-a Po lekturze poprzednich rozdziałów Czytelnik potrafi już stworzyć dokument XML, przetworzyć go za pomocą klas SAX oraz zawęzić. W tym rozdziale zostanie omówione kolejne zagadnienie — sprawdzanie poprawności dokumentu XML za pomocą Javy. Bez takiej możliwości tworzenie aplikacji firma-firma oraz komunikacji międzyaplikacyjnej staje się o wiele trudniejsze. Zawężenie zwiększa przenośność danych; natomiast sprawdzanie poprawności — spójność. Innymi słowy, możliwość zawężenia dokumentu nie zda się na wiele, jeśli stworzonych zawężeń nie przeforsujemy w aplikacji XML. W tym rozdziale przedstawione zostaną klasy i interfejsy SAX służące do sprawdzania poprawności dokumentów XML względem ich zawężeń. Czytelnik dowie się, jak ustawić cechy i właściwości parsera zgodnego z SAX, aby możliwe było proste sprawdzanie poprawności, obsługa przestrzeni nazw i wykonywanie innych czynności. Ponadto szczegółowo omówione zostaną błędy i ostrzeżenia zgłaszane przez parsery sprawdzające poprawność.
Konfiguracja parsera W obliczu bogactwa specyfikacji i technologii autorstwa konsorcjum W3C związanych z językiem XML, dodanie obsługi nowej cechy lub właściwości nie jest proste. W wielu implementacjach parserów dodano własne rozszerzenia lub metody kosztem przenośności kodu. W pakietach tych mógł zostać zaimplementowany interfejs XMLReader, ale metody do ustawiania sprawdzania poprawności, obsługi przestrzeni nazw i innych kluczowych cech są odmienne w różnych implementacjach parserów. W związku z tym w interfejsie SAX 2.0 zdefiniowano standardowy mechanizm do ustawiania istotnych właściwości i cech parsera. Umożliwia on dodawanie nowych właściwości i cech w miarę przyjmowania ich przez W3C, bez konieczności stosowania własnych metod lub rozszerzeń.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 117
118
Rozdział 5. Sprawdzanie poprawności składni XML-a
Ustawianie właściwości i cech Na szczęście dla nas, interfejs SAX 2.0 wyposażono w metody służące do ustawiania właściwości i cech interfejsu XMLReader. Oznacza to, że w celu zażądania sprawdzania poprawności, ustawienia separatora przestrzeni nazw czy obsługi innych cech nie trzeba zmieniać zbyt wiele w istniejącym kodzie. Odpowiednie metody przedstawione są w tabeli 5.1.
Tabela 5.1. Metody do obsługi właściwości i cech parsera Metoda
Zwraca
setProperty() void setFeature()
void
Parametry
Składnia
String IDwlasciwosci, Object wartosc String IDcechy, boolean stan
W każdej z powyższych metod identyfikatorem ID określonej właściwości lub cechy jest identyfikator URI. Lista najważniejszych właściwości i cech jest zamieszczona w dodatku B. Producent parsera XML powinien udostępnić dodatkową dokumentację informujacą o obsługiwanych cechach i właściwościach. Pamiętajmy jednak, że identyfikatory te, podobnie jak identyfikatory URI przestrzeni nazw, służą wyłącznie do kojarzenia odpowiednich cech. Dobry parser umożliwi korzystanie z nich bez posiadania połączenia z siecią. W tym sensie identyfikatory URI można postrzegać jako proste stałe, które akurat mają format URI. Po skorzystaniu z takiej metody następuje lokalne przetworzenie identyfikatora, często jako stałej reprezentującej odpowiednią czynność, którą należy podjąć. W kontekście konfiguracji parsera właściwość wymaga obecności obiektu, z którego można skorzystać. Na przykład w celu obsługi leksykalnej jako wartość właściwości można dostarczyć klasę LexicalHandler. Cecha natomiast ma postać znacznika wykorzystywanego przez parser w celu określenia, czy ma nastąpić przetwarzanie określonego typu. Typowe cechy to sprawdzanie poprawności, obsługa przestrzeni nazw i dołączanie encji zewnętrznych. Najwygodniejsza własność powyższych metod polega na tym, że umożliwiają one łatwe dodawanie i zmianę różnych cech. Nowe lub zaktualizowane właściwości wymagają obsługi ze strony parsera, ale dostęp do nich uzyskuje się wciąż za pomocą tych samych metod — konieczne jest tylko zdefiniowanie nowego identyfikatora URI. Bez względu na złożoność nowych koncepcji związanych z XML, ich implementacja w parserach przebiega bezproblemowo, właśnie dzięki tym metodom.
Włączanie sprawdzania poprawności Czytelnik dowiedział się już, jak ustawiać cechy i właściwości, ale nie poznał jeszcze samych cech i właściwości. W tym rozdziale interesujemy się przede wszystkim sprawdzaniem poprawności w czasie przetwarzania. Aby zilustrować, jak ważne są wspomniane wyżej metody, trzeba odwołać się do historii. W interfejsie SAX 1.0 implementacje parserów musiały udostępniać własne rozwiązania do obsługi przetwarzania ze sprawdzaniem poprawności lub bez. Nie było możliwości włączenia lub wyłączenia sprawdzania poprawności w sposób standardowy, więc najprostszy spo-
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 118
Konfiguracja parsera
119
sób polegał na dostarczeniu dwóch niezależnych klas przetwarzających. Na przykład, aby wykonać przetwarzanie bez sprawdzania poprawności we wczesnych wersjach parsera Project X firmy Sun, trzeba było skorzystać z przedstawionego poniżej fragmentu kodu.
Przykład 5.1. Uruchamianie parsera nie sprawdzającego poprawności w interfejsie SAX 1.0 try { // Rejestrujemy parser w SAX Parser parser = ParserFactory.makeParser( "com.sun.xml.parser.Parser"); // Przetwarzamy dokument parser.parse(uri); } catch (Exception e) { e.printStackTrace(); }
Ponieważ nie istniał standardowy mechanizm włączania sprawdzania poprawności, trzeba było załadować inną klasę; ta nowa klasa jest niemal identyczną implementacją interfejsu Parser w SAX 1.0, ale wykonującą sprawdzanie poprawności. Kod pozwalający na użycie parsera jest niemal identyczny (przykład 5.2), różnica jest tylko w klasie załadowanej w celu przetwarzania.
Włączając lub wyłączając sprawdzanie poprawności trzeba więc było zmieniać i kompilować kod. Ponadto wynikał z tego dodatkowy problem z przetwarzaniem. Standardowe środowisko programistyczne wykorzystuje kod, który sprawdza poprawność danych XML wytwarzanych przez aplikację. To sprawdzanie poprawności, choć obniża wydajność, zapewnia, że aplikacja tworzy zawsze poprawny kod XML albo że otrzymuje poprawne dokumenty XML na wejściu. Często takie zawężenia po intensywnym testowaniu można usunąć, dzięki czemu odzyskuje się wysoką wydajność działania aplikacji. Pozbawienie parsera możliwości sprawdzania poprawności jest uzasadnione, ponieważ dogłębne testy potwierdziły poprawność tworzonego dokumentu XML; ale zmiana ta wymaga modyfikacji i rekompilacji kodu. Wydaje się to być sprawą banalną, ale wiele firm nie pozwala na wdrożenie do produkcji kodu, który był modyfikowany później niż przed jakimś określonym czasem — często kilka dni, a nawet tygodni. Taka niewielka zmiana może więc spowodować dodatkowy cykl testowy — niejednokrotnie niepotrzebny, a dodatkowo wydłużający czas wdrożenie aplikacji. Ale przecież nazwę parsera można pobrać z pliku właściwości (zostało to już powiedziane w rozdziale 2. przy okazji opisywania aspektów przenośności aplikacji XML). Tak, ale zmiana całej implementacji parsera tuż przed wdrażaniem aplikacji do produkcji to zmiana poważna, która po-
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 119
120
Rozdział 5. Sprawdzanie poprawności składni XML-a
winna być należycie przetestowana. Jeśli porównamy to ze zmianą wartości zestawu cech (zakładając, że wartość do ustawiania tej cechy także pobierana jest z pliku właściwości), to łatwo zgadnąć, które rozwiązanie jest lepsze. Z tych wszystkich powodów w interfejsie SAX 2.0 do XMLReader dodano omawiane metody. Dzięki nim włączenie sprawdzania poprawności polega na wykorzystaniu odpowiedniego identyfikatora URI: http://xml.org/sax/features/validation. Moglibyśmy zażądać również przetwarzania encji zewnętrznych i przestrzeni nazw, ale tymczasem „włączymy” tylko sprawdzanie poprawności (przykład 5.3).
Po tych prostych zmianach możemy już zmodyfikować nasz przykładowy plik XML tak, by znów zawierał odwołanie do definicji DTD i zewnętrzną encję (zostały one opatrzone komentarzami w poprzednim rozdziale). Java i XML
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 120
Wynik sprawdzania poprawności
121
... &OReillyCopyright;
Upewniamy się, czy w podanym tutaj katalogu mamy stworzoną wcześniej definicję DTD. Zanim uruchomimy przykład, musimy połączyć się z Internetem — pamiętajmy, że encje muszą zostać „przetłumaczone”. W naszym przykładowym pliku mamy taką encję — OReillyCopyright. W definicji DTD odwołujemy się do identyfikatora URI http://www.oreilly.com/catalog/javaxml/ docs/copyright.xml. Jeśli w czasie sprawdzania poprawności identyfikator URI nie jest dostępny, pojawią się błędy przetwarzania. Jeśli nie mamy dostępu do Internetu albo nie chcemy z niego korzystać, możemy odwołanie sieciowe zastąpić odwołaniem do pliku lokalnego. Na przykład tworzymy jednowierszowy plik jak w przykładzie 5.4.
Przykład 5.4. Lokalny plik z opisem praw autorskich Przykładowy współdzielony plik z opisem praw autorskich.
Plik ten zachowujemy w katalogu dostępnym z poziomu programu parsera i zamieniamy deklarację encji w DTD na ścieżkę do tego pliku:
W tym przykładzie plik tekstowy zachowany jest pod nazwą copyright.txt w katalogu encje/. Po takiej zmianie możemy już uruchomić nasz program na przykładowym pliku XML.
Wynik sprawdzania poprawności Sprawdzamy, czy pliki XML, DTD, plik z prawami autorskimi (o ile taki stworzyliśmy) oraz odpowiednie klasy Javy znajdują się w jednym miejscu, i uruchamiamy przykładowy program. Wynik może być zaskakujący (przykład 5.5).
Przykład 5.5. Wynik działania programu SAXParserDemo D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... **Przetwarzanie błędu** Wiersz: 11 URI: file:/D:/prod/JavaXML/contents/contents.xml Komunikat: Document root element "JavaXML:Ksiazka", must match DOCTYPE root "JavaXML:Ksiazka".
Ten dość enigmatyczny komunikat wynika z istnienia poważnego problemu związanego z używaniem definicji DTD i przestrzeni nazw. Z informacji wynikałoby, że element główny określony w deklaracji DOCTYPE (JavaXML:Ksiazka) jest różny od elementu głównego samego dokumentu. Ale przecież elementy te są identyczne, prawda? Otóż nie! Domyślnie SAX 2.0 wymaga
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 121
122
Rozdział 5. Sprawdzanie poprawności składni XML-a
od parserów włączenia przetwarzania przestrzeni nazw, chyba że jawnie wyłączymy tę cechę1. My ustawienia domyślnego nie zmienialiśmy, a więc nasza implementacja interfejsu XMLReader jest „świadoma” przestrzeni nazw. Nieoczekiwany rezultat wynika stąd, że nasz element główny jest postrzegany (przez parser) jako Ksiazka z przedrostkiem przestrzeni nazw JavaXML. Ale pamiętajmy, że XML 1.0 oraz definicje DTD nie rozróżniały pomiędzy przedrostkiem a nazwą elementu, a więc element główny DTD oczekuje nazwy JavaXML:Ksiazka. Kiedy znajduje Ksiazka, zgłasza błąd. Jedynym sposobem obejścia tej nieco uciążliwej cechy SAX-a jest wyłączenie „świadomości” przestrzeni nazw w dokumentach, w których sprawdzanie poprawności odbywa się za pomocą DTD. Dodajemy następujący fragment kodu: try { // Stwórz egzemplarz parsera XMLReader parser = XMLReaderFactory.createXMLReader( "org.apache.xerces.parsers.SAXParser"); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Włącz sprawdzanie poprawności parser.setFeature("http://xml.org/sax/features/validation", true); // Wyłącz "świadomość" przestrzeni nazw parser.setFeature("http://xml.org/sax/features/namespaces", false); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Po tej zmianie wszystkie nazwy traktowane są jako zawierające zarówno przedrostek przestrzeni nazw, jak i nazwę lokalną elementu. Do korzystania z przestrzeni nazw powrócimy w dalszej części rozdziału. Tymczasem spróbujmy ponownie uruchomić program z zaimplementowanymi zmianami. Powinniśmy uzyskać wynik widoczny w przykładzie 5.6.
Przykład 5.6. Wynik działania programu SAXParserDemo po wyłączeniu obsługi przestrzeni nazw D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: contents.xml
1
Domyślne włączanie obsługi przestrzeni nazw miało miejsce w czasie pisania tej książki, jednakże niektórzy programiści związani z XML-em (np. zespół programistów Apache Xerces) postulowali wyłączenie tej funkcji. Jeśli Czytelnik nie otrzymuje wspomnianego wyżej komunikatu, to być może domyślne ustawienie zostało już zmienione. (W czasie tłumaczenia książki, w grudniu 2000 r., obsługa przestrzeni nazw była już domyślnie wyłączona — przyp. tłum.).
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 122
Wynik sprawdzania poprawności
123
* setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... startElement: nie posiada skojarzonej przestrzeni nazw Atrybut: =http://www.oreilly.com/catalog/javaxml/ ignorableWhitespace: [ ] startElement: nie posiada skojarzonej przestrzeni nazw znaki: Java i XML endElement: ...
Wynik może nas nieco rozczarować. Nie różni się niczym od wyniku uzyskanego w przypadku parsera nie sprawdzającego poprawności (zob. rozdział 3.)! To dlatego, że nasz dokument jest poprawny i poprawnie sformatowany — parser sprawdzający poprawność nie ma o czym komunikować. To ważne — zachowanie parsera w przypadku sprawdzania poprawnego dokumentu niemal nie różni się od zachowania parsera nie sprawdzającego poprawności. Jeśli to wydaje się dziwne, to należy przypomnieć, że sprawdzanie poprawności ma na celu zagwarantowanie, że dokument nie łamie zdefiniowanych wcześniej zasad. Jeśli wszystkie reguły są przestrzegane, aplikacja użyje dokumentu XML zgodnie z jego przeznaczeniem. Tylko kiedy nastąpi złamanie zasad, parser sprawdzający poprawność wkracza do akcji — poniżej zostaną omówione takie przypadki. Wcześniej jednak trzeba zauważyć, że mimo wszystko powyższy wynik nieco różni się od poprzednich. Wcześniej wszystkie znaki białe pomiędzy elementami w dokumencie XML zgłaszane były poprzez wywołanie metody characters() w implementacji ContentHandler. W dokumencie XML, którego poprawność nie jest sprawdzana, parser ma możliwość zgłoszenia białych znaków albo przez tę metodę, albo przez ignorableWhitespace().Wynika to z faktu, że parser nie może zakładać określonego przeznaczenia obecności białych znaków pomiędzy elementami bez definicji DTD zawężającej XML. Kiedy w programie włączamy sprawdzanie poprawności, widzimy, że wszystkie znaki białe zgłaszane są za pomocą ignorableWhitespace(). Przy sprawdzaniu poprawności wszystkie białe znaki są ignorowane i traktowane tak, jakby ich nie było — chyba że zostanie to inaczej ujęte w definicji DTD. Dzięki temu parser potrafi określić, czy zawartość elementu XML zgodna jest z definicją DTD bez analizowania białych znaków otaczających tę zawartość. Innymi słowy, parser potraktuje taki fragment XML: Cześć!
dokładnie tak samo jak: Cześć!
Drugi fragment jest co prawda mniej miły dla ludzkiego oka, ale zgodny jest z tymi samymi zawężeniami co fragment pierwszy i powinien być identycznie traktowany w czasie sprawdzania poprawności. Brak wcięć nie może wpłynąć na działanie aplikacji korzystających z danych XML. Gdyby białe znaki tworzące wcięcia zgłaszane były poprzez wywołanie characters(), aplikacja monitorująca to wywołanie uznałaby, że dokumenty nie są identyczne.
Ostrzeżenia W wyniku żądania sprawdzania poprawności nie mogą się pojawić niemal żadne ostrzeżenia. Jakiekolwiek dane XML niezgodne z odpowiednią definicją DTD są postrzegane jako błąd. Niepo-
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 123
124
Rozdział 5. Sprawdzanie poprawności składni XML-a
prawność dokumentu została uznana przez W3C za rzecz na tyle ważną, że zawsze zgłaszany jest wtedy błąd. Dlatego trudno jest, szczególnie za pomocą parsera SAX 2.0, wywołać ostrzeżenie. Jednak istnieją parsery SAX 1.0, w których jest to możliwe. Na przykład parser Project X firmy Sun zawiera obecnie klasę służącą do sprawdzania poprawności XML-a w czasie przetwarzania i klasę nie wykonującą takiego sprawdzania. Jest to rozwiązanie podobne do omawianego wcześniej. Jeśli parser sprawdzający poprawność przetwarza dokument nie deklarujący definicji DTD w sposób jawny, generowane jest ostrzeżenie. Dotyczy to tylko niektórych implementacji parserów SAX 1.0, dlatego w niniejszej książce nie zostanie przedstawiony kod powodujący zgłoszenie ostrzeżenia. Czytelnik może natomiast obejrzeć wynik działania takiego ostrzeżenia (przykład 5.7) — podobny do tych opisywanych w rozdziale 3.
Przykład 5.7. Program SAXParserDemo zgłaszający ostrzeżenie D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... **Przetwarzanie ostrzeżenia** Wiersz: 6 URI: file:/D:/prod/JavaXML/contents/contents.xml Komunikat: Valid documents must have a Java i XML
Taka zmiana, choć nie wpływa na poprawność danych XML, nie jest zgodna z narzuconymi regułami — atrybut dataPublikacji nie został zadeklarowany w definicji DTD i w czasie przetwarzania zgłoszony zostanie błąd (przykład 5.8).
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 124
Wynik sprawdzania poprawności
125
Przykład 5.8. Błąd zgłaszany przez program SAXParserDemo D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml Przetwarzanie pliku XML: contents.xml * setDocumentLocator() została wywołana Rozpoczyna się przetwarzanie... **Przetwarzanie błędu** Wiersz: 14 URI: file:///mnt/teksty_zdalny/biura/helion/javaxml/cwiczenia/contents.xml Komunikat: Attribute "dataPublikacji" must be declared for element type "JavaXML:Ksiazka". Błąd w przetwarzaniu: Napotkano błąd
Analizator przesyła błąd do implementacji ErrorHandler, która zgłasza błąd — w tym przypadku mówiący, że atrybut nie został zadeklarowany dla zamykającego go elementu. Oczywiście, poprawność dokumentu można zburzyć na wiele sposobów — to tylko jeden z nich; zawsze jednak zgłoszony zostanie ten sam błąd, warto więc poeksperymentować. Należy po prostu pamiętać, że jakiekolwiek pogwałcenie DTD spowoduje zgłoszenie błędu. Dotyczy to przypadków, w których zawartość dokumentu nie jest poprawna, elementy nie są prawidłowo zagnieżdżone, atrybuty znajdują się nie tam, gdzie trzeba, lub są niepoprawne itd.
Błędy krytyczne Co ciekawe, dokument niezgodny z zawężeniami DTD nigdy nie spowoduje wygenerowania błędu krytycznego. W czasie przetwarzania dokumentu nie mogą zajść takie warunki, które spowodują przerwanie tego procesu. Może nam się wydawać, że kontynuacja przetwarzania niepoprawnego dokumentu kłóci się z celowością sprawdzania poprawności. Należy jednak pamiętać o tym, że w większości przypadków dokumenty XML są generowane przez aplikacje. Innymi słowy, aplikacja otrzymuje dane wejściowe XML z innego programu lub podprogramu. Jeśli dane te są niepoprawne, aplikacja usiłująca z nich skorzystać powinna zgłosić aplikacji klienta błąd, a nie przerywać przetwarzanie. Co więcej, przetwarzanie to niejednokrotnie powinno być kontynuowane, aby możliwe było zakończenie procesu w sposób elegancki — w ten sposób aplikacja jest w stanie precyzyjnie zakomunikować, jakie błędy wystąpiły. Dokumenty błędnie sformatowane spowodują zatrzymanie przetwarzania, a dokumenty niepoprawne wskażą, że albo nastąpił błąd, który można poprawić, albo zaszła sytuacja, o której klient powinien wiedzieć, np. wprowadzono niepoprawne dane. Zastanówmy się, jak trudno byłoby obsługiwać edytor XML lub środowisko programistyczne IDE, jeśli za każdym razem, gdy nie do końca przestrzegaliśmy zawężeń DTD, edytor kończyłby działanie z błędem krytycznym, lub też odmawiałby w ogóle przetwarzania dokumentu; a przecież niektóre edytory mogłyby takie błędy poprawiać za nas! Dlatego właśnie niepoprawne dokumenty powodują zgłaszanie ostrzeżeń i błędów, ale nigdy błędów krytycznych. Jedyny błąd krytyczny, na jaki możemy się natknąć przy używaniu definicji DTD w dokumentach, których poprawność nie jest sprawdzana, to błąd składniowy w wykorzystywanej definicji DTD. To nie powinno Czytelnika dziwić, jako że błędy składniowe w dokumencie XML także powodują błędy krytyczne. Ma tutaj miejsce to samo rozumowanie — nie jest możliwe dalsze przetwarzanie lub sprawdzanie poprawności, jeśli nie można określić zawężeń (a tak jest, gdy składnia DTD nie jest poprawna). Powinniśmy zdawać sobie sprawę, że to nie to samo co błąd generowany, gdy mamy do czynienia z niepoprawnie sformatowanym dokumentem XML — główna różnica polega na
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 125
126
Rozdział 5. Sprawdzanie poprawności składni XML-a
tym, że definicja DTD nigdy nie jest postrzegana jako „poprawnie sformatowana”, bo przecież nie jest to tak naprawdę dokument XML. Ale rezultat wystąpienia błędów składniowych w DTD jest taki sam, jak rezultat przetwarzania niepoprawnie sformatowanych danych XML.
Interfejs DTDHandler Ostatnia ważna procedura obsługi udostępniania przez SAX rejestruje metody wywołań wstecznych w czasie procesu czytania i przetwarzania definicji DTD dokumentu. W interfejsie tym nie są zdefiniowane zdarzenia, jakie występują w czasie sprawdzania poprawności, a jedynie te, które występują w czasie czytania DTD. W podrozdziale dotyczącym pułapek zostaną omówione problemy wywoływane przez to zróżnicowanie. Ta procedura obsługi zachowuje się w taki sam sposób jak interfejsy ContentHandler i ErrorHandler, omawiane w rozdziale 3. — definiowane są dwie metody wywoływane w czasie przetwarzania. Sprawdzanie poprawności dokumentu XML jest bardzo ważne, ale zdarzenia związane z odczytywaniem dokumentu DTD — już mniej. Są tylko dwie metody, obie nie są wykorzystywane tak często, a więc prawdopodobnie rzadko Czytelnik będzie musiał korzystać z interfejsu DTDHandler (chyba że pisząc edytor lub IDE dla XML-a będzie chciał tworzyć lub przetwarzać dokumenty DTD w celu sprawdzenia poprawności zapisu i składni). Dlatego pokrótce omówimy tutaj te dwie metody udostępniane przez SAX, ale nie będziemy poświęcali im zbyt dużo czasu. O opcjonalnej procedurze obsługi SAX, służącej do pobierania innych informacji z DTD, można przeczytać w części DeclHandler, w dodatku A (pakiet org.xml.sax.ext).
Nieprzetwarzane deklaracje encji Pierwsze wywołanie wsteczne, unparsedEntityDecl(), wywoływane jest, kiedy deklaracja encji w DTD mówi, iż encja ta ma nie być przetwarzana przez parser XML. Choć nie omawialiśmy jeszcze przykładu takiego zachowania, nieprzetwarzane encje są dość typowe w dokumentach XML, w których istnieją odwołania do plików graficznych lub innych danych binarnych (często plików multimedialnych). Metoda ta pobiera nazwę encji, identyfikatory, publiczny i systemowy, oraz nazwę notacyjną encji. Nazwy notacyjne w XML-u nie były jeszcze omawiane. Spójrzmy na przykład dokumentu XML odwołującego się do pliku graficznego (np. do logo firmy) — przykład 5.9.
Przykład 5.9. Dokument XML z nieprzetwarzaną encją &LogoFirmy;
Podczas przetwarzania parser stara się przetłumaczyć wszystkie encje i wstawić w ich miejsce przetworzoną wartość. Jednakże parser nie potrafi przetwarzać plików graficznych i powinien pozostawić dane binarne w postaci nieprzetworzonej. Można go o tym „poinformować” poprzez następującą definicję typu dokumentu:
Słowo kluczowe NDATA powoduje, że parser XML nie przetworzy tej encji. Gdyby ta definicja DTD była przetwarzana za pośrednictwem zarejestrowanej implementacji DTDHandler, informacja w deklaracji encji zostałaby przekazana do wywołania. Inną ważną sprawą jest tutaj fakt, że wywołanie następuje w miejscu deklaracji w DTD, a nie w chwili, gdy deklaracja ta jest przetwarzana w dokumencie XML. Oznacza to, że nawet jeśli encja nie znajduje się w dokumencie
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 126
Interfejs DTDHandler
127
XML, to i tak wywołanie nastąpi. To ma sens — przecież wywołanie to stanowi część interfejsu DTDHandler, a nie ContentHandler.
Deklaracje notacji Deklaracje notacji zawsze skojarzone są z nieprzetwarzanymi deklaracjami i stanowią przedmiot działania drugiej metody wchodzącej w skład DTDHandler. Ostatnia część powyższej deklaracji nieprzetwarzanej encji to słowo „gif”. Słowo to określa typ nieprzetwarzanej encji i musi stanowić odwołanie do typu zdefiniowanego w innym miejscu DTD, za pośrednictwem konstrukcji NOTATION. W ten sposób parser poznaje identyfikator URI dla danego typu — często jest to odwołanie publiczne dla typów binarnych. Przypomina to odwoływanie się do DTD z dokumentu XML, ponieważ specyficzne dane (w tym przypadku plik GIF) są kojarzone z publicznym identyfikatorem lub URI. Przetwarzanie definicji NOTATION i deklaracji nieprzetwarzanej encji powoduje w rezultacie, że parser XML nie przetwarza danych binarnych. A o to właśnie chodziło.
Wystąpienia takich deklaracji zgłaszane są zarejestrowanej procedurze obsługi poprzez wywołanie notationDecl(). Po wywołaniu metoda ta otrzymuje deklarację notacji, identyfikator systemowy i dostępny identyfikator publiczny. Podobnie jak w przypadku nieprzetwarzanych encji, metoda ta wywoływana jest podczas odczytywania definicji DTD, a nie samego dokumentu XML.
Rejestrowanie procedury obsługi Rejestrowanie implementacji DTDHandler w naszym parserze XML nie różni się niczym od rejestrowania procedur obsługi błędów i zawartości. Egzemplarz klasy przekazywany jest metodzie setDTDHandler(), a parser rejestruje odpowiednie zdarzenia związane z metodami SAX w tej klasie: import java.io.IOException; import import import import import import import import import
I cała reszta... Czytelnik może odnieść wrażenie, że interfejs procedury obsługi DTD został omówiony dość pobieżnie, szczególnie w porównaniu z innymi procedurami. Jednakże aplikacje wykorzystujące XML rzadko muszą rejestrować tego typu procedury. W systemach XML-owych często sprawdza się poprawność dokumentu, ale szczegółowe informacje odnośnie nieprzetworzonych encji rzadko przydają się na wyższym poziomie wykorzystania XML-a. Dlatego właśnie przejdziemy do innych szczegółowych informacji, bardziej przydatnych przy tworzeniu aplikacji korzystających z XML-a. Czytelnik mógłby oczekiwać, że teraz zostanie omówione sprawdzanie poprawności za pomocą XML Schema; schematy są jednakże w coraz większym stopniu wykorzystywane nie tylko do sprawdzania poprawności, ale także do zwykłej reprezentacji danych. Dlatego właśnie omawianie schematów z poziomu Javy zostało przeniesione do rozdziału 14. — wtedy Czytelnik będzie już znał XSL i różnego rodzaju praktyczne sposoby użycia XML-a w aplikacjach. Interfejsy SAX do obsługi schematów są o wiele potężniejsze niż DTDHandler i będą wymagały od Czytelnika większej wiedzy z zakresu XML-a. W niniejszym rozdziale omawianie sprawdzania poprawności danych XML zostanie zakończone przedstawieniem typowych problemów, na jakie można się natknąć w czasie tego procesu.
Uwaga! Pułapka! Podczas sprawdzania poprawności dokumentu XML na programistę czyha wiele pułapek. Poniżej zostaną omówione problemy, z którymi najczęściej mają do czynienia początkujący programiści XML, a także te, których rozwiązanie wcale nie jest łatwe. Warto dobrze się zapoznać z poniższym materiałem, bo opisywane trudności spędziły sen z oczu już niejednemu programiście.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 128
Uwaga! Pułapka!
129
Obsługa sprawdzania poprawności a obsługa definicji DTD Jednym z typowych nieporozumień odnośnie SAX-a i sprawdzania poprawności jest błędne założenie, że sprawdzanie poprawności dokumentu XML następowało automatycznie po zarejestrowaniu implementacji DTDHandler w parserze XML. Często zdarza się, że programista implementuje ten interfejs i rejestruje go w parserze, ale nie włącza funkcji sprawdzania poprawności parsera. Ten błąd wynika z zagubienia różnicy pomiędzy obsługą samego DTD a wykorzystaniem DTD do sprawdzania poprawności. W tym przypadku definicja DTD zostanie przetworzona, wystąpią też wszystkie wywołania związane z DTD (o ile są konieczne), jednakże nie nastąpi sprawdzenie poprawności dokumentu XML — zostanie on wyłącznie przetworzony. Należy pamiętać, że wynik sprawdzania poprawności poprawnego dokumentu XML wygląda niemal tak samo jak wynik przetwarzania dokumentu bez sprawdzania jego poprawności; zawsze należy śledzić, kiedy występuje sprawdzanie poprawności — w ten sposób można uniknąć błędów w aplikacji: try { // Stwórz egzemplarz parsera XMLReader parser = XMLReaderFactory.createXMLReader( "org.apache.xerces.parsers.SAXParser"); // Zarejestruj procedurę obsługi zawartości parser.setContentHandler(contentHandler); // Zarejestruj procedurę obsługi błędów parser.setErrorHandler(errorHandler); // Z tego nie wynika włączenie sprawdzania poprawności parser.setDTDHandler(dtdHandler); // Włącz sprawdzanie poprawności parser.setFeature("http://xml.org/sax/features/validation", true); // Wyłącz "świadomość" przestrzeni nazw parser.setFeature("http://xml.org/sax/features/namespaces", false); // Przetwórz dokument parser.parse(uri); } catch (IOException e) { System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); }
Trzeba koniecznie pamiętać o tym, że zarejestrowanie DTD nie ma nic wspólnego z procesem sprawdzania poprawności; parser z zarejestrowaną implementacją DTDHandler nie zawsze sprawdza poprawność XML-a; zaś parser bez zarejestrowanej tej procedury może sprawdzić poprawność. To od cech (w implementacjach SAX 2.0) lub klasy (w implementacjach sprzed SAX 2.0) parsera XML zależy, czy sprawdzanie poprawności będzie się odbywało — a nie od zarejestrowanej procedury obsługi. Pamiętajmy o tym — implementacja DTDHandler do sprawdzania poprawności nie jest potrzebna; potrzebne jest natomiast ustawienie odpowiednich cech parsera lub użycie innej klasy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 129
130
Rozdział 5. Sprawdzanie poprawności składni XML-a
W czasie tworzenia priorytetem jest poprawność; w gotowej aplikacji liczy się szybkość Programista powinien wiedzieć, kiedy należy korzystać ze sprawdzania poprawności, a kiedy nie. Jednym z największych problemów związanych ze sprawdzaniem poprawności jest często niezwykle powolne działanie aplikacji produkcyjnej. Programista analizuje przyczyny takiego spadku wydajności pracy aplikacji, nie pamiętając o tym, że w środowisku produkcyjnym pozostawił włączone sprawdzanie poprawności. Zazwyczaj sprawdzanie poprawności powinno mieć miejsce tylko w czasie testowania lub w czasie działania procesu kontrolowania jakości (ang. Quality Assurance). Jeśli część aplikacji generuje lub modyfikuje pliki XML, sprawdzanie poprawności wynikowego dokumentu XML powinno odbywać się właśnie w czasie testowania. To proces kosztowny — parser przetwarza o wiele więcej danych i musi podjąć więcej decyzji dotyczących dokumentu. Po sprawdzeniu poprawności wyników generowanych przez aplikację zazwyczaj dobrze jest wyłączyć sprawdzanie poprawności. Aplikacja produkcyjna zyskuje dzięki temu na wydajności, a jeśli testowanie przeprowadzono zgodnie z zasadami tej sztuki, to nie powinny pojawić się żadne problemy. Jedyną sytuacją, w której nie należy wyłączać sprawdzania poprawności w aplikacji produkcyjnej, jest ta, w której w tworzeniu danych XML bierze aktywny udział klient — np. w narzędziach XML IDE lub GUI, albo ta, w której dane XML uzyskuje się od innych aplikacji — np. w e-biznesie. Wynika to stąd, że dane wejściowe są poza naszą kontrolą i mogą być niepoprawne — sprawdzanie poprawności jest więc zalecane, a nawet oczekiwane przez klienta. Jednak w większości przypadków gotowa aplikacja może pracować bez sprawdzania poprawności.
Co dalej? Czytelnik powinien już dobrze znać zasady tworzenia dokumentów XML oraz sposoby ich zawężania. W niniejszym rozdziale zostały omówione wszystkie najważniejsze aspekty korzystania z interfejsów i klas SAX. Czytelnik poznał już cykle przetwarzania i sprawdzania poprawności oraz dostępne wywołania. Powinien potrafić skonfigurować i użyć parsera XML oraz zarejestrować w nim różne procedury obsługi SAX. W następnym rozdziale zostanie przedstawiona kolejna specyfikacja — XSL, czyli rozszerzalny język arkuszy stylów. Omówienie przekształcania za pomocą XSL to wstęp do dyskusji o obiektowym modelu dokumentu i mechanizmach publikacyjnych, a także do bardziej dogłębnego poznawania zagadnień programowania aplikacji.
C:\WINDOWS\Pulpit\Szymon\Java i XML\05-08.doc — strona 130
6 Przekształcanie kodu XML Jeśli Czytelnik bierze udział w tworzeniu systemów wewnętrznych lub projektuje systemy, to już na tym etapie czytania powinien doceniać możliwości XML-a. Język reprezentujący dane w sposób niezależny od producenta, prosty w przetwarzaniu, przenośny i obsługiwany z poziomu Javy — czy to właśnie ten format danych rozwiąże wszystkie problemy ze współpracą pomiędzy aplikacjami? Możliwe. Jeśli jednak Czytelnik jest programistą zawartości, budowniczym aplikacji, lub też zaangażowany jest w prezentację aplikacji, może odczuwać zniecierpliwienie — po przeczytaniu pięciu rozdziałów nie wie jeszcze, jak generować zawartość dla klientów z wykorzystaniem XML-a, jak separować prezentacje od danych. W tym rozdziale zostanie rozpoczęte omawianie przekształcania danych XML. Jest to obszerny temat obszerny. Najpierw zostaną przedstawione cele i sposoby przekształcania danych XML. W następnym rozdziale przedstawiony zostanie procesor Java XSLT, który pobiera dokument XML i generuje różne rodzaje zawartości — często różniące się w sposób zasadniczy od pierwotnych danych XML. Czytelnik dowie się również, jak w tym procesie wykorzystywany jest obiektowy model dokumentu (DOM). Przekształcanie XML-a jest tak obszernym tematem, że nie sposób omówić tutaj wszystkich dostępnych konstrukcji składniowych. Referencyjne podejście do przekształcania przedstawione jest w książce XML Pocket Reference Roberta Ecksteina (O'Reilly & Associates). Przekształcaniu XML-a poświęcono również sporą część serwisu WWW grupy W3C: http://www.w3.org/Style/XSL/.
Cel Przed przystąpieniem do omawiania procesu przekształcania danych XML, warto powiedzieć kilka słów o samym celu przekształcania. Podobnie jak w przypadku omawiania istoty zawężania danych XML, dobre zrozumienie przekształcania umożliwi poprawne wykonywanie tego procesu. Przekształcanie nie jest rozwiązaniem ostatecznym w zakresie prezentacji danych i nie wszędzie powinno być wykorzystywane; jednak są sytuacje, w których przekształcanie oszczędzi wiele godzin naszej pracy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 131
132
Rozdział 6. Przekształcanie kodu XML
W następnych dwóch rozdziałach skoncentrujemy się na sposobie, w jaki dane XML mogą zostać przedstawione różnym klientom. Jednakże niekoniecznie oznacza to, że będziemy zajmować się prezentacją danych samemu użytkownikowi... właściwie o tym nie powiemy prawie nic! Jeśli Czytelnik czuje się teraz nieco zagubiony, to nic nie szkodzi. Najpierw przypomnijmy sobie, co rozumiemy pod pojęciem klienta.
Klient klienta mojego klienta Najczęściej klienta lub aplikację klienta opisuje się jako użytkownika końcowego. Ale pogląd, że klientem aplikacji jest po prostu człowiek siedzący przed przeglądarką WWW lub interfejsem GUI jest nieścisły. Powiedzieliśmy już o tym, jak różne mogą być klienty korzystające z aplikacji; teraz rozszerzymy tę definicję jeszcze bardziej. Weźmy na przykład mechanizm bazy danych. Dane w bazie rzadko wyświetlane są bezpośrednio użytkownikowi, bez wcześniejszego sformatowania ich przez inny program. Czy możemy więc powiedzieć, że z bazy nie korzystają żadne klienty? A co z aplikacją przetwarzającą dane o pogodzie z innej strony HTML i wyświetlającą je użytkownikowi w nowym formacie? Czy aplikacja ta ma dwa klienty? A np. system X Windows, gdzie wyświetlany obraz generowany jest na serwerze, a aplikacja zdalna jest klientem? Oczywiste jest, że definicja klienta jest bardzo pojemna. Na potrzeby niniejszej książki klientem będziemy nazywali wszystko to, co korzysta z danych aplikacji, programu lub mechanizmu. Klientem w tym pojęciu byłby więc program korzystający z opisanej wyżej bazy danych, a także użytkownik, który przegląda sformatowane dane. Program formatujący dane o pogodzie jest klientem; są nimi także użytkownicy korzystający ze sformatowanych danych. Jak widać, granica między pojęciami „użytkownik” a „program” zaciera się. W wielowarstwowym systemie, w którym uruchomiono bazę danych, pojemnik Enterprise JavaBean, mechanizm serwletów i strukturę publikacji istnieją cztery, pięć lub więcej klientów! Czytelnik winien zrozumieć, że w przypadku XML-a nie rozróżniamy pomiędzy klientem a programem korzystającym z danych. Pozwala to nam postrzegać przekształcanie danych w sposób bardziej użyteczny — jeśli aplikacja A wymaga danych (w formacie A) od aplikacji B (przechowującej dane w formacie B), musimy te dane przekształcić (ang. transform). W przekształcaniu biorą udział dwa formaty, natomiast same dane pozostają nienaruszone. Kiedy znamy już nową definicję klienta, to możemy jeszcze od procesu tego oddzielić szczegółowe informacje o aplikacji wykorzystującej dane. Jeśli aplikacja C wykorzystuje sformatowane dane aplikacji B w formacie C, musi się odbyć kolejne przekształcanie. Nie jest istotne, czy formaty te to HTML, SQL, XML zgodny z różnymi DTD, czy coś jeszcze zupełnie innego. Nie jest również istotne to, czy klient jest kolejnym programem, użytkownikiem czy systemem własnym. Przekształcanie to po prostu przekładanie z jednego formatu na drugi. Wszystkie te informacje pomogą Czytelnikowi zrozumieć, dlaczego przekształcanie danych XML jest tak ważne.
Ja nie rozumiem chińskiego ... Jak można się domyślać, najważniejszym celem przekształcania danych XML jest doprowadzenie ich do takiej postaci, aby były czytelne dla danego klienta. Czasami można to osiągnąć poprzez przekształcenie dokumentu XML zgodnego z pewną definicją DTD na dokument zgodny z inną definicją (lub schematem). Innym razem konieczne jest uzyskanie zupełnie innych dokumentów z dokumentu bazowego. Tak czy inaczej, potrzebne tutaj będą informacje dotyczące zawężania i sprawdzania poprawności danych XML. Nawet jeśli aplikacja „zna” format danych, to nie będzie go mogła jeszcze zinterpretować — do tego konieczne są definicje DTD, schematy lub inne zawę-
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 132
Składniki
133
żenia dokumentu. Wtedy dopiero aplikacja „zrozumie”, jakiej konwersji można dokonać i jakie struktury danych są obecne w dokumencie źródłowym. Problemy pojawiają się wtedy, gdy wzrasta liczba permutacji współpracujących ze sobą aplikacji; innymi słowy, im więcej jest składników biorących udział w wymianie danych, tym więcej możliwości przekształcania tych danych. Każdy komponent musi znać zawężenia innych komponentów. Dlatego właśnie stworzono zestaw specyfikacji i standardów służących do przekształcania danych XML. Powstała warstwa pośrednia, niezależna od poszczególnych komponentów aplikacji, która umożliwia konwersję danych z jednego formatu lub stylu na inny; samym komponentom pozostawia się czuwanie nad logicznym przetwarzaniem danych. Tę powłokę pośrednią będziemy nazywali procesorem. Procesor ma za zadanie pobrać jeden lub więcej dokumentów, określić ich formaty i zawężenia i dokonać przekształcenia (transformacji), po której powstają dane w nowym formacie (a to może nawet oznaczać rozdzielenie ich na wiele plików). Dokument wynikowy może potem zostać użyty przez inny komponent. Oczywiście, ta aplikacja może z kolei przekazać dane do innego procesora, który przekaże dokument w jeszcze innym formacie trzeciemu komponentowi i tak dalej. W ten sposób aplikacje nie znające tego samego „języka” mogą się ze sobą porozumiewać. Poniżej omówione zostaną „podzespoły” biorące udział w tym procesie.
Składniki Przekształcanie danych XML jest bardzo przydatne, ale wcale nie łatwe w implementacji. Nie określono sposobów przekształcania w oryginalnej specyfikacji XML 1.0; zamiast tego powstały trzy oddzielne zalecenia dotyczące przekształcania. Jeden z tych sposobów (XPath) jest również wykorzystywany w specyfikacji XPointer, ale na pewno najważniejszym celem opisywanych niżej komponentów jest przekształcanie XML-a z jednego formatu na inny. Te trzy specyfikacje są ze sobą ściśle powiązane (i niemal zawsze wykorzystywane wspólnie), więc zwykle opisuje się je razem. Powstały opis jest prostszy w zrozumieniu, ale niekoniecznie poprawny technicznie. Innymi słowy, termin XSLT, oznaczający dokładnie „przekształcenia XSL”, często stosowany jest jako określenie zarówno rozszerzalnych arkuszy stylów (XSL), jak i specyfikacji XPath. Podobnie, za pomocą terminu XSL określa się często wszystkie te trzy technologie. Tutaj zalecenia te zostaną rozdzielone. Jednak w celu uproszczenia będziemy używać skrótów XSL i XSLT przemiennie — określając za ich pomocą cały proces przekształcania. Nie jest to może zgodne z wytycznymi wspomnianych specyfikacji, ale na pewno zgodne z ich duchem i prostsze w zrozumieniu.
Rozszerzalny język arkuszy stylów (XSL) XSL to rozszerzalny język arkuszy stylów (ang. Extensible Stylesheet Language). Służy do tworzenia arkuszy stylów. To obszerna definicja, którą można rozbić na dwie części: • XSL to język służący do przekształcania dokumentów XML; • XSL to zbiór słów XML służących do formatowania dokumentów XML. Te definicje są do siebie podobne. Jedna z nich opisuje przekształcanie z jednego formatu XML na inny; druga odnosi się do samej prezentacji zawartości dokumentu. Mówiąc prościej, XSL opisuje, jak przekształcać dokument z formatu A na format B. Składniki tego języka opisują sposób takiego przekształcania.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 133
134
Rozdział 6. Przekształcanie kodu XML
XSL a drzewa Aby zrozumieć istotę XSL, trzeba przede wszystkim uświadomić sobie, że wszystkie dane biorące udział w przetwarzaniu XSL mają strukturę drzewiastą (patrz rysunek 6.1). Nawet same reguły definiowane za pomocą arkuszy XSL umieszczane są w takiej strukturze. Dzięki temu przetwarzanie dokumentów XML (także posiadających strukturę drzewiastą) jest bardzo proste. Za pomocą szablonów dopasowuje się element główny przetwarzanego dokumentu XML. Następnie dalsze reguły („liście”) stosuje się na dalszych elementach („liściach”) dokumentu XML, aż do tych położonych najniżej. Elementy mogą być przetwarzane, przekształcane za pomocą stylów, ignorowane, kopiowane lub w inny sposób „obrabiane” na dowolnym etapie tego procesu.
Rysunek 6.1. Operacje na drzewach w XSL Struktura drzewiasta ma tę zaletę, że umożliwia grupowanie dokumentów XML. Jeśli element A zawiera elementy B i C i nastąpi jego przeniesienie lub skopiowanie to elementy w nim zawarte będą podlegały tej samej operacji. Dzięki temu obsługa dużych porcji danych za pomocą arkusza XSL jest spójna i przejrzysta. O budowie takiego drzewa powiemy więcej w następnej części, przy okazji omawiania standardu XSLT.
Obiekty formatujące Niemal cała specyfikacja XSL to definicje obiektów formatujących. Obiekt formatujący oparty jest na dużym modelu określanym — jak łatwo się domyślić — nazwą modelu formatującego. W ramach modelu zdefiniowano zestaw obiektów, które przekazywane są na wejście programu formatującego. Program taki „aplikuje” obiekty formatujące na całym albo na części dokumentu; w rezultacie powstaje nowy dokument składający się z wszystkich lub tylko z części danych oryginalnego dokumentu XML, ale już w nowym formacie, odpowiadającym obiektom wykorzystanym przez program formatujący. Jest to, jak widać, koncepcja bardzo luźno zdefiniowana; dlatego też w ramach specyfikacji XSL starano się stworzyć bardziej konkretny model, z którym obiekty miałyby być zgodne. Innymi słowy, istnieje obszerny zestaw właściwości i słów kluczowych,
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 134
Składniki
135
które można wykorzystać w obiektach formatujących. Mamy tutaj typy obszarów podlegających wizualizacji, właściwości linii, czcionek, grafiki i innych obiektów wizualnych, obiekty formatujące typu „inline” i blokowe oraz wiele innych konstrukcji składniowych. Obiekty formatujące są szczególnie intensywnie wykorzystywane przy konwersji tekstowych danych XML na formaty binarne, takie jak pliki PDF lub dokumenty binarne w rodzaju Microsoft Word. Przy przekształcaniu danych XML na innegy format tekstowy obiekty te rzadko wykorzystuje się jawnie. Ponieważ większość aplikacji używanych w przedsiębiorstwach jest obecnie przynajmniej częściowo oparta na architekturze WWW, a w funkcji klienta wykorzystywana jest przeglądarka WWW, skoncentrujemy się głównie na przekształcaniu do formatu HTML-a lub XHTML-a. Będzie to wymagało jedynie pobieżnego omówienia obiektów formatujących; temat obiektów formatujących jest jednak na tyle obszerny, że zasługuje na oddzielną książkę lub witrynę WWW. Więcej informacji na ten temat można znaleźć w specyfikacji XSL pod adresem http://www.w3.org/TR/WD-xsl.
Transformacje XSL (XSLT) Drugim istotnym elementem opisywanego zagadnienia są transformacje XSL. Językiem opisującym konwersję dokumentu z jednego formatu na drugi jest XSLT. Składnia XSLT opisuje głównie te transformacje, które nie generują danych binarnych. Chodzi tu na przykład o przekształcanie dokumentu XML do HTML-a lub WML-a (Wireless Markup Language). Faktycznie, specyfikacja XSLT opisuje składnię arkuszy XSL w sposób bardziej jawny niż sama specyfikacja XSL! Podobnie jak w przypadku XSL, XSLT to zawsze poprawnie sformatowany i poprawny dokument XML. Dozwolone konstrukcje opisane są w odpowiedniej definicji DTD. W celu korzystania z XSLT konieczne jest więc nauczenie się jedynie nowej składni — a nie całkowicie nowych struktur, jak to ma miejsce w przypadku definicji DTD. Podobnie jak w przypadku XSL, XSLT oparty jest na hierarchii drzewiastej — elementy zagnieżdżone to „liście” lub „potomki”. XSLT udostępnia mechanizm dopasowania wzorców występujących w oryginalnym dokumencie XML (za pośrednictwem wyrażeń XPath, które wkrótce zostaną przedstawione), a następnie stosowania formatowania na znalezionych w ten sposób danych. Formatowanie może polegać po prostu na wysłaniu danych pozbawionych nazw elementów XML na wyjście albo na wstawieniu ich do złożonej tabeli HTML i wyświetleniu w różnych kolorach. XSLT udostępnia również rozmaite popularne operatory, takie jak instrukcje warunkowe, możliwość kopiowania fragmentów drzewa dokumentu, zaawansowane dopasowania wzorca oraz dostęp do elementów XML poprzez ścieżki absolutne i względne. Wszystko to upraszcza proces przekształcania dokumentu XML na nowy format.
Język XML Path (XPath) XPath to ostatni element używany w procesie przekształcania XML-a. XPath to mechanizm umożliwiający odwoływanie się do różnych nazw elementów i atrybutów w dokumencie XML. Jak wspomnieliśmy wcześniej, wiele specyfikacji XML korzysta z technologii XPath, ale tutaj skupimy się jedynie na wykorzystaniu jej w ramach XSLT. Przy złożonej strukturze, jaką może mieć dokument XML, zlokalizowanie konkretnego elementu lub grupy elementów może sprawiać trudności — tym większe, że nie można zakładać istnienia definicji DTD lub innych zawężeń opisujących strukturę dokumentu. Musi istnieć możliwość przekształcania zarówno dokumentów podlegających, jak i nie podlegających sprawdzaniu poprawności. Adresowanie elementów w XPath odbywa się za pomocą składni odpowiadającej strukturze drzewiastej dokumentu XML oraz operującym na niej procesom i konstrukcjom XSLT.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 135
136
Rozdział 6. Przekształcanie kodu XML
Najprościej odwołać się do dowolnego elementu lub atrybutu w dokumencie XML poprzez podanie ścieżki do tego elementu względem elementu bieżącego (przetwarzanego w danej chwili). Innymi słowy, jeśli element B jest elementem bieżącym, a w nim zagnieżdżone są elementy C i D, to te dwa ostatnie najprościej zlokalizujemy za pomocą ścieżki względnej. Przypomina to ścieżki względne w strukturze katalogów systemu operacyjnego. W XPath zdefiniowano również sposób adresowania elementów względem elementu głównego. Konieczne jest tutaj odwołanie się do elementu nie znajdującego się w zakresie nazwy elementu bieżącego; innymi słowy — elementu nie zagnieżdżonego w obecnie przetwarzanym elemencie. W XPath zdefiniowano wreszcie składnię dopasowywania wzorca („znajdź element, którego elementem macierzystym jest E i którego elementem siostrzanym jest F”). W ten sposób zapełniono lukę pomiędzy ścieżkami absolutnymi i względnymi. We wszystkich wyrażeniach można również korzystać z atrybutów (i stosować na nich podobne dopasowania). W przykładzie 6.1 reprezentowane są różne sposoby użycia Xpath.
Przykład 6.1. Wyrażenia XPath
Ponieważ często dokument wejściowy nie ma ustalonej struktury, może się okazać, że wyrażenie XPath przetwarza puste dane wejściowe, jeden element lub atrybut wejściowy, lub też wiele elementów i atrybutów wejściowych. Ta pożyteczna możliwość wiąże się z wprowadzeniem szeregu dodatkowych pojęć. Wynik przetwarzania wyrażenia XPath określa się mianem zestawu węzłów (ang. node set). To nie powinno Czytelnika dziwić, ponieważ termin „węzeł” był już używany wcześniej. Koncepcja ta jest zbieżna z hierarchiczną strukturą drzewiastą, często opisywaną pojęciami „liści” lub właśnie „węzłów”. Wynikowy zestaw węzłów może być następnie przekształcony, skopiowany, zignorowany lub poddany innym dozwolonym operacjom. Oprócz wyrażeń umożliwiających wybór zestawów węzłów, XPath udostępnia również funkcje operujące na zestawach węzłów, takie jak not() i count(). Funkcje te pobierają zestaw węzłów (zazwyczaj w postaci wyrażenia XPath) i poddają je dalszym przeliczeniom. Wszystkie wyrażenia i funkcje stanowią część specyfikacji i implementacji XPath; pojęcie XPath jest jednak również używane do opisania wyrażenia zgodnego z samą specyfikacją. Nie jest to (podobnie jak w XSL i XSLT) zawsze technicznie poprawne, ale upraszcza opisywanie XSL i XPath. Opis składni tych trzech komponentów jako takiej byłby po prostu kopiowaniem specyfikacji. Dlatego po raz kolejny posłużymy się naszym przykładowym dokumentem XML. Stworzymy fragment dokumentu HTML na podstawie poznanego już spisu treści w formacie XML. Pozwoli nam to zaznajomić się z językami XSL, XSLT i XPath w kontekście praktycznego zastosowania.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 136
Składnia
137
Składnia Skoro już znamy funkcje poszczególnych fragmentów układanki o nazwie „przekształcanie XML”, spróbujmy poukładać je w całość. Rozpoczniemy od naszego pierwotnego dokumentu XML reprezentującego fragment spisu treści niniejszej książki. Za zadanie postawimy sobie uzyskanie sformatowanego według naszego uznania dokumentu HTML. Operacje tego typu przeprowadza się niezwykle często. My zastosujemy jedynie proste formatowanie, ale możliwości są tutaj ogromne — ograniczeniem jest tylko wiedza i doświadczenie programisty. W poniższych ćwiczeniach zbudujemy arkusz stylu, który zastosujemy na naszym dokumencie XML i przy okazji omówimy najistotniejsze konstrukcje XSLT oraz sposób ich działania w aplikacjach XML. Ponieważ niniejszy rozdział stanowi wprowadzenie do technologii XSLT, nie będziemy omawiać bardziej złożonych przekształceń z jednego formatu XML na inny. Przekształcenia takie, typowe w dużych aplikacjach typu firma-firma, często zależą od konkretnego zastosowania i reguł panujących w danym przedsiębiorstwie. Przekształcenia tego typu zostaną omówione w dalszych rozdziałach. Konstrukcje stosowane w przekształceniu będą identyczne — inny będzie jedynie wynik ich działania.
XSL to XML Dowolny arkusz stylów XSL musi być przede wszystkim zgodny ze specyfikacją XML. Należy pamiętać, że XSL posiada swoje własne konstrukcje, ale tak naprawdę składają się one na jeden konkretny zasób słów zdefiniowanych w XML-u. Oznacza to, że arkusz XSL musi być poprawnie sformatowany, musi zawierać deklarację XML i muszą w nim być zadeklarowane wykorzystywane przestrzenie nazw. W przestrzeni nazw XSL (z przedrostkiem xsl) zdefiniowano elementy konieczne do przeprowadzenia transformacji (przekształcania). Każdy element arkusza „obsługujący” przekształcanie będzie więc rozpoczynał się przedrostkiem xsl. Na przykład element główny każdego arkusza stylu XSL powinien nosić nazwę xsl:stylesheet. Ta przestrzeń nazw nie tylko umożliwia identyfikację przestrzeni XSL przez parser i procesor, ale również upraszcza przeglądanie arkusza XSL — widzimy, które elementy służą do transformacji, a które nie. Oprócz deklaracji XML i wspomnianego elementu głównego, musimy wstawić odwołanie do przestrzeni nazw xsl. To nie powinno już stanowić dla Czytelnika niespodzianki — jeśli wstawiamy przedrostki jakiejś przestrzeni nazw, to parser musi znać identyfikator URI dla tej przestrzeni. Najnowszą wersję specyfikacji przestrzeni nazw XSL znaleźć można na stronach W3C, pod adresem http://www.w3.org/1999/XSL/Transform. W dokumentacji procesora XSL należy sprawdzić typ wersji XSL. Oprócz przestrzeni nazw XSL, będziemy się również odwoływali do elementów w naszym spisie treści XML, należących do przestrzeni JavaXML — jej deklarację musimy więc również zawrzeć w arkuszu stylu, taką samą jak w dokumencie XML. Należy pamiętać, że deklaracje te są wykorzystywane jedynie jako sposób przypisania wybranej przestrzeni nazw, nie reprezentują natomiast schematu, definicji DTD czy jakichkolwiek innych faktycznych danych. Z tej początkowej deklaracji, elementu głównego i deklaracji przestrzeni nazw możemy już utworzyć prosty szkielet arkusza XSL (przykład 6.2).
Przykład 6.2. Szkielet arkusza XSL
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 137
138
Rozdział 6. Przekształcanie kodu XML
Czytelnik winien zauważyć, że dodano atrybut version, który obecnie jest wymagany w arkuszach XSL. Powyższy arkusz stylów jest już poprawny, ale jeszcze zupełnie bezużyteczny. Nie zdefiniowaliśmy żadnych reguł dopasowujących elementy wejściowych danych XML. Zadanie to zostanie wykonane w następnym podrozdziale.
Szablony XSL Podstawowym zadaniem arkuszy XSL jest wyszukiwanie określonych elementów lub grup elementów w wejściowym dokumencie XML i stosowanie do nich reguł lub zestawów reguł. W języku Javy możemy to wyrazić następująco: wywołujemy akcesor np. pobierzWezly(kryteria) i wykonujemy obliczanie (transformację) na wartości, którą zwraca. Proces ten jest wykonywany za pośrednictwem szablonu XSL. Szablon to zestaw reguł, jakie należy zastosować w danych XML odpowiadających określonej ścieżce XPath. A więc teraz zaczynamy wykorzystywać wszystkie te komponenty, które zostały opisane wcześniej. Szablon definiujemy za pomocą elementu XSL template. Po dodaniu przedrostka przestrzeni nazw będzie to, oczywiście, element xsl:template. Element ten powinien zawierać atrybut match (dopasuj). Wartość atrybutu jest wyrażeniem XPath, dopasowującym zero lub więcej elementów przetwarzanego dokumentu XML.
Jak widać, jedyną trudnością w procesie tworzenia szablonu jest sformułowanie wyrażenia XPath dopasowującego element lub elementy XML, które chcemy „obrabiać”. Najprościej zastosować względne wyrażenie XPath. Podobnie jak podajemy katalog lib/ względem katalogu głównego, możemy określać elementy po prostu podając ich nazwę — o ile tylko znajdujemy się „o stopień wyżej” w hierarchii. Procesor XSLT „ustawia” nas na samym szczycie hierarchii elementów, a więc element główny dokumentu określamy podając jego nazwę:
W tym przypadku dopasowywany jest dokładnie jeden element, ale możliwe jest również dopasowanie wielu lub też żadnego elementu danych wejściowych. Takie sytuacje zostaną omówione w dalszej kolejności. Kiedy już element jest dopasowany, wypadałoby coś z nim zrobić. W szablonie mamy dostęp do wszystkich elementów znajdujących się wewnątrz dopasowanego elementu. Wracając do przykładu z katalogami, możemy powiedzieć, że zmieniliśmy katalog bieżący na lib/. Teraz możemy odwoływać się za pomocą zwykłych nazw do elementów niższego poziomu (JavaXML:Tytul i JavaXML:Spis); odwołania do wszelkich innych elementów wymagają bardziej złożonych
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 138
Składnia
139
wyrażeń XPath. Zanim jednak się tym zajmiemy, spróbujmy uzyskać jakikolwiek wynik zastosowania arkusza. Np. zdanie „Witaj świecie!” uzyskamy po prostu wpisując je w szablonie: Witaj świecie!
Oczywiście, taki wynik nie jest zbyt imponujący — chcemy przecież uzyskać dostęp do naszych danych, a nie wysyłać z arkusza XSL zwykły tekst. Najprościej uzyskamy pożądany efekt poprzez wykorzystanie domyślnego „zachowania” arkusza XSL. Skoro dopasowaliśmy element główny, to do szablonu została załadowana cała hierarchia elementów dokumentu XML. Dalej możemy zażądać, aby na danych zastosowane zostały wszystkie inne szablony naszego arkusza stylu. To może nie brzmi zbyt rozsądnie (przecież nie zdefiniowaliśmy żadnych innych szablonów); jednak w takim przypadku procesor XSLT przetwarza całą hierarchię elementów i po napotkaniu każdego węzła („liścia”) dodaje dane do strumienia wynikowego transformacji. W wyniku tego wszystkie dane dokumentu XML są drukowane hierarchicznie, bez stosowania jakiegokolwiek formatowania. Odpowiednia konstrukcja XSL nosi nazwę xsl:apply-templates. Jeśli nie zostaną podane żadne atrybuty, element ten spowoduje, że procesor dopasuje wszystkie elementy podrzędne względem bieżącego za pomocą wszystkich szablonów w arkuszu:
Jednak taki zapis jest wciąż bezużyteczny — są to przecież dane bez żadnego formatowania. My chcemy zastosować znaczniki HTML i stworzyć stronę WWW. Podobnie jak w przypadku tekstu „Witaj świecie!”, standardowe znaczniki HTML wystarczy po prostu wstawić. Do tekstu wynikowego dodajemy więc znaczniki head i body, a następnie wstawiamy dane XML „wewnątrz” tych znaczników — właśnie za pomocą omówionego wyżej elementu xsl:apply-templates. W ten sposób wykonaliśmy pierwszy krok w kierunku stworzenia faktycznego dokumentu HTML. Wprowadźmy więc do naszego arkusza stylu zmiany wskazane w przykładzie 6.3.
Przykład 6.3. Tworzenie wyjściowego pliku HTML Moja strona HTML!
Wynikowy plik HTML wyglądałby tak, jak w przykładzie 6.4.
Przykład 6.4. Plik HTML powstały z przykładu 6.3 i pliku XML ze spisem treści
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 139
140
Rozdział 6. Przekształcanie kodu XML Moja strona HTML! Java i XML
Wprowadzenie Co to jest? Jak z tego korzystać? Dlaczego z tego korzystać? Co dalej?
Pisanie w XML-u Dokument XML Nagłówek Zawartość Co dalej? Przykładowy współdzielony plik z opisem praw autorskich.
Nareszcie coś zaczyna się dziać. Na razie nie musieliśmy korzystać z wyszukanych funkcji XSL-a, ale już widać efekt w postaci wyjściowych danych HTML, i to przy niewielkim wysiłku z naszej strony. Można zauważyć, że elementy, których specjalne znaczenie w arkuszu powinno być zniesione (np. nawiasy trójkątne), zostały bezproblemowo przeniesione do drzewa wyjściowego. Dzieje się tak, ponieważ w samym arkuszu XSL elementy nie wchodzące w skład specyfikacji XSL (np. head i body) są wstawiane do drzewa wyjściowego bezpośrednio. To umożliwia nam dodawanie znaczników bez konieczności znoszenia ich specjalnego znaczenia. Zanim przejdziemy dalej, powiemy jeszcze o tym, jak dopasować w szablonie specyficzny element. Załóżmy, że chcemy, aby tytuł dokumentu HTML był identyczny z zawartością elementu JavaXML:Tytul. Dobrze byłoby, gdyby nie trzeba było tworzyć oddzielnego szablonu dla tego elementu — nie ma tutaj żadnego formatowania, więc byłoby to działanie na wyrost. My chcemy tylko uzyskać wartość elementu i „wpleść” ją w nasz HTML. Jak łatwo się domyślić, coś takiego można zrobić za pomocą jednej prostej konstrukcji — mianowicie xsl:value-of. Konstrukcja ta, zamiast przetwarzać dalej wyrażenie XPath, powoduje uzyskanie wartości dopasowanych przez XPath elementów. Wyrażenie XPath podawane jest jako wartość atrybutu select tego elementu. Wkrótce Czytelnik przekona się, że atrybut ten stosowany jest w wielu elementach XSL i że zawsze służy do podawania ścieżki XPath. W naszym arkuszu chcemy dopasować element JavaXML: Tytul, będący podrzędnym względem elementu bieżącego — czyli znów nie musimy w wyrażeniu XPath stosować jakichś specjalnych zabiegów. Po prostu wstawiamy tam nazwę elementu:
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 140
Składnia
141
Zanim przejdziemy do bardziej skomplikowanych struktur i wyrażeń, warto wspomnieć o jeszcze jednej rzeczy. W ostatnim przykładzie dopasowaliśmy element JavaXML:Tytul. Jednak wybranie wartości tego elementu nie oznacza usunięcia go z hierarchii elementów stanowiącej dane wejściowe procesora XSLT. To nieco osobliwe — nie tylko wartość elementu pojawi się w tytule dokumentu; zostanie ona powielona w jego treści, w wyniku działania xsl:apply-templates. Jeśli to zachowanie wydaje się Czytelnikowi niewłaściwe, warto zauważyć, że dane wejściowe procesora są nienaruszalne — nie można ich zmienić, a jedynie określić, w jaki sposób mają być przetwarzane i jakie informacje należy do nich dodać. Aby uniknąć przetwarzania jakiegoś elementu, musimy stworzyć wyrażenie XPath nie obejmujące go albo spowodować, aby szablon dopasowywał element, ale nie generował żadnych danych wyjściowych. Innymi słowy, musimy stworzyć szablon jawnie dopasowujący element JavaXML:Tytul i zrobić tak, aby po jego napotkaniu nie były tworzone żadne dane wyjściowe:
Kiedy wszystkie szablony w ramach szablonu elementu głównego zostaną już zastosowane, dopasowany — i zignorowany — zostanie także powyższy element. Mam nadzieję, że Czytelnik spostrzegł, że to rozwiązanie jest nieeleganckie i zastanawia się, jak stworzyć wyrażenie XPath ignorujące określony element. O tym wszystkim powiemy!
Struktury sterujące XSL, jak każdy dobry język przetwarzania, udostępnia struktury sterujące. Nie przypominają one może instrukcji sterujących Javy czy C, ale bardzo pomagają w sterowaniu przetwarzaniem drzewa wejściowego w procesorze XSLT. Przyjrzymy się najpopularniejszym z nich i zobaczymy, jak za ich pomocą określić dane, do których mamy mieć dostęp, oraz jak ustalić kolejność i sposób uzyskiwania tego dostępu.
Filtrowanie w XPath Omawianie struktur sterujących XSL zaczniemy od omówienia sterowania przetwarzaniem drzewa dokumentów. Czynność ta nie wymaga stosowania żadnej specjalnej konstrukcji, ale łatwo ją wykonać za pomocą wyrażeń XPath. Na przykład, jeśli zbudujemy wyrażenie XPath mówiące, że element opisujący tytuł ma nie być dublowany, to będzie to równie sprawna struktura sterująca, jak „typowe” struktury sterujące, o których powiemy dalej. Problem elementu JavaXML:Tytul można rozwiązać w prosty sposób. Pamiętamy, że zestaw węzłów zwróconych z wyrażenia XPath jest przetwarzany hierarchicznie; innymi słowy, procesor XSLT „nie widzi” wszystkich elementów dokumentu XML naraz. Rozpoznaje wszystkie elementy na określonym poziomie zagnieżdżenia, a następnie przetwarza w głąb struktury drzewiaste poszczególnych elementów. W przypadku naszego dokumentu elementy postrzegane przez procesor XSLT znajdujące się w dokumencie głównym to te zagnieżdżone dokładnie o jeden poziom niżej: JavaXML:Tytul, JavaXML: Spis oraz JavaXML:Copyright. Chcemy wyłączyć z przetwarzania JavaXML:Tytul. Najprościej wykorzystać tutaj funkcję XPath not(). Służy ona do generowania zestawu węzłów nie pasujących do podanego wyrażenia XPath. Najpierw dodajemy atrybut umożliwiający wprowadzenie wyrażenia XPath do szablonów. Na razie wystarczy podać atrybut select z gwiazdką — czyli wybieramy wszystkie węzły potomne węzła bieżącego. Nazwiemy to wybieraniem osi, na której będziemy przeprowadzać operację — tutaj wybieramy oś potomną. Oczywiście, w ten sposób nie rozwiązujemy do końca naszego problemu, ale początek mamy już za sobą:
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 141
142
Rozdział 6. Przekształcanie kodu XML
Teraz musimy dokładnie dopasować zestaw wynikowy. Na końcu kryterium wyboru dodamy nawiasy kwadratowe, w których wstawimy wyrażenie opisujące zestaw węzłów i zwracające węzły do przetworzenia. W naszym przykładzie w miejscu tym pojawi się funkcja not(), a następnie węzły osi potomnej, których nie chcemy przetwarzać:
Wyrażenie, które teraz wstawimy, powinno zawierać nazwę (wraz z przedrostkiem przestrzeni) ignorowanego elementu, czyli tutaj JavaXML:Tytul. Jednakże to wciąż nie wszystko. Ponieważ wybieramy węzły znajdujące się na określonej osi, musimy poinformować procesor XSLT, skąd pochodzi węzeł, do którego się odwołujemy. To dziwne — dlaczego programista ma sam podawać miejsce pochodzenia węzła? Zauważmy jednak, że często wybierana oś nie jest osią potomną, a więc konieczne jest podanie „punktu odniesienia” — nawet w naszym prostym przykładzie. W tym celu używamy słowa kluczowego self. Dzięki niemu procesor dowiaduje się, że węzły umieszczone po tym słowie są potomne względem węzła bieżącego (JavaXML:Ksiazka). Do oddzielenia słowa kluczowego od elementów (w naszym przypadku jednego elementu) używamy podwójnego dwukropka (jeden dwukropek pozostaje zarezerwowany jako separator przestrzeni nazw).
Jeśli to wydaje się Czytelnikowi zagmatwane, nie powinien się przejmować — to jest zagmatwane! Zestawy węzłów, osie i transformacje to wcale nie banalna sprawa. Te zagadnienia warte są osobnej publikacji. Tymczasem Czytelnik powinien starać się zrozumieć jak najwięcej z powyższego opisu i odnotować, co wciąż pozostaje niejasne. Po przeczytaniu tego i następnego rozdziału Czytelnik może znaleźć dodatkowe wyjaśnienia na stronach W3C (http://www.w3.org). Może również przejrzeć i dołączyć się do listy adresowej http://www.mulberrytech.com.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 142
Składnia
143
Pętle XSL udostępnia również konstrukcje do sterowania przetwarzaniem, analogiczne do standardowych programistycznych struktur sterujących. Pierwszą konstrukcją sterującą, jaką omówimy, jest xsl:for-each. Doskonale nadaje się ona do iteracji po danych w ramach jednego typu elementu. Na przykład — nasz spis treści zawiera szereg rozdziałów. Przetwarzając kolejne rozdziały, chcemy wydrukować ich tytuły. Aby to osiągnąć, tworzymy nowy szablon w pliku XSL, dopasowujący element JavaXML:Spis. Tak powstaje baza, w której zbudujemy pętlę (inaczej nie mielibyśmy gdzie jej wstawić). Moglibyśmy dodawać kolejne elementy do pierwszego szablonu JavaXML:Ksiazka, ale wtedy nasz arkusz szybko zapełniłby się złożonymi wyrażeniami XPath, odwołującymi się do węzłów leżących wiele warstw niżej. Tworząc nowy szablon dla elementu JavaXML:Spis, umożliwiamy budowanie wyrażeń XPath, w których „bazą” jest właśnie JavaXML:Spis, a nie JavaXML:Ksiazka. Wyrażenia XPath pozostają dzięki temu proste, a cały arkusz stylu — czytelny. Po przejściu do nowego szablonu można wydrukować nagłówek informujący, że teraz wyświetlony zostanie spis treści — wystarczy bezpośrednio wstawić odpowiedni fragment kodu HTML. Następnie dodajemy linię poziomą (), oddzielającą tytuł od zawartości: Spis treści
Zanim jednak stworzymy pętlę, musimy rozwiązać jeden problem związany z powyższym fragmentem. Jeśli Czytelnik nie widzi błędu, winien przypomnieć sobie, że plik XSL musi zawsze być poprawnie sformatowanym dokumentem XML. Nawet kiedy dodajemy statyczne fragmenty kodu HTML, to muszą one być zgodne z powyższą zasadą (co w praktyce czyni te fragmenty danymi XHTML!). A więc przy przetwarzaniu powyższego fragmentu zgłoszony zostałby błąd, ponieważ znacznik nie posiada odpowiadającego mu znacznika zamykającego. Z punktu widzenia XML-a nie jest to nic zaskakującego, ale jeśli Czytelnik jest przyzwyczajony do HTML-a, to tutaj natrafia na coś nowego. Proste rozwiązanie polega na stworzeniu pustego zapisu w znaczniku, ignorowanego przez przeglądarki przetwarzające wynikowy HTML: Spis treści
Do takich zapisów trzeba się po prostu przyzwyczaić — z biegiem czasu stają się one równie naturalne jak pisanie w mniej formalnym standardzie HTML. Skoro ten drobny szczegół mamy już za sobą, możemy budować pętlę. Konstrukcja xsl:for-each pobiera wyrażenie XPath (wstawione w atrybut select) opisujące węzeł, po którym następować będzie iteracja. W naszym przypadku życzymy sobie, aby wyrażenie zwracało wszystkie elementy JavaXML:Rozdzial. Tyle potrafimy już zrobić: Spis treści
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 143
144
Rozdział 6. Przekształcanie kodu XML
Teraz wystarczy dodać zawartość. Każdy przetwarzany węzeł na czas kolejnej iteracji pętli staje się węzłem bieżącym hierarchii. Oznacza to, że aby odwołać się do elementów i atrybutów zagnieżdżonych w elemencie JavaXML:Rozdzial, należy potraktować je jako podrzędne względem tego elementu, a nie elementu przetwarzanego w szablonie (JavaXML:Spis). My chcemy wydrukować nagłówek każdego rozdziału. Aby wynik wyglądał ładniej, każdy nagłówek będzie stanowił element nieponumerowanej listy HTML: Spis treści
Wynikowy kod HTML zaczyna już wyglądać obiecująco! Informacje o tym, jak taką zawartość stworzyć w mechanizmie serwletów, Czytelnik znajdzie w rozdziale 9. Wynikowy kod HTML wygląda następująco:
Przykład 6.5. Dane HTML powstałe w wyniku zastosowania zmodyfikowanego arkusza XSL Java i XML Spis treści
Wprowadzenie
Pisanie w XML-u
Przetwarzanie XML-a
Struktury publikacji WWW
Przykładowy współdzielony plik z opisem praw autorskich.
Czytelnik prawdopodobnie chciałby już się dowiedzieć, w jaki sposób faktycznie została przeprowadzona ta transformacja; o tym powiemy jednak dopiero w następnym rozdziale, po omówieniu konstrukcji XSL. Przed używaniem procesora XSLT należy poznać składnię XSL-a — to będzie procentowało na dalszym etapie nauki. Czytelnik może teraz zadawać sobie pytanie: dlaczego właściwie w stylach XSL korzystamy z pętli? Czy nie prościej byłoby stworzyć nowy szablon dla elementu, który zamierzamy sformatować (np. JavaXML:Rozdzial) i obsłużyć formatowanie w ramach tego szablonu? Tak, byłoby to prostsze (i robiliśmy to już wcześniej w przypadku wspomnianego elementu), ale problemem staje się wtedy czytelność kodu. W naszym arkuszu doskonale widać, do czego służy pętla — na pewno jest to rozwiązanie czytelniejsze, niż gdybyśmy tworzyli szablony dla każdego elementu JavaXML:Naglowek oddzielnie. W prosty sposób daje się także wyświetlić tylko te dane, które chcemy uwidocznić — w naszym przypadku nagłówek. Nie musimy pokazywać innych zagnieżdżonych w szablonie elementów (np. JavaXML:PodzialSekcji), ale rozdziały są wykorzys-
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 144
Składnia
145
tywane do formatowania w ramach listy (
) tworzonej w szablonie JavaXML:Spis. Gdybyśmy rzeczywiście stworzyli szablon dla rozdziałów, musielibyśmy pamiętać, że zawartość szablonu musi być elementem listy (). To mało eleganckie. Podobnie jak w przypadku innych aspektów języka XML, możliwość zrobienia czegoś nie oznacza jeszcze, że jest to do końca poprawne. Tak więc odpowiedź brzmi: owszem, byłoby prościej stworzyć szablon, ale nie jest to dobre rozwiązanie.
Wybór elementów do przetwarzania Oprócz stosowania pętli, często przydaje się także możliwość przetwarzania tylko tych węzłów, które spełniają pewne kryteria. Programistom od razu przychodzi tutaj na myśl konstrukcja typu if-then, obecna w większości języków. W języku XSL zachowanie takie symulujemy konstrukcją xsl:if, zwracającą tylko te węzły, które odpowiadają wymogom zarówno wyrażenia XPath, jak również kryteriom użytkownika. Przydaje się to wszędzie tam, gdzie konieczne jest przeanalizowanie wszystkich danych pewnego typu i gdzie chcemy wyświetlić lub sformatować w określony sposób jedynie podzbiór tych danych. W naszym przykładzie oddzielimy rozdziały dotyczące Javy od tych poświęconych głównie XML-owi. Informacje o tematyce rozdziału pobierzemy z atrybutu tematyka, już obecnego w naszym dokumencie XML. Atrybutem uzupełniającym konstrukcję xsl:if jest test. Wynikiem obliczenia wyrażenia test powinna być wartość „prawda” lub „fałsz”. W pierwszym przypadku element xsl:if będzie obliczany, w drugim ignorowany. Nic trudnego, prawda? Spójrzmy na nasz przykład. Za pomocą opisywanej konstrukcji wyświetlimy teraz tylko te rozdziały, które zawierają atrybut tematyka o wartości „Java”: Spis treści
Mamy tutaj kilka nowych rzeczy. Nowy jest sposób odwołania do atrybutu XML. Zamiast nazwy (jak w przypadku elementów), jako przedrostek wstawiamy znak @. Dzięki temu procesor XSLT „wie”, że odwołujemy się do atrybutu, a nie do elementu. Spójrzmy także na wartość dosłowną „Java”, wstawioną w apostrofy (czyli jako tekst statyczny). W przypadku dwóch rozdziałów, w których wyrażenie nie jest prawdziwe (rozdziały 1. i 2.), nagłówek nie jest drukowany; pozostałe dwa, spełniające zadane kryteria, są przetwarzane i drukowane na ekranie. To dość typowy i przydatny sposób „podejmowania decyzji” w czasie transformacji; jednak w przypadku naszego dokumentu — wciąż nie najlepszy. Zamiast tylko pokazywać rozdziały o XML-u lub o Javie, możemy zażyczyć sobie wyświetlania nazwy rozdziału, a potem jego tematyki w nawiasach. Owszem, moglibyśmy osiągnąć to za pomocą konstrukcji xsl:if, ale konieczne byłoby użycie dwóch pętli; pierwsza sprawdzałaby, czy tematyką poszczególnych rozdziałów jest XML, a druga sprawdzałaby, czy tematyką jest Java. Nie tylko odbiłoby się to na wydajności, ale również spowodowało wyświetlenie rozdziałów w złej kolejności — rozdziały o XML-u byłyby pierwsze, a te o Javie — ostatnie. Chcemy więc przeprowadzić podobny test, ale tak, aby zadana czynność wykonywana była w obu przypadkach. Służy do tego element xsl:choose. Pozwala on na przetestowanie warunku i wykonanie pewnej czynności, gdy warunek został spełniony, a in-
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 145
146
Rozdział 6. Przekształcanie kodu XML
nej, jeśli nie został. Element ten otacza blok czynności do wykonania w obu przypadkach. Wewnątrz bloku wstawiany jest element xsl:when („jeśli”), w którym za pomocą atrybutu test określa się rodzaj testu do przeprowadzenia. Test ten jest identyczny z podanym w atrybucie elementu xsl:if. Wewnątrz tego elementu wstawia się instrukcje przetwarzania, jakie mają zostać wykonane, gdy test zwróci wartość „prawda”. Różnica polega na tym, że jest jeszcze element xsl:otherwise („w przeciwnym razie”), zawierający instrukcje przetwarzania wykonywane, gdy warunek nie zostanie spełniony. Ten element zachowuje się podobnie jak słowo kluczowe default w instrukcji switch Javy. Do naszego przykładu wprowadzimy teraz drobne poprawki — za każdym razem drukowana będzie nazwa elementu i, na podstawie testu, wyświetlana odpowiednia tematyka: Spis treści
(Tematyka: Java)
(Tematyka: XML)
Należy sobie zdać sprawę z faktu, że powyższy przykład działa tylko dlatego, że dla atrybutu tematyka istnieją tylko dwie wartości. Trzeba także pamiętać, że moglibyśmy po prostu wysłać wartość tego atrybutu na ekran. Oczywiście, ale w ten sposób nie dowiedzielibyśmy się nic nowego o XSL-u! A mówiąc poważnie, element xsl:choose bardzo przydaje się do sterowania przetwarzaniem zestawu węzłów zwróconego z wyrażenia XPath, szczególnie gdy pewien podzbiór węzłów musi zostać wyizolowany i potraktowany w inny sposób. Nasz wynikowy plik HTML coraz bardziej przypomina rzeczywisty spis treści (przykład 6.6).
Przykład 6.6. Wynikowy plik HTML po zastosowaniu instrukcji sterujących Java i XML Spis treści
Wprowadzenie (Tematyka: XML)
Pisanie w XML-u (Tematyka: XML)
Przetwarzanie XML-a (Tematyka: Java)
Struktury publikacji WWW (Tematyka: Java)
Przykładowy współdzielony plik z opisem praw autorskich.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 146
Składnia
147
Struktury sterujące umożliwiają wykonanie wielu różnych zadań; przydają się również do zrozumienia bardziej złożonych wyrażeń XPath oraz testów. W następnych rozdziałach Czytelnik dowie się, jak za pomocą języka XSL przetwarzać i tworzyć elementy i atrybuty uzupełniające te już obecne na wejściu procesora.
Elementy i atrybuty Teraz czytelnik powinien już rozumieć, jak dużą kontrolę nad dokumentem XML daje nam arkusz XSL. Nie powinno być zaskoczeniem, że można również definiować własne atrybuty i elementy. Mogą one służyć do obliczeń lub po prostu zostać dodane do danych wyjściowych. Najczęściej wykorzystuje się takie elementy w zaawansowanych szablonach i przy przetwarzaniu parametrów, tego tematu nie będziemy jednak obszernie omawiali. Aby dowiedzieć się więcej o tych bardziej zaawansowanych zastosowaniach arkuszy XSL, warto zapisać się na listę adresową XSL, na której tematyka ta jest często poruszana. Informacje o liście uzyskamy pod adresem http://www.mulberrytech.com/xsl/xsl-list. Jednym z typowych powodów, dla których tworzy się elementy i atrybuty w arkuszu stylów, jest konieczność zbudowania dynamicznych odwołań HTML wewnątrz danych XML. Aby to zademonstrować, do naszego arkusza dodamy sekcję zawierającą informacje o materiałach dodatkowych (być może nie nadaje się to do spisu treści, ale służy jako doskonały przykład!), zagnieżdżoną w elemencie JavaXML:Ksiazka (należy pamiętać o wyłączeniu sprawdzania poprawności — w przeciwnym razie zostaną złamane zasady zdefiniowane w DTD!): Java i XML Grupa W3C http://www.w3.org/Style/XSL Lista XSL http://www.mulberrytech.com/xsl/xsl-list
Aby obsłużyć taki fragment, trzeba dodać nowy szablon do arkusza stylu. Można również dodać formatowanie HTML oraz pętlę przebiegającą po odsyłaczach w dokumencie — wszystko to już potrafimy:
Pożyteczne informacje dodatkowe
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 147
148
Rozdział 6. Przekształcanie kodu XML
Pozostaje teraz utworzyć odsyłacz HTML. Powinien on zawierać wartość elementu JavaXML: Nazwa jako etykietę oraz JavaXML:Url jako adres. Ale jak dodać to do zdefiniowanego znacznika? Musimy uzyskać coś takiego: [wartość elementu Nazwa]
Trik polega na tym, że atrybut na wyjściu musi składać się z elementu znajdującego się na wejściu procesora. Dobrym sposobem osiągnięcia takiego rezultatu jest zastosowanie konstrukcji xsl: element i xsl:attribute — pozwalają one ustawić wartości odpowiednich elementów i atrybutów. Pierwsza z nich zawiera atrybut name, w którym podawana jest nazwa budowanego elementu. Innymi słowy, zapis Cze••! spowoduje wyświetlenie Cze••!. Do definicji możemy także dodać znaczniki , działające w analogiczny sposób. Tak więc następujący arkusz XSL: Java jest świetna!
zostałby przetworzony do postaci wynikowej: jest świetna!
W elementach xsl:element i xsl:attribute mogą pojawić się złożone wyrażenia, co umożliwia tworzenie naprawdę dowolnych „wplecionych” wartości. I teraz wiemy już, jak rozwiązać nasz problem z adresami URL:
Pożyteczne informacje dodatkowe
Potrafimy już wstawiać wartości elementów i tworzyć atrybuty, które potem interpretowane są jako odsyłacze w HTML-u. Wynik transformacji, przedstawiony w przykładzie 6.7, jest zgodny z naszymi oczekiwaniami:
Przykład 6.7. Wynikowy plik HTML zawierający „pożyteczne informacje” Java i XML
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 148
Składnia
149
Spis treści
Wprowadzenie (Tematyka: XML)
Pisanie w XML-u (Tematyka: XML)
Przetwarzanie XML-a (Tematyka: Java)
Struktury publikacji WWW (Tematyka: Java)
Pożyteczne informacje dodatkowe
Grupa W3C
Lista XSL
Przykładowy współdzielony plik z opisem praw autorskich.
Dane... po prostu dane Można dowieść, że w idealnej sytuacji w dowolnym dokumencie XML wystarczy przeprowadzić tylko jedną transformację. Można także twierdzić, że dokument XML powinien zawierać czyste dane, bez jakiegokolwiek znacznika formatującego lub elementu, który powinien pozostać nieprzetworzony. Niestety, sytuacje idealne zdarzają się rzadko. Czasem elementy dokumentu XML są wykorzystywane jako dane. Jeśli to wydaje się dziwne, to nic nie szkodzi — to jest dziwne dla wielu osób. Ale prawdopodobnie niejeden Czytelnik dobrze już wie, o co tutaj chodzi. Wielokrotnie zdarza się, że chcemy stworzyć kod HTML wewnątrz dokumentu XML i powiedzieć procesorowi XSLT: „Tego HTML-a proszę mi nie ruszać!”. Zdarza się tak w sytuacji, gdy trzeba łączyć w łańcuchy różne arkusze stylów lub generować elementy, które mają pozostać nieprzetworzone i wykorzystane w innej aplikacji „znającej XML”. Sytuacja taka ma również miejsce w przypadku danych, które mają zostać wyświetlone na ekranie bez przetwarzania. Jednym słowem — jeśli jeszcze na taką sytuację Czytelnik się nie natknął, to prędzej czy później mu się to przydarzy! Ostatnią konstrukcją, jaką omówimy w związku z przetwarzaniem XSLT, jest konstrukcja zabraniająca przetwarzania! Najlepiej posłużyć się przykładem. Zamiast korzystać z encji odwołującej się do praw autorskich, wstawmy w tym miejscu dane HTML. Dane te mogą być pobrane z innego pliku lub też mogą zostać wygenerowane poza naszą kontrolą. Innymi słowy, otrzymujemy dane HTML, na które nie mamy wpływu — a to zdarza się często. Dodajmy więc do naszego dokumentu XML kod HTML:
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 149
150
Rozdział 6. Przekształcanie kodu XML Copyright O'Reilly and Associates, 2000
I to mamy teraz obsłużyć w transformacji XSL. Po bezpośrednim przetworzeniu otrzymalibyśmy tekst „Copyright O'Reilly and Associates, 2000” bez jakiegokolwiek formatowania. Nie powinno to Czytelnika dziwić, jeśli przypomni sobie opis sposobu dopasowania w szablonie. Trzeba pamiętać, że jeśli dla elementu nie podano szablonu, dane wynikowe nie będą zawierały nic, a drzewo wejściowe jest przetwarzane dopóty, dopóki nie zostaną znalezione jakieś dane do wydrukowania. To, co zostało dostarczone do procesora jako znaczniki HTML, jest traktowane jako XML i wszystkie znaczniki typu center, table itd. są ignorowane — pozostają tylko dane tekstowe, niesformatowane i niezmienione. Na szczęście istnieje proste rozwiązanie tego problemu. Aby pewne elementy oznaczyć jako dane, wystarczy użyć konstrukcji xsl:copy-of. Działa ona identycznie jak xsl:value-of, pobierając wyrażenie XPath poprzez wartość atrybutu select. Jednak zamiast dawać w wyniku wartości zwróconego zestawu węzłów, przekazuje taki zestaw bezpośrednio z wejścia na wyjście. Cała zawartość zestawu węzłów nie jest przetwarzana.
Zawartość elementu JavaXML:Copyright (wraz z całym kodem HTML) jest przekazywana bez zmian. Nie znaczy to jednak, że możemy teraz dowolnie łamać zasady XML-a! Zawartość tego węzła jest przetwarzana dokładnie tak samo, jak dowolne inne dane XML, zanim w ogóle nastąpi transformacja. Innymi słowy, korzystanie ze znaku & lub elementów bez znaczników zamykających (np. ) jest niedozwolone. Ale możliwość bezpośredniego przesłania danych na wyjście w celu ewentualnego dalszego przetwarzania jest bardzo przydatna wszędzie tam, gdzie dane te nie są XML-em lub gdzie konieczne jest zastosowanie wielu arkuszy stylów i nie wszystkie elementy mają być przetwarzane przez jeden styl. Teraz spójrzmy na gotowy arkusz XSL z dodanym nowym szablonem (przykład 6.8):
Przykład 6.8. Gotowy arkusz XSL
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 150
Składnia
151
Spis treści
(Tematyka: Java)
(Tematyka: XML)
Pożyteczne informacje dodatkowe
Tabele HTML zostały przekazane na wyjście bez zmian; wynikowy dokument HTML można obejrzeć w przykładzie 6.9.
Przykład 6.9. Wynikowy dokument HTML ze skopiowaną zawartością XHTML Java i XML Spis treści
Wprowadzenie (Tematyka: XML)
Pisanie w XML-u (Tematyka: XML)
Przetwarzanie XML-a (Tematyka: Java)
Struktury publikacji WWW (Tematyka: Java)
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 151
152
Rozdział 6. Przekształcanie kodu XML
Pożyteczne informacje dodatkowe
Grupa W3C
Lista XSL
Copyright O'Reilly and Associates, 2000
Rysunek 6.2, będący zapowiedzią rozdziału o procesorze XSLT, przedstawia otrzymany plik HTML w takiej postaci, w jakiej będzie on widoczny w przeglądarce.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 152
Składnia
153
Rysunek 6.2. Wynikowa strona HTML w przeglądarce WWW
Kiedy tekst nie wystarcza ... Przekształcanie danych XML na inny format jest przydatne i często stosowane. Jednak czasami to nie wystarcza. Powiedzieliśmy, że dane XML można przekształcić na niemal dowolne formaty, a nie tylko na formaty tekstowe w rodzaju HTML i WML. Na przykład XML można przetworzyć i przekształcić na format PDF (Portable Document Format), który to format odczytywany jest później np. za pomocą programu Adobe Acrobat. Do takich binarnych przekształceń wykorzystuje się obiekty formatujące. Wspomnieliśmy o nich krótko już wcześniej, przy okazji omawiania specyfikacji XSL. Ponieważ w wyniku działania XSL musi powstać poprawnie sformatowany XML, nie jest możliwe, aby procesor XSLT bezpośrednio zwrócił dane binarne. Obiekty formatujące umożliwiają zdefiniowanie zestawu elementów i atrybutów XML, które wykorzystuje się do reprezentacji obszarów (ang. areas) formatu wyjściowego. Obszar jest następnie wykorzystywany przez procesor obiektów formatujących i przekształcany na format binarny. Jako przykład rozważmy następujący fragment arkusza XSL, przekształcający XML z wykorzystaniem obiektów formatujących:
Wynik transformacji elementu JavaXML:Title, którego wartość to „Java i XML”, miałby następującą postać: Java i XML
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 153
154
Rozdział 6. Przekształcanie kodu XML
Powyższy zapis jest sam w sobie nieco zagadkowy i na pewno nie jest fragmentem dokumentu PDF. Jednakże procesor obiektów formatujących, taki jak FOP autorstwa grupy Apache XML (http://xml.apache.org), może przekształcić taki fragment na binarne dane PDF. Uzyskalibyśmy wtedy tytuł „Java i XML” umieszczony na środku strony, wpisany czcionką o podanej wielkości. Ten sam fragment XML można byłoby przekształcić na dokument programu Word lub na arkusz kalkulacyjny Star Office — o ile tylko istniałyby odpowiednie procesory. W arkuszu XSL generowane są „obszary” XML z wykorzystaniem obiektów formatujących; te reprezentacje danych można potem przekształcać w celu uzyskania odpowiedniej postaci binarnej. Jednocześnie zachowany jest format XML, zarówno w postaci oryginalnej, jak i w przekształconych dokumentach — dane te są więc cały czas przenośne. Tak więc ustalenie tego, czy wystarcza uzyskanie danych tekstowych, czy nie, nic nie zmienia w samej technice przetwarzania XSL — zmieniają się tylko przekształcane obiekty. Wciąż mamy jeden dokument źródłowy, z którego można uzyskać wiele dokumentów wynikowych.
Co dalej? Mamy już pełny, działający arkusz XSL. Czytelnik powinien już potrafić przetwarzać i przekształcać dane XML oraz tworzyć nowe dane w arkuszu XSL. Aby jednak rzeczywiście zrealizować współpracę standardów XML i XSL, potrzebny jest nam procesor XSLT. Procesor taki, w naszym przypadku napisany w Javie, zajmie się faktyczną transformacją i stworzy dane wynikowe — dokument XML z narzuconymi stylami XSL. W następnym rozdziale zostanie omówiona obsługa takiego właśnie procesora XSLT — zarówno standardowa, z wiersza poleceń, jak i z poziomu programu w Javie. Przedstawiony zostanie również obiektowy model dokumentu (DOM); służy on do generowania danych XML w formacie odpowiednim dla danych wejściowych procesora XSLT. W zakończeniu rozdziału 7. zostaną podsumowane poznane dotychczas wiadomości dotyczące przetwarzania XML-a — Czytelnik zobaczy, w jaki sposób łączą się one w jedną całość i są wykorzystywane w większych aplikacjach XML.
C:\WINDOWS\Pulpit\Szymon\Java i XML\06-08.doc — strona 154
7 Czerpanie z XML-a W poprzednim rozdziale Czytelnik nauczył się tworzyć arkusze stylów XSL dla posiadanych dokumentów XML. W niniejszym rozdziale temat ten będzie kontynuowany. Czytelnik dowie się, jak dokument i arkusz stylów są przetwarzane i przekształcane na dane wyjściowe. Podobnie jak w poprzednich rozdziałach, tym razem przyjrzymy się poznanym strukturom języka XML z punktu widzenia Javy. Omówimy procesory XSLT, interfejsy API Javy do obsługi wejścia XML w formacie drzewiastym oraz powiemy, czym różnią się te interfejsy od opisywanego już SAX-a. Najpierw przeanalizujemy sposób, w jaki wykonywane są w komputerze transformacje. Stworzymy w ten sposób „wirtualny plac zabaw”, gdzie będziemy mogli eksperymentować z własnymi konstrukcjami XSL i XSLT. Spróbujemy również dodać nieco bardziej złożone formatowanie do arkusza stylów, który stworzyliśmy w ostatnim rozdziale. Ponadto zaczniemy dokładniej analizować sposób działania procesora XSLT; na końcu szczegółowo omówimy, jakiego rodzaju i formatu danych wejściowych oczekuje taki procesor. Tym samym rozpoczniemy dyskusję o obiektowym modelu dokumentu (DOM) — alternatywnym względem SAX-a sposobie uzyskiwania dostępu do danych XML. Na koniec odejdziemy od tematu parserów, procesorów i interfejsów API, spróbujemy natomiast poskładać wszystkie elementy „układanki XML” w jedną całość. Będzie to wstęp do pozostałej części książki — spróbujemy bardziej przekrojowo opisać różne typy aplikacji XML i sposoby wykorzystania wzorców projektowych i struktur XML do własnych potrzeb. Przed lekturą dalszej części książki Czytelnik powinien zrozumieć nie tylko tematykę niniejszego rozdziału, ale także to, jakich tematów ten rozdział nie porusza. Czytelnik nie znajdzie tutaj opisu tworzenia procesora XSLT (podobnie jak wcześniej nie znalazł receptury tworzenia parsera XML). Opisywane tutaj zagadnienia są bardzo ważne — w zasadzie kluczowe — do korzystania z procesora XSLT; to także świetny wstęp do ewentualnego zaangażowania się w rozbudowę istniejących procesorów XSLT, takich jak Xalan grupy Apache. Jednakże parsery i procesory to programy niezwykle złożone i próba wyjaśnienia ich wewnętrznych mechanizmów zajęłaby resztę książki, a może i całą następną! My natomiast zajmiemy punkt widzenia programisty aplikacji lub architekta programów w Javie; postaramy się wykorzystać istniejące już narzędzia i w razie konieczności rozbudować je do własnych potrzeb. Innymi słowy, zanim zabierzemy się za programowanie procesorów, powinniśmy nauczyć się ich używać!
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 155
156
Rozdział 7. Czerpanie z XML-a
Uzyskiwanie danych wyjściowych Jeśli Czytelnik śledził przykłady z ostatniego rozdziału, powinien być już przygotowany na przekazanie arkusza i dokumentu XML do procesora. W przypadku większości procesorów jest to dość prosta czynność. Zgodnie z przyjętą taktyką korzystania z najlepszych w branży produktów typu open source, użyjemy procesora Apache Xalan (można go pobrać i uzyskać o nim informacje pod adresem http://xml.apache.org). Nad Xalanem pracują najtęższe umysły — programiści firm Lotus, IBM, Sun, Oracle i innych. Ponadto procesor ten świetnie współpracuje z opisywanym we wcześniejszych rozdziałach parserem Apache Xerces. Jeśli jednak Czytelnik posiada już inny procesor, to także nie powinien mieć problemów ze znalezieniem informacji, dotyczących uruchamiania przykładów opisywanych w tym rozdziale, a wynik działania programu powinien być identyczny lub bardzo podobny do uzyskiwanego w książce. Najpierw spróbujemy uruchomić procesor XSLT z wiersza poleceń. Często robi się to na potrzeby testowania, usuwania błędów i tworzenia zawartości dokumentów w trybie offline. Warto pamiętać, że w wielu „poważnych” witrynach WWW zawartość tworzona jest właśnie offline, często w godzinach nocnych lub raz w tygodniu, dzięki czemu w czasie żądania pobrania strony nie ma spadku wydajności związanego z dynamicznym przetwarzaniem XML-a na HTML lub inny język znaczników. Uruchamiany w ten sposób procesor pomoże nam również w przeanalizowaniu różnych warstw transformacji XML. Dokumentacja używanego procesora powinna zawierać instrukcje dotyczące sposobu uruchamiania go z wiersza poleceń. W przypadku procesora Apache Xalan polecenie ma następującą postać D:\prod\JavaXML> java org.apache.xalan.xslt.Process -IN [Dokument XML] -XSL [Arkusz stylu XSL] -OUT [Plik wyjściowy]
Xalan, jak każdy inny procesor, umożliwia podanie także wielu innych opcji w wierszu poleceń, ale my będziemy korzystali głównie z tych trzech powyższych. Xalan domyślnie korzysta z parsera Xerces, a więc w ścieżce dostępu do klas będą musiały się znaleźć zarówno klasy parsera, jak i procesora. W wierszu poleceń można zażądać zmiany parsera XML, ale w Xalanie obsługa parsera Xerces jest najbardziej zaawansowana. Jeśli przekształcanie odbywa się w powyższy sposób, nie trzeba odwoływać się do arkusza stylu z poziomu dokumentu; procesor XSLT sam zastosuje arkusz stylu podany w wierszu poleceń. Wewnętrzne deklaracje arkuszy stylów zostaną użyte dopiero w rozdziale 9., Struktury publikacji WWW. Tak więc do zbudowania polecenia uruchamiającego nasz procesor potrzebujemy nazwy dokumentu XML i arkusza XSL (w tym przypadku znajdującego się w podkatalogu). Ponieważ w wyniku mamy uzyskać plik HTML, jako plik wyjściowy podajemy contents.html: D:\prod\JavaXML> java org.apache.xalan.xslt.Process -IN contents.xml -XSL XSL/JavaXML.html.xsl -OUT contents.html
Uruchomienie takiego polecenia w odpowiednim katalogu spowoduje, że Xalan rozpocznie proces transformacji. Uzyskamy wynik podobny do tego przedstawionego w przykładzie 7.1.
Przykład 7.1. Przekształcanie pliku XML za pomocą procesora Apache Xalan D:\prod\JavaXML> java org.apache.xalan.xslt.Process -IN contents.xml -XSL XSL/JavaXML.html.xsl -OUT contents.html
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 156
Pobieranie danych wejściowych
157
========== Parsing file:D:/prod/JavaXML/XSL/JavaXML.html.xsl ========= Parse of file:D:/prod/JavaXML/XSL/JavaXML.html.xsl took 1161 milliseconds ========= Parsing contents.xml ========== Parse of contents.xml took 311 milliseconds ============================= Transforming... transform took 300 milliseconds XSLProcessor: done
Po ukończeniu przetwarzania powinno być możliwe otworzenie uzyskanego pliku contents.html w edytorze lub przeglądarce WWW. Jeśli Czytelnik postępował zgodnie z instrukcjami w ostatnim rozdziale, to w przeglądarce powinna zostać wyświetlona strona widoczna na rysunku 7.1.
Rysunek 7.1. Strona HTML uzyskana po transformacji danych XML Teraz Czytelnik wie już, jak wprowadzać zmiany i testować dane wynikowe plików XML i arkuszy XSL. Procesor Xalan uruchomiony z wiersza poleceń posiada także pożyteczną funkcję odnajdywania błędów w plikach XML lub XSL i podawania numerów wierszy, w których one wystąpiły — to jeszcze bardziej upraszcza usuwanie błędów i testowanie plików.
Pobieranie danych wejściowych Poza powodami, o których już wspomnieliśmy, jest jeszcze jedna istotna przyczyna, dla której nie będziemy zajmować się omawianiem wewnętrznych mechanizmów procesora — dane wejściowe i wyjściowe procesora są o wiele bardziej zajmujące! Widzieliśmy już, jak można przetwarzać dokument przyrostowo za pomocą interfejsów i klas SAX. W procesie tym w prosty sposób decydujemy, co zrobić z napotkanymi elementami, jak obsłużyć określone atrybuty i jakie czynności powinny zostać podjęte w przypadku napotkania błędów. Jednakże korzystanie z takiego modelu w pewnych sytuacjach rodzi również problemy. Jedną z takich sytuacji jest przekazywanie danych wejściowych dla procesora XSLT.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 157
158
Rozdział 7. Czerpanie z XML-a
SAX działa sekwencyjnie Model sekwencyjny oferowany przez interfejs SAX nie umożliwia uzyskania swobodnego dostępu do dokumentu XML. Innymi słowy, korzystając z SAX-a pobieramy informacje o dokumencie XML wtedy, kiedy robi to parser — i podobnie jak parser informacje te tracimy. Kiedy pojawia się element 2., to nie można uzyskać dostępu do informacji w elemencie 4., ponieważ nie został on jeszcze przetworzony. Natomiast kiedy pojawi się element 4., to nie możemy powrócić do elementu 2. Oczywiście, mamy prawo zachować informacje napotkane w procesie przetwarzania, ale zakodowanie tego typu przypadków specjalnych może być bardzo trudne. Przeciwną skrajnością jest stworzenie reprezentacji dokumentu XML w pamięci. Wkrótce okaże się, że parser DOM postępuje właśnie w ten sposób; tak więc wykonywanie tego w interfejsie SAX byłoby bezcelowe, a prawdopodobnie także wolniejsze i bardziej kłopotliwe.
SAX a elementy siostrzane Innym zadaniem trudnym do wykonania w interfejsie SAX jest przechodzenie z elementu na element „w poziomie”. Dostęp do elementów poprzez SAX jest w dużym stopniu hierarchiczny i sekwencyjny. Uzyskujemy dostęp do krańcowego elementu węzła, potem przechodzimy z powrotem „w górę” drzewa i znów schodzimy do innego elementu na dole hierarchii. Nie ma przejrzystego odniesienia do „poziomu” hierarchii, na którym aktualnie się znajdujemy. Identyfikację poziomów można co prawda wdrożyć poprzez wprowadzenie wyszukanych liczników, ale ogólnie SAX nie jest do tego typu operacji przystosowany. Nie ma zaimplementowanego pojęcia elementu siostrzanego, następnego elementu na tym samym poziomie; nie ma też możliwości sprawdzenia, które elementy są zagnieżdżone w których. Procesor XSLT musi znać elementy siostrzane danego elementu; co ważniejsze, musi znać jego elementy potomne. Spójrzmy na taki fragment szablonu XSL:
Szablony nakładane są poprzez konstrukcję xsl:apply-templates, ale to nakładanie odbywa się na konkretnym zestawie węzłów, pasującym do podanego wyrażenia XPath. W powyższym przykładzie szablon powinien być nałożony jedynie na elementPotomny1 lub elementPotomny2 (są one rozdzielone operatorem LUB wyrażeń XPath, czyli kreską poziomą). Ponadto, ponieważ wykorzystujemy ścieżkę względną, wspomniane elementy muszą być bezpośrednio potomne względem elementu elementMacierzysty. Określenie i zlokalizowanie tych węzłów w reprezentacji dokumentu XML oferowanej przez SAX byłoby niezwykle trudne. Dzięki hierarchicznej reprezentacji dokumentu w pamięci czynność ta jest bardzo łatwa — i jest to kolejny powód, dla którego tak często korzysta się z modelu DOM jako wejścia dla procesorów XSLT.
Przyczyny korzystania z SAX-a Wszystkie te „wady” SAX-a skłaniają zapewne Czytelnika do zastanawiania się, dlaczego w ogóle korzysta się z interfejsu SAX. Warto więc tutaj przypomnieć, że powyższe problemy odnoszą się do konkretnego zastosowania danych XML, w tym przypadku przetwarzania poprzez XSL. Otóż „wady” te są jednocześnie... zaletami SAX-a! Czy to nie wydaje się zagmatwane? Wkrótce okaże się, że nie tak bardzo.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 158
Obiektowy model dokumentu (DOM)
159
Wyobraźmy sobie, że przetwarzamy spis treści czasopisma National Geographic w postaci danych XML. Dokument taki często osiąga 500 wierszy długości, czasem więcej. Teraz wyobraźmy sobie indeks książki O'Reilly w postaci pliku XML. Setki słów z numerami stron, odsyłaczami itd. Wszystko to przykłady w miarę małych, spójnych aplikacji XML. W miarę wzrostu dokumentu XML rośnie obszar zajmowanej przez niego pamięci, jeśli korzystamy z drzewa DOM. Weźmy teraz pod uwagę sytuację, w której dokument XML staje się tak wielki, że jego reprezentacja w modelu DOM zaczyna wpływać na wydajność aplikacji. I wyobraźmy jeszcze sobie, że te same wyniki można uzyskać poprzez przetworzenie dokumentu wejściowego sekwencyjnie, za pomocą SAX-a — przy wykorzystaniu jednej dziesiątej lub jednej setnej zasobów systemowych. Powyższy przykład obrazuje, że podobnie jak w Javie istnieje wiele sposobów wykonania tego samego zadania, tak i w różny sposób można uzyskać dane dokumentu XML. W wielu scenariuszach SAX stanowi lepszy wybór — oferuje szybkie przetwarzanie i przekształcanie. W innych przypadkach zwycięża DOM — zapewnia prosty, przejrzysty interfejs danych określonego formatu. To my, programiści, musimy zastanowić się nad celem budowania aplikacji i wybrać odpowiednią metodę (albo opracować sposób współdziałania obydwu metod). Jak zwykle umiejętność podjęcia właściwej decyzji wynika ze znajomości dostępnych rozwiązań. Mając to na uwadze, przyjrzyjmy się takiemu właśnie nowemu rozwiązaniu.
Obiektowy model dokumentu (DOM) W przeciwieństwie do interfejsu SAX, obiektowy model dokumentu wywodzi się z kręgów konsorcjum W3C. SAX to oprogramowanie będące własnością publiczną, stanowiące owoc długich dyskusji na liście adresowej XML-dev. Natomiast DOM jest samym w sobie standardem, tak jak XML. DOM nie został również opracowany wyłącznie dla Javy; jego zadaniem jest reprezentacja zawartości i modeli dokumentów we wszystkich językach i narzędziach programistycznych. Istnieją interfejsy DOM dla JavaScriptu, Javy, CORBA i innych języków. Jest to więc specyfikacja neutralna językowo i platformowo. Kolejna różnica polega na tym, że DOM jest dostarczany jako „poziomy”, a nie wersje. DOM Level One (DOM poziomu pierwszego) ma status przyjętego zalecenia W3C, a pełną specyfikację możemy przejrzeć pod adresem http://www.w3.org/TR/REC-DOM-Level-1/. Level One opisuje funkcje i sposób nawigacji po zawartości dokumentu. Dokument w modelu DOM nie musi być dokumentem XML — może to być HTML bądź dowolny inny rodzaj zawartości! Level Two (DOM poziomu drugiego), ukończony w roku 2000, uzupełnia Level One o moduły i opcje dla poszczególnych modeli zawartości, takich jak XML, XSL czy CSS (kaskadowe arkusze stylów). W ten sposób „uzupełniane są luki” pozostawiane przez bardziej ogólne narzędzia Level One. Bieżący dokument kandydujący do oficjalnego zalecenia W3C znajduje się pod adresem http://www.w3.org/TR/DOM-Level-2/. Trwają już prace nad modelem Level Three (DOM poziomu trzeciego), udostępniającym kolejne narzędzia dla specyficznych typów dokumentów — np. procedury obsługi sprawdzania poprawności dla XML-a.
DOM a Java Aby móc korzystać z modelu DOM w określonym języku programowania, należy zastosować interfejsy i klasy oraz zaimplementować sam model DOM. Ponieważ wykorzystywane metody nie są określone w samej specyfikacji DOM (specyfikacja ta charakteryzuje jedynie model dokumentu), konieczne było opracowanie interfejsów języka reprezentujących konceptualną strukturę modelu DOM — zarówno dla Javy, jak i innych języków. Interfejsy te umożliwiają manipulację dokumentami w sposób określony właśnie w specyfikacji DOM.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 159
160
Rozdział 7. Czerpanie z XML-a
Oczywiście, w tej książce najbardziej interesuje nas interfejs dla Javy. Dowiązania dla tego języka (ang. bindings), określane nazwą DOM Level Two Java bindings, można pobrać ze strony http://www.w3.org/TR/DOM-Level-2/java-binding.html. Klasy, które powinniśmy dodać do ścieżki dostępu do klas, znajdują się w pakiecie org.w3c.dom (i podpakietach). Jednakże zanim je pobierzemy z sieci, warto zerknąć do posiadanego parsera XML i procesora XSLT. Podobnie jak pakiet SAX, DOM jest często dostarczany wraz z tymi narzędziami. W ten sposób mamy również zagwarantowane, że parser, procesor i posiadana wersja DOM poprawnie współpracują ze sobą. Większość procesorów nie generuje samodzielnie danych wejściowych DOM. Korzystają w tym celu z parsera XML, któremu powierza się zadanie wygenerowania drzewa DOM. Dlatego często to parser XML, a nie procesor XSLT będzie posiadał wymagane klasy DOM. Ponadto w ten sposób zapewnia się niezależność obu narzędzi — zawsze można zamienić parser albo procesor na produkt innego producenta. Ponieważ domyślnie Apache Xalan wykorzystuje parser Xerces do przetwarzania i generowania modelu DOM, zajmiemy się tutaj obsługą DOM z poziomu tego narzędzia.
Uzyskanie parsera DOM Aby zorientować się w sposobie działania modelu DOM, powiemy teraz, w jaki sposób procesor Apache Xalan i inne programy wymagające danych wejściowych w formacie DOM otrzymują dokument XML w strukturze drzewiastej DOM. W ten sposób poznamy pierwsze dowiązania języka Java do modelu DOM i wyjaśnimy koncepcje leżące u podstaw obsługi dokumentów XML poprzez model DOM. Model DOM nie określa, w jaki sposób tworzone jest drzewo DOM. Autorzy specyfikacji skoncentrowali się na strukturze i interfejsach API służących do manipulacji tym drzewem; pozostawiono dużą dowolność implementacji parsera DOM. W przeciwieństwie do klasy SAX XMLReader, dynamicznie ładującej implementację, w przypadku DOM to my musimy jawnie zaimportować i stworzyć egzemplarz klasy parsera DOM określonego producenta. Na początek stwórzmy nowy plik w Javie o nazwie DOMParserDemo.java. Zbudujemy prosty program przetwarzający z wykorzystaniem modelu DOM; będzie on wczytywał dokument XML i wyświetlał jego zawartość na ekranie. Zacznijmy od nakreślenia szkieletu naszej przykładowej klasy (przykład 7.2).
Przykład 7.2. Klasa DOMParserDemo // Importujemy parser DOM określonego producenta import org.apache.xerces.parsers.DOMParser; /** * DOMParserDemo pobiera plik XML i wyświetla * jego zawartość za pomocą modelu DOM. * * @author Brett McLaughlin * @version 1.0 */ public class DOMParserDemo { /** *
* Tutaj przetwarzamy plik i drukujemy dokument * za pomocą modelu DOM. *
* * @param uri String URI pliku do przetworzenia. */
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 160
Obiektowy model dokumentu (DOM)
161
public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Tworzymy egzemplarz implementacji parsera DOM danego producenta DOMParser parser = new DOMParser(); try { // parser.parse(uri) } catch (Exception e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } } /** *
* Obsługa wiersza poleceń programu. *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: java DOMParserDemo [XML URI]"); System.exit(0); } String uri = args[0]; DOMParserDemo parserDemo = new DOMParserDemo(); parserDemo.performDemo(uri); } }
Struktura jest podobna do tej wykorzystywanej wcześniej w klasie SAXParserDemo, ale tym razem importujemy bezpośrednio klasę Apache Xerces DOMParser i tworzymy jej egzemplarz. Faktyczną metodę parse() opatrzyliśmy tymczasem komentarzami; zanim zajmiemy się przetwarzaniem struktury DOM, musimy omówić zagadnienia związane z wyborem parsera określonego producenta. Należy pamiętać, że powyższy przykład jest bardzo prosty i działa w wielu aplikacjach, ale nie jest przenośny na inne implementacje parsera, tak jak to było w przypadku SAX-a. Może pojawić się pokusa użycia konstrukcji Javy w rodzaju Class.forName(parserClass).newInstance() i pobrania za jej pomocą klasy parsera odpowiedniego producenta. Jednakże inne implementacje modelu DOM zachowują się rozmaicie — czasem metoda parse() zwraca obiekt org.w3c.dom.Document (o którym powiemy niżej), a czasem konieczne jest wywołanie tej metody z różnymi parametrami (InputSource, InputStream, String, URI itd.). Innymi słowy, drzewo DOM jest konstrukcją przenośną, natomiast metoda uzyskania tego drzewa — już nie, chyba że programista zastosuje dość złożone klasy i metody dynamiczne.
Dane wyjściowe modelu DOM Jak pamiętamy, w interfejsie SAX działanie parsera skoncentrowane było wokół cyklu życia procesu — metody wsteczne oferowały „punkty zaczepienia” danych. Działanie modelu DOM skoncentrowane jest natomiast na danych wyjściowych procesu przetwarzania. Danych nie można użyć, dopóki cały dokument nie zostanie przetworzony i dodany do wyjściowej struktury drzewiastej. Dane wyjściowe z procesu przetwarzania, które mają zostać wykorzystane przez interfejsy
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 161
162
Rozdział 7. Czerpanie z XML-a
DOM, mają postać obiektu org.w3c.dom.Document. Obiekt ten działa jako „procedura obsługi” drzewa, w którym znajdują się nasze dane XML; z punktu widzenia hierarchii elementów, o której cały czas mówimy, obiekt ten znajduje się jeden poziom „ponad” elementem głównym dokumentu XML. Innymi słowy, każdy element naszego wejściowego dokumentu XML jest bezpośrednio lub pośrednio potomny względem niego. Niestety, standaryzacja modelu DOM dotyczy samej obróbki danych, ale nie sposobu ich uzyskania. Powstało więc kilka mechanizmów uzyskiwania obiektu Document po przetwarzaniu. W wielu implementacjach, np. w starszych wersjach parsera IBM XML4J, obiekt Document był zwracany przez metodę parse(). Kod wykorzystujący tego typu implementację wyglądałby następująco: public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Tworzymy egzemplarz implementacji parsera DOM danego producenta DOMParser parser = new DOMParser(); try { Document doc = parser.parse(uri); } catch (Exception e) { e.printStackTrace(); System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } }
Większość nowszych parserów, takich jak Apache Xerces, korzysta z innych sposobów. W celu zachowania standardowego interfejsu w parserach SAX i DOM metoda parse() jest typu void, podobnie jak ta, którą poznaliśmy przy okazji omawiania SAX-a. Dzięki temu w aplikacji można korzystać z klasy parsera DOM i SAX zamiennie; jednakże wiąże się to z koniecznością stworzenia nowej metody, służącej do uzyskania obiektu Document będącego wynikiem przetwarzania XML. W parserze Apache Xerces metoda ta nosi nazwę getDocument(). Jeśli więc korzystamy z tego parsera, to aby uzyskać drzewo wynikowe DOM z procesu przetwarzania, uzupełniamy nasz przykładowy kod następująco: public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Tworzymy egzemplarz implementacji parsera DOM danego producenta DOMParser parser = new DOMParser(); try { parser.parse(uri); Document doc = parser.getDocument(); } catch (Exception e) { e.printStackTrace(); System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } }
Należy również pamiętać o zaimportowaniu odpowiedniej klasy DOM: // Importujemy parser DOM określonego producenta import org.apache.xerces.parsers.DOMParser;
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 162
Obiektowy model dokumentu (DOM)
163
Aby dowiedzieć się, w jaki sposób można uzyskać rezultat przetwarzania, należy zajrzeć do dokumentacji posiadanego parsera. W następnym rozdziale przyjrzymy się interfejsowi API JAXP firmy Sun oraz innym, bardziej ustandaryzowanym sposobom uzyskiwania dostępu do drzewa DOM z dowolnej implementacji parsera. Jak widać, istnieją pewne różnice pomiędzy sposobami uzyskiwania opisywanych danych, natomiast uzyskane dane są zawsze takie same, a więc nie powinniśmy mieć dalszych kłopotów w przypadku używania innego parsera.
Zastosowanie drzewa DOM Skoro już uzyskaliśmy ten „drzewiasty” obiekt, spróbujmy wykorzystać go do czegoś pożytecznego. Na przykład, „przejdziemy” przez strukturę, do której mamy dostęp, i wyświetlimy dane XML. W tym celu najprościej pobrać wstępny obiekt Document i przetworzyć każdy węzeł, oraz — rekursywnie — wszystkie jego węzły potomne. Jeśli Czytelnik kiedykolwiek miał do czynienia ze strukturami drzewiastymi, ta czynność nie powinna stanowić problemu. W celu zrozumienia zasady działania całego procesu, należy przyjrzeć się podstawowym obiektom, przez które zostaną udostępnione nasze dane XML. Poznaliśmy już obiekt Document; ten oraz inne podstawowe interfejsy obiektów DOM zostały zaprezentowane na rysunku 7.2 (są tam również rzadziej wykorzystywane interfejsy). Za pomocą tych właśnie interfejsów będziemy przetwarzali dane w drzewie DOM. Warto dobrze przyjrzeć się przedstawionym interfejsom. Szczególną uwagę należy poświęcić interfejsowi Node — stanowi on interfejs bazowy wszystkich innych. Widząc tego typu strukturę, powinniśmy natychmiast pomyśleć o rozpoznawaniu typu obiektu w czasie pracy programu. Innymi słowy, możliwe jest napisanie metody pobierającej węzeł, rozpoznającej, jakiego typu strukturę DOM ten węzeł stanowi i wyświetlającej węzeł w odpowiedni sposób. W ten sposób możemy wyświetlić całe drzewo DOM za pomocą jednej metody. Kiedy węzeł jest już wyświetlony, korzystamy z dostępnych metod „przenoszących” nas do następnego elementu siostrzanego, pobierających atrybuty (jeśli jest to element) i obsługujących wszelkie możliwe sytuacje specjalne. Następnie, wykonując iterację po węzłach potomnych, rekurencyjnie uruchamiamy metodę na każdym z tych węzłów, dopóki nie wyświetlimy całego drzewa DOM — oto prosty i przejrzysty sposób obsługi drzew DOM.
Do dzieła Ponieważ sam obiekt Document stanowi węzeł Node modelu DOM, możemy przekazać go w postaci niezmienionej do metody wyświetlającej. Zanim stworzymy szkielet takiej metody, musimy jednak dodać odpowiednie instrukcje importujące: import org.w3c.dom.Document; import org.w3c.dom.Node; // Importujemy parser DOM określonego producenta import org.apache.xerces.parsers.DOMParser;
Następnie dodajemy sygnaturę naszej metody, pobierającej i wyświetlającej obiekt DOM Node:
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 163
164
Rozdział 7. Czerpanie z XML-a
Rysunek 7.2. Model klasy UML podstawowych interfejsów i klas DOM Level 2 /** *
* Tutaj wyświetlamy węzeł DOM i przechodzimy * przez wszystkie węzły potomne. *
* * @param node Node obiekt do wyświetlania. * @param indent String białe znaki do wyświetlania * przed Node */ public void printNode(Node node, String indent) { // Rozpoznajemy typ węzła // Wyświetlamy węzeł // Przetwarzamy rekurencyjnie węzły potomne }
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 164
Obiektowy model dokumentu (DOM)
165
Kiedy szkielet metody jest już we właściwym miejscu, możemy uruchomić metodę na początkowym obiekcie Document i przetwarzać rekursywnie dopóty, dopóki nie zostanie wyświetlone całe drzewo. Jest to możliwe właśnie dlatego, że interfejs Document to część wspólnego interfejsu Node: public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Tworzymy egzemplarz implementacji parsera DOM danego producenta DOMParser parser = new DOMParser(); try { parser.parse(uri); Document doc = parser.getDocument(); // Wyświetlamy dokument z drzewa DOM printNode(doc); } catch (Exception e) { System.out.println("Błąd w przetwarzaniu: " + e.getMessage()); } }
Teraz możemy już skompilować plik programu w Javie. Nie uzyskamy żadnych danych wyjściowych, ale przekonamy się chociaż, że stworzenie aplikacji wykorzystującej wejście DOM z parsera XML jest dość proste. W dalszej części spróbujemy nieco uatrakcyjnić nasz program demonstracyjny.
Rozpoznawanie typu węzła Wewnątrz metody wyświetlającej musimy przede wszystkim rozpoznać typ węzła. Moglibyśmy wykorzystać w tym celu metodologię Javy (instanceof i refleksja), ale dowiązania DOM dla Javy znacznie upraszczają ten proces. W interfejsie Node zdefiniowano metodę pomocniczą getNodeType(), zwracającą wartość typu całkowitego. Wartość ta porównywana jest z zestawem stałych zdefiniowanych również w ramach interfejsu Node i w ten sposób rozpoznajemy typ węzła. Takie rozwiązanie w sposób naturalny pasuje do konstrukcji switch Javy — za jej pomocą możemy w naszej metodzie rozbić wydruk na logiczne części. Typ węzła porównujemy z najczęściej spotykanymi typami; co prawda istnieje szereg innych typów (patrz rysunek 7.2), ale te wymienione poniżej są najbardziej popularne, a opisywane rozwiązania w razie potrzeby można zastosować także do pozostałych. public static void printTree(Node node)
{
// Wybieramy czynność do wykonania na podstawie typu węzła switch (node.getNodeType()) { case Node.DOCUMENT_NODE: // Wyświetlamy zawartość obiektu Document break; case Node.ELEMENT_NODE: // Wyświetlamy element i jego atrybuty break; case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: // Wyświetlamy dane tekstowe break; case Node.PROCESSING_INSTRUCTION_NODE: // Wyświetlamy instrukcję przetwarzania (PI)
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 165
166
Rozdział 7. Czerpanie z XML-a break; case Node.ENTITY_REFERENCE_NODE: // Wyświetlamy encję break; case Node.DOCUMENT_TYPE_NODE: // Wyświetlamy Wyświetlamy deklarację DTD break; } }
Warto zauważyć, że węzły CDATASection i Text obsługujemy za pomocą jednej instrukcji case. W tym przykładzie nie będzie nas bowiem interesować, czy w oryginalnym dokumencie tekst znajduje się w części CDATA, czy nie; chcemy go tylko wyświetlić. Teraz w odpowiednich blokach kodu możemy dodać instrukcje wyświetlające i przetwarzające rekurencyjnie.
Typy węzłów DOM Skoro wiemy już, jak wygląda i działa model DOM, wystarczy tylko poznać składnię dla poszczególnych typów węzłów — w ten sposób uzyskamy solidną wiedzę o działaniu modelu. Innymi słowy, teraz możemy już traktować dowiązania Javy dla DOM-a tak jak inne interfejsy API — np. pakiety JNDI czy rozszerzenia serwletów. Zazwyczaj najtrudniejszą częścią nauki języka jest poznanie zasady jego działania; opanowanie składni wymaga już tylko materiałów referencyjnych i przykładowego kodu. W tej części przedstawimy taki właśnie przykładowy kod. Wyświetlimy najczęściej spotykane typy węzłów oraz przyjrzymy się przetwarzaniu drzewa DOM. Później jako materiał referencyjny można wykorzystać dokumentację online modelu DOM, znajdującą się pod adresem http://www.w3.org/DOM; podobną rolę pełni dodatek A, zawierający materiały referencyjne dla interfejsów SAX, DOM i JDOM (o tym ostatnim powiemy w następnym rozdziale).
Węzeł Document Ponieważ Document to część samego węzła Node, możemy używać go wymiennie z innymi typami węzłów. Stanowi on jednak swoisty „przypadek specjalny”, ponieważ zawiera element główny i definicję DTD dokumentu oraz szereg innych specjalnych informacji nie należących do hierarchii elementów XML. Dlatego przetwarzając ten węzeł, musimy uzyskać element główny i przekazać go funkcji wyświetlającej. Wyświetlimy również prostą deklarację wersji, tak aby dokument wyjściowy utrzymywał zgodność ze specyfikacją XML: case Node.DOCUMENT_NODE: System.out.println("\n"); Document doc = (Document)node; printTree(doc.getDocumentElement()); break;
Ponieważ chcemy uzyskać dostęp do metody specyficznej dla obiektu Document, najpierw musimy wykonać rzutowanie implementacji Node przekazanej do metody wyświetlającej na interfejs Document. Później można już uzyskać element główny dokumentu wejściowego XML za pomocą metody getDocumentElement() i przekazać go do metody wyświetlającej, rozpoczynając rekurencyjne przetwarzanie drzewa DOM. Niestety, model DOM Level 2 (podobnie jak SAX 2.0) nie udostępnia deklaracji XML.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 166
Obiektowy model dokumentu (DOM)
167
Wydaje się, że nie jest to duży problem, ale tylko dopóki nie zdamy sobie sprawy, że w deklaracji tej zawarta jest również informacja o kodowaniu dokumentu. DOM Level 3 ma rozwiązać to niedopatrzenie (wersję roboczą specyfikacji udostępniono we wrześniu 2000 r. — przyp. tłum.). Do czasu rozwiązania problemu należy unikać stosowania mechanizmu DOM w tym celu.
Elementy DOM Oczywiście, najbardziej typowym zadaniem wykonywanym w aplikacji będzie pobranie węzła DOM Element i wyświetlenie jego nazwy, atrybutów i wartości, a następnie jego elementów potomnych. Jak można oczekiwać, wszystko to wykonuje się w bardzo prosty sposób metodami DOM. Najpierw pobieramy nazwę elementu (metoda getNodeName() wchodząca w skład interfejsu Node) i wyświetlamy ją. Chwilowo pominiemy obróbkę atrybutów i wydrukujemy nawias zamykający elementu. Następnie odnajdujemy elementy potomne względem bieżącego i również wyświetlamy ich nazwy. Elementy potomne węzła Node są udostępniane poprzez metodę getChildNodes(), zwracającą egzemplarz DOM NodeList. Większość interfejsów API Javy wykorzystuje Vector, Hashtable lub inne zbiory klas obsługujące przetwarzanie list. DOM zwraca natomiast specjalny interfejs, NodeList. Nie możemy oczekiwać, że zaczniemy obsługiwać atrybuty poprzez metody Javy, do których jesteśmy przyzwyczajeni — korzystając z DOM-a, będziemy musieli zaznajomić się z nowymi strukturami.
Uzyskanie długości takiej listy jest zadaniem banalnym; podobnie jest z iteracją po elementach potomnych, wywoływaniem na nich metod wyświetlających i kontynuacją algorytmu rekurencyjnego. Na koniec wstawiamy znacznik zamykający elementu. Najpierw dodajemy wymagany interfejs DOM: import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; // Importujemy parser DOM określonego producenta import org.apache.xerces.parsers.DOMParser;
Teraz wymagane klasy i interfejsy są dostępne „po imieniu” — możemy więc dodać opisane wyżej fragmenty kodu. Uzyskujemy nazwę implementacji Node, drukujemy ją w formacie XML, drukujemy jej elementy potomne (za pomocą wartości null sprawdzając, czy elementy takie istnieją) i zamykamy element. Poniższy kod nie obsługuje jeszcze atrybutów, ale powinien wyświetlić elementy XML z całego drzewa DOM: case Node.ELEMENT_NODE: String name = node.getNodeName(); System.out.print(""); // Rekurencyjne przetwarzanie elementów potomnych NodeList children = node.getChildNodes(); if (children != null) { for (int i=0; i java DOMParserDemo D:\prod\JavaXML\contents.xml Przetwarzanie pliku XML: contents.xml ...
Wynik jest zgodny z oczekiwaniami, wciąż jednak uzyskane informacje nie są ani przejrzyste, ani zbyt przydatne. Bardzo trudno stwierdzić, gdzie elementy zaczynają się, a gdzie kończą — nie ma wcięć, które były w oryginalnym dokumencie. Jak pamiętamy, białe znaki występujące pomiędzy elementami są „obcinane” i zazwyczaj ignorowane przez parsery, tak więc tutaj musimy ponownie je dodać. W tym celu przekażemy prosty łańcuch z „wcięciami” do metody wyświetlającej. Wcięcia będą dodawane w miarę rekurencyjnego przechodzenia po drzewie DOM: /** *
* Tutaj drukujemy węzeł DOM i przechodzimy * przez wszystkie węzły potomne. *
* * @param node Node obiekt do wyświetlania. * @param indent String białe znaki do wyświetlania * przed Node */ public void printNode(Node node, String indent) {
// Wybieramy czynność do wykonania na podstawie typu węzła switch (node.getNodeType()) { case Node.DOCUMENT_NODE: System.out.println("\n");
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 169
170
Rozdział 7. Czerpanie z XML-a Document doc = (Document)node; printNode(doc.getDocumentElement(), ""); break; case Node.ELEMENT_NODE: String name = node.getNodeName(); System.out.print(indent + ""); // Rekurencyjne przetwarzanie elementów potomnych NodeList children = node.getChildNodes(); if (children != null) { for (int i=0; i java DOMParserDemo D:\prod\JavaXML\contents.xml Przetwarzanie pliku XML: contents.xml ...
Teraz można już dodać wartości tekstowe zawarte w elementach.
Węzły tekstowe Czytelnik być może zastanawia się teraz, gdzie w książce zawarte są informacje dotyczące węzłów o wartościach typu całkowitego, numerycznych czy boolowskich. Otóż w niniejszej książce nie ma ich wcale. Jak pamiętamy, wszystkie dane XML w elementach zwracane są przez wywołanie SAX characters(). Już to powinno podpowiedzieć Czytelnikowi, że parser XML „postrzega” wszystkie dane jako tekst i że to aplikacja dokonuje w razie potrzeby konwersji typów. Tak więc aby wyświetlić wartości elementów w DOM-ie, należy skupić uwagę na interfejsach Text i CDATASection. Wyświetlanie wartości jest całkiem proste — korzystamy ze znanej już metody getNodeValue() interfejsu DOM Node: case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: System.out.print(node.getNodeValue()); break;
Po dodaniu tego fragmentu przetwarzanie modelu DOM jest już niemal kompletne. Zanim jednak przejdziemy dalej, przyjrzyjmy się kilku rzadziej używanym, ale równie przydatnym interfejsom DOM (i odpowiadającym im typom Node): ProcessingInstruction, DocumentType i EntityReference — wszystkie występują w naszym dokumencie.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 171
172
Rozdział 7. Czerpanie z XML-a
Instrukcje przetwarzania W ramach dowiązań DOM do Javy zdefiniowano interfejs do obsługi instrukcji przetwarzania występujących w wejściowym dokumencie XML. To bardzo przydatne rozwiązanie, ponieważ instrukcje te są budowane według tego samego modelu znaczników co elementy i atrybuty XML, a mimo to stanowią istotną część XML-a i powinna o nich wiedzieć aplikacja. W naszym przykładowym dokumencie procesorowi XSLT przekazujemy instrukcje informujące o arkuszu stylów, a strukturze publikacji (Apache Cocoon) — instrukcję informującą o tym, jakie przetwarzanie ma zostać wykonane. Jeśli w pliku Czytelnika instrukcje PI są wciąż opatrzone komentarzami, to należy je teraz usunąć:
Węzeł PI w modelu DOM stanowi pewne odstępstwo od schematu, który poznaliśmy — aby zachować zgodność ze składnią interfejsu Node, metoda getNodeValue() zwraca wszystkie instrukcje w PI w postaci jednego łańcucha String. To pozwala w prosty sposób wyświetlić instrukcję PI na ekranie; wciąż jednak do pobrania nazwy PI musimy korzystać z metody getNodeValue(). Gdybyśmy pisali aplikację, która otrzymywałaby instrukcje PI z dokumentu XML, skorzystalibyśmy raczej z interfejsu ProcessingInstruction; dzięki temu uzyskujemy dostęp do takich samych danych, ale nazwy metod (getTarget() i getData()) są bardziej zgodne z formatem PI. Mając to na uwadze, możemy już dodać kod wyświetlający instrukcje przetwarzania: case Node.PROCESSING_INSTRUCTION_NODE: System.out.println(""); break;
Po skompilowaniu i uruchomieniu tak zmienionego programu znów może nas spotkać niespodzianka — żadne instrukcje przetwarzania w dokumencie nie zostały wyświetlone na ekranie! Dlaczego? Otóż kod w takiej postaci uzyskuje obiekt Document z parsera XML i przetwarza tylko element główny wejściowych danych XML. Ponieważ instrukcje przetwarzania znajdują się na tym samym „poziomie” co element główny — są one ignorowane. Aby to poprawić, musimy zmienić część kodu obsługującą węzeł typu Document. Wprowadzimy fragment podobny do tego, jakim obsługiwaliśmy elementy potomne w celu przetworzenia wszystkich struktur XML „najwyższego poziomu”, a nie tylko elementu głównego: case Node.DOCUMENT_NODE: System.out.println("\n"); // Rekurencyjne przetwarzanie elementów potomnych NodeList nodes = node.getChildNodes(); if (nodes != null) { for (int i=0; i
Copyright O'Reilly and Associates, 2000
--> &OReillyCopyright;
Takie zachowania mogą stanowić przyczynę błędu w aplikacji. Cóż więc za pożytek z typu węzła opisującego encję, jeśli i tak jest ona wcześniej przetwarzana przez parser? Otóż węzeł taki powstał z myślą o tworzeniu nowego, a nie przetwarzaniu istniejącego dokumentu XML. To zagadnienie stanowić będzie treść kolejnego podrozdziału.
Mutacje drzewa DOM Czytelnik obeznany z modelem DOM zapewne zauważył, że pominęliśmy jeden bardzo istotny temat — możliwość zmiany drzewa DOM. Możliwe jest proste dodawanie węzłów do drzewa DOM. Właściwie jeśli pominąć prostotę, ta zdolność modyfikacji i dodawania węzłów do drzewa jest faktycznie jedną z najczęściej wykorzystywanych i najbardziej cenionych cech. I w ten sposób powróciliśmy do kwestii transformacji XML-a. Dlaczego model DOM jest tak istotny dla procesora XSLT? Otóż nie tylko chodzi tu o dane wejściowe, udostępniane w prosty sposób; ważne jest także to, że procesor XSLT może tworzyć nowe drzewo DOM na potrzeby dokumentu wyjściowego — a także w prosty sposób kopiować, modyfikować, dodawać i usuwać węzły drzewa wejściowego. Takie przetwarzanie preferowane jest szczególnie w złożonych aplikacjach — często dane wejściowe przekazywane są na drzewo wyjściowe bez rzutowania klas czy tworzenia nowych egzemplarzy obiektów — to istotnie zmniejsza złożoność i zwiększa wydajność procesora XSLT. To dlatego poświęciliśmy tak dużo czasu modelowi DOM. Omówienie mutowania, na które z pewnością czeka Czytelnik, zawarte jest w kolejnych rozdziałach. Teraz omówienie tego tematu oddaliłoby nas od tematu arkuszy XSL, ale w dalszych rozdziałach powrócimy do zagadnień związanych z tworzeniem i modyfikacją XML-a. Zanim jednak przejdziemy do bardziej zaawansowanych rozdziałów, należy omówić pułapki, jakie mogą czyhać na programistę Javy korzystającego z potężnego interfejsu DOM.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 174
Uwaga! Pułapka!
175
Uwaga! Pułapka! Podobnie jak w poprzednich rozdziałach, zostaną tutaj omówione najczęstsze pułapki czyhające na programistów Javy zajmujących się XML-em, ze szczególnym uwzględnieniem modelu DOM. Niektóre z poniższych uwag mają charakter bardziej informacyjny niż praktyczny, ale na pewno przyczynią się do podejmowania właściwych decyzji odnośnie stosowania modelu DOM i pomogą w zrozumieniu, co dzieje się „pod maską” naszych aplikacji XML.
Pamięć i wydajność w modelu DOM Dużo czasu zajęło nam wcześniej omawianie powodów, dla których warto zdecydować się na DOM lub SAX. Już wcześniej podkreślaliśmy, że korzystanie z modelu DOM wymaga wczytania całego dokumentu XML do pamięci i przechowywania go tam w postaci struktury drzewiastej — o tym trzeba koniecznie pamiętać! Niezwykle często zdarza się sytuacja, w której programista ładuje cały zbiór złożonych dokumentów XML do procesora XSLT i zostawia je do przetworzenia w trybie offline. Po powrocie komputer z systemem Windows wyświetla straszliwy „niebieski ekran śmierci”, a Linux zalewa ekran komunikatami o braku pamięci. Powtórzmy więc jeszcze raz — należy uważać na DOM przy przetwarzaniu dużych ilości danych! Korzystanie z modelu DOM wymaga zaangażowania pamięci w ilości proporcjonalnej do rozmiaru i złożoności dokumentu XML. Nie ma sposobu obniżenia wymogów odnośnie pamięci. Co więcej, same transformacje to „zasobożerne” operacje; w połączeniu z wymaganiami pamięciowymi to naprawdę daje dużo do myślenia. Czyż więc programista nie powinien nigdy nie korzystać z DOM-a? Oczywiście, powinien korzystać! Trzeba jednak zachować dużą ostrożność i uważać na to, co przekazujemy do modelu. Jeśli operujemy na niewielkich, mniejszych niż megabajt dokumentach, prawdopodobnie żadne kłopoty nigdy nam się nie przydarzą. Większe dokumenty — podręczniki techniczne czy całe książki — mogą pochłonąć zasoby systemowe i wpłynąć na wydajność aplikacji.
Analizatory DOM zgłaszające wyjątki SAX W naszych przykładach związanych z modelem DOM nie wspomnieliśmy o wyjątkach zgłaszanych w wyniku przetwarzania dokumentu. Wynika to stąd, że — jak mówiliśmy — proces generowania drzewa DOM pozostawiono implementacji parsera, a te różnią się przecież miedzy sobą. Jednakże zawsze dobrze jest przechwytywać specyficzne wyjątki i odpowiednio na nie reagować. Odpowiednia zmiana głównej pętli parsera może być nieco zaskakująca. Oto wersja dla Apache Xerces: /** *
* Tutaj przetwarzamy plik i wyświetlamy dokument * za pomocą modelu DOM. *
* * @param uri String URI pliku do przetworzenia. */ public void performDemo(String uri) { System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n"); // Tworzymy egzemplarz implementacji parsera DOM danego producenta DOMParser parser = new DOMParser(); try { parser.parse(uri); Document doc = parser.getDocument();
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 175
176
Rozdział 7. Czerpanie z XML-a // Wyświetlamy dokument z drzewa DOM // z wcięciem w postaci łańcucha pustego printNode(doc, ""); } catch (IOException e) { System.out.println("Błąd wczytywania URI: " + e.getMessage()); } catch (SAXException e) { System.out.println("Błąd przetwarzania: " + e.getMessage()); } }
Niespodzianką nie jest IOException (wskazuje błąd w lokalizacji podanego URI, podobnie jak w przykładzie SAX). Zastanawia coś innego — możliwość zgłoszenia wyjątku SAXException. Nasz parser DOM zgłasza wyjątek SAX? Chyba zaimportowaliśmy nie te klasy! Nie, klasy są właściwe. Pamiętajmy o tym, co mówiliśmy wcześniej — możliwe jest własnoręczne zbudowanie struktury drzewiastej danych w dokumencie XML za pomocą SAX-a, ale DOM stanowi ciekawą alternatywę. To prawda, ale nie wyklucza to jeszcze użycia interfejsu SAX. Rzeczywiście SAX udostępnia wydajny i szybki sposób przetworzenia dokumentu; akurat w naszym przypadku dokument jest przetwarzany przez SAX i wstawiany do struktury DOM. Ponieważ nie istnieje standard rządzący sposobem tworzenia struktury DOM, takie rozwiązanie jest dopuszczalne, a nawet popularne. Nie należy więc dziwić się, że importujemy i przechwytujemy org.xml.sax.SAXException w aplikacjach DOM.
Co dalej? Rozdziały 1. – 7. stanowią pokaźny zbiór informacji o XML-u. Czytelnik powinien już potrafić posługiwać się XML-em, definicjami DTD i schematami oraz XSLT. To najważniejsze technologie w programowaniu z wykorzystaniem języka XML. W kolejnych rozdziałach pojawią się oczywiście jeszcze inne skróty i specyfikacje, ale omówione zagadnienia to klucz do korzystania z XML-a z poziomu Javy. Oprócz specyfikacji zostały omówione także SAX i DOM, zatem Czytelnik potrafi reprezentować większość danych XML w Javie. Można powiedzieć, że Czytelnik jest już oficjalnie programistą znającym Javę i XML! W następnym rozdziale powrócimy do omawiania interfejsów SAX i DOM; wykażemy ich zalety i wady. Przed zagłębieniem się w kodach aplikacji (czemu poświęcona jest reszta książki) zostanie omówiony interfejs JDOM. Twórcy JDOM starali się naprawić problemy i dokuczliwości związane z interfejsami SAX i DOM. Powstały w ten sposób interfejs API otwiera przed programistą XML-a nowe możliwości.
C:\WINDOWS\Pulpit\Szymon\Java i XML\07-08.doc — strona 176
8 JDOM Nasza podróż po krainie języka XML dobiegła półmetka. Czytelnik powinien już mieć ogólne pojęcie o przydatności opisywanych narzędzi — niektóre z nich są bardzo użyteczne, inne nieco siermiężne. Powinien również umieć korzystać z tych interfejsów i koncepcji w swoich aplikacjach, a przede wszystkim doceniać, jak bardzo może się mu przydać XML. W tym rozdziale — zanim zagłębimy się w specyficzne tematy związane z XML-em — zostaną omówione dodatkowe interfejsy programistyczne łączące Javę i XML. Najpierw omówimy pomocniczy interfejs programistyczny, Java API for XML Parsing (JAXP). Interfejs ten, opracowany przez firmę Sun, udostępnia abstrakcyjną warstwę w procesie uzyskiwania egzemplarza parsera SAX lub DOM; Czytelnik zapewne spostrzegł we wcześniejszych rozdziałach, że zadanie to nie wszędzie zostało ustandaryzowane (szczególnie chodzi tu o DOM) i nie przekłada się na niezależność XML-a względem konkretnego producenta. Po omówieniu interfejsu JAXP zostanie przedstawiony nowy interfejs programistyczny, JDOM. Interfejs ten nie jest spokrewniony z DOM-em w obszarze struktury czy implementacji, ale — podobnie jak DOM — oferuje kompletną reprezentację dokumentu XML. Został jednak stworzony z myślą o konkretnym celu — rozwiązaniu szeregu opisanych już problemów związanych z SAX-em i DOM-em (patrz podrozdziały Uwaga! Pułapka!) i zwiększeniu przydatności i wydajności względem istniejących interfejsów API dla Javy. Czytelnik pozna cel utworzenia, oferowane funkcje i przyszłość tego interfejsu jako alternatywy dla SAX-a, DOM-a i JAXP-a. Najpierw jednak do naszego zestawu narzędzi dodajmy interfejs JAXP.
Parsery i JAXP Czytelnik zainteresowany tematem XML-a i Javy prawdopodobnie natknął się na produkt firmy Sun o nazwie Java API for XML Parsing, najczęściej określanego skrótem JAXP. Wspomnieliśmy o nim również skrótowo w rozdziale 1. Jeśli wziąć pod uwagę, że JAXP jest często wymieniany jednym tchem z SAX-em i DOM-em, może nieco zaskakiwać fakt, że zajmujemy się nim dopiero teraz. Jednakże cały pakiet JAXP, wchodzący w skład javax.xml.parsers, to ledwie sześć klas, z których cztery są abstrakcyjne. Pozostałe dwie opisują wyjątki zgłaszane przez pierwsze cztery. Czytelnik zapewne pamięta, że podczas korzystania z interfejsu DOM (i SAX bez klasy XMLReaderFactory) trzeba jawnie zaimportować i wpisać odwołanie do klasy parsera danego producenta. W Apache Xerces odpowiednie klasy to org.apache.xerces.parsers.SAXParser oraz
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 177
178
Rozdział 8. JDOM
org.apache.xerces.parsers.DOMParser. Problem polega na tym, że zmiana parsera wymaga zmiany kodu aplikacji i rekompilacji. To istotna wada — dobrze byłoby, gdyby parsery można było dołączać na zasadzie „wtyczek”. Do tego właśnie służy interfejs JAXP. Kiedy korzystamy z interfejsu JAXP, to zamiast bezpośrednio importować klasę parsera określonego producenta, definiujemy ją za pomocą właściwości systemowej. JAXP odczytuje tę właściwość i zajmuje się załadowaniem danego parsera. W ten sposób zmiana implementacji parsera wymaga tylko zmiany właściwości systemowej — kod aplikacji wykorzystuje dostarczoną przez firmę Sun warstwę abstrakcyjną.
Współpraca JAXP-SAX Kiedy korzystamy z interfejsu SAX, powinniśmy skorzystać z klas JAXP SAXParser i SAXParserFactory. Pierwsza przejmuje rolę implementacji parsera SAX, a druga obsługuje dynamiczne ładowanie implementacji. Przed omówieniem tych zmian zobaczymy, w jaki sposób można stworzyć abstrakcję konkretnej implementacji parsera XML (przykład 8.1).
Przykład 8.1. Pobieranie implementacji parsera SAX za pomocą JAXP import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import org.xml.sax.helpers.HandlerBase; public class JAXPSAXTest { public void doSomeParsing() { SAXParser parser; SAXParserFactory factory = SAXParserFactory.newInstance(); HandlerBase myHandler = new MyHandlerBase(); factory.setValidating(true); factory.setNamespaceAware(true);
}
try { parser = factory.newSAXParser(); parser.parse(myURI, myHandler); } catch (SAXException e) { // Obsługa błędów SAX } catch (IOException e) { // Obsługa błędów związanych z wczytywaniem URI } catch (ParserConfigurationException e) { // Obsługa błędów związanych z niemożnością // załadowania określonej implementacji parsera } }
Przykład nie różni się specjalnie od tych, które widzieliśmy w poprzednich rozdziałach, z tym że nie ma tutaj kodu specyficznego dla Apache Xerces czy jakiegokolwiek innego konkretnego parsera. Klasa SAXParser przechwytuje egzemplarz wykorzystywanej implementacji parsera i pobiera ten egzemplarz z egzemplarza klasy SAXParserFactory. Jedyne, czym powyższy kod się różni od omawianych wcześniej, to fakt, że sprawdzanie poprawności i „świadomość” przestrzeni nazw włączana jest poprzez SAXParserFactory, a nie przez sam egzemplarz parsera.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 178
Parsery i JAXP
179
Różnica polega na tym, że tutaj wszystkie egzemplarze uzyskują podane właściwości; należy więc pamiętać, aby funkcji nie włączać zbyt wcześnie, i zapomnieć, że jest włączona przy pobieraniu implementacji parsera w dalszej części kodu. Inne odstępstwo od kodu, w którym bezpośrednio korzystaliśmy z implementacji parsera SAX, polega na tym, że do metody parser() klasy SAXParser konieczne jest przekazanie egzemplarza klasy pomocniczej HandlerBase. Tak więc wszystkie programy obsługi zawartości, błędów i inne gromadzone są w jednej podklasie HandlerBase. Należy uważać, aby nie implementować bezpośrednio interfejsów SAX i próbować korzystać z nich indywidualnie (poprzez metody setXXXHandler() dostępne w interfejsie SAX Parser). Jeśli klasa HandlerBase z niczym się Czytelnikowi nie kojarzy, to prawdopodobnie Czytelnik nie zna interfejsu SAX 1.0. Niestety, JAXP obsługuje tylko SAX 1.0 — w SAX-ie 2.0 klasę HandlerBase zastąpiono klasą DefaultHandler. W klasie tej zaimplementowano najważniejsze interfejsy SAX 1.0, udostępniając puste implementacje wszystkich metod zdefiniowanych w ErrorHandler, DTDHandler, EntityResolver i DocumentHandler (który w SAX-ie 2.0 zarzucono na rzecz ContentHandler). W podklasie HandlerBase nadpisujemy wszystkie wywołania, w których ma nastąpić jakieś działanie. Po utworzeniu programu obsługi metoda parse() może zostać wywołana na egzemplarzu SAXParser — jako parametry podaje się identyfikator URI dokumentu do przetworzenia oraz egzemplarz DefaultHandler.
Współpraca JAXP-DOM Podstawy współpracy JAXP-a z DOM-em są takie same jak w przypadku współpracy JAXP — SAX. Klasami analogicznymi do SAXParser i SAXParserFactory są DocumentBuilder i DocumentBuilderFactory, służące do tworzenia drzewa modelu DOM (także w pakiecie javax.xml.parsers). Faktycznie obie te klasy wykorzystują do komunikacji z resztą aplikacji interfejsy API SAX — zgłaszają również te same wyjątki co klasy SAX (w tym SAXException). Specyfikacja JAXP nie wymaga, aby implementacje DOMBuilder używały SAX-a do tworzenia drzewa DOM; wymaga za to wykorzystania interfejsu API w komunikacji z aplikacją. Kod wykorzystujący klasy JAXP DOM (przykład 8.2) jest niemal identyczny z kodem poprzedniego przykładu.
Przykład 8.2. Pobieranie implementacji parsera DOM za pomocą JAXP import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import org.xml.sax.helpers.HandlerBase; public class JAXPDOMTest { public void doSomeParsing() { DocumentBuilder parser; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(true); factory.setNamespaceAware(true); try { parser = factory.newDocumentBuilder(); Document doc = parser.parse(myURI);
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 179
Klasa DocumentBuilderFactory umożliwia włączenie sprawdzania poprawności i „świadomości” przestrzeni nazw; ustawienia te zostaną utrzymane dla wszystkich egzemplarzy DocumentBuilder uzyskanych w wyniku działania metody newDocumentBuilder(). Po określeniu położenia dokumentu do przetwarzania można wywołać metodę parse(), zwracającą obiekt DOM Document, będący wynikiem przetwarzania. Po przetworzeniu można wykorzystać standardowe obiekty i metody DOM — aplikacja jest zupełnie oddzielona od detali związanych z parserem określonego producenta.
Wybór parsera Nie powiedzieliśmy jeszcze o sposobie ustalenia, jaki parser ma być zastosowany. Jak wspomnieliśmy, JAXP ma uprościć zmianę implementacji parsera. Jednakże nie jest to czynność tak prosta, jak mogłoby się wydawać. Ponieważ JAXP zawiera cztery klasy abstrakcyjne, każdy parser obsługujący ten interfejs musi udostępniać implementacje klas JAXP. Na przykład Apache Xerces posiada wymagane klasy w org.apache.xerces.jaxp. Specyfikacja JAXP mówi, że każda implementacja może domyślnie udostępniać dowolny parser; innymi słowy, implementacja Apache Xerces udostępnia jako domyślny parser Apache Xerces, a Oracle — najprawdopodobniej parser Oracle. Zmiana domyślnej klasy parsera na inną może zostać wykonana poprzez ustawienie właściwości systemowej javax.xml.parsers.SAXParserFactory tak, by wskazywała na nowego producenta SAX, lub przez ustawienie javax.xml.parsers.DocumentBuilderFactory tak, by wskazywała na nowego producenta DOM. Właściwości systemowe można ustawiać za pomocą opcji -D w programach uruchamianych z wiersza poleceń lub poprzez System.setProperty() w kodzie Javy. Klasy JAXP odczytują właściwości systemowe i odpowiednio reagują na wywołania newSAXParser() i newDocumentBuilder() — udostępniają egzemplarze klas danego producenta. Jednakże większość współczesnych aplikacji nie jest obsługiwana z wiersza poleceń, ale poprzez interfejs WWW; czasem stanowią one część większego pakietu. Co więcej, użycie System.setProperty() w zwyczajny sposób spowodowałoby odczytanie informacji przekazanych do setProperty() (takich jak nazwa własności i klasa sterownika SAX) z pliku właściwości. Plik taki nie może być plikiem XML (o tym więcej w rozdziale 11.), ponieważ nie istnieje jeszcze odpowiedni parser. Pakiet Java Development Kit (JDK) 1.3 udostępnia możliwość określania właściwości we wdrażanym pliku jar; jednak w czasie pisania tej książki wiele popularnych platform (np. Linux) nie obsługiwało jeszcze JDK 1.3. Innymi słowy, duże możliwości konfiguracyjne w ramach JAXP dopiero powstają. Mimo tych niedogodności, koncepcje przyświecające utworzeniu interfejsu JAXP są niezwykle wartościowe; ponadto firma Sun przekazała niedawno kody JAXP i parsera Project X opiekunom projektu Apache Xerces (kod nosi nazwę „Crimson”), co oznacza, że Sun zamierza przyspieszyć rozwój swojego API i że sprzyja otwartym standardom. Pod koniec roku 2000 można oczekiwać
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 180
JDOM — kolejny API?
181
wersji JAXP 1.1, obsługującej DOM Level 2, SAX 2.0 i umożliwiającej bardziej ogólny sposób wyboru parsera. (Wersja robocza specyfikacji JAXP 1.1 została udostępniona w grudniu 2000 r. — przyp. tłum.).
JDOM — kolejny API? Czytelnik poznał już interfejsy API służące do korzystania z XML-a z poziomu Javy; zostały omówione ich największe wady i zalety. Jednak trudno zafascynować się tym, co mają nam do zaoferowania SAX, DOM i JAXP. O ile społeczność XML-a ma już konieczne do pracy narzędzia, o tyle programiści Javy są nieco zdezorientowani niestandardowymi sposobami zachowania SAX-a i DOM-a oraz ogólną trudnością manipulacji dokumentami XML, a nawet po prostu problemami z uzyskaniem parsera! Dlatego, zgodnie z tradycją oprogramowania open source, projektów związanych z językiem Java i wydawnictwa O'Reilly & Associates, postanowiliśmy naprawić ten błąd i zaprezentować nowe rozwiązanie — interfejs JDOM.
Skąd nazwa? Pierwsi testerzy JDOM byli nieco zaskoczeni nazwą — zbliżoną do DOM, który to interfejs jest z natury bardziej pojemny. Ponieważ jednak JDOM po prostu reprezentuje dokument w Javie, nazwa wydaje się odpowiednia. Innymi słowy, została wybrana ze względu na precyzyjne odwzorowanie przeznaczenia interfejsu mimo podobieństwa do nazwy innego API. Co więcej, JDOM jest tylko luźno związany z XML-em. Obsługuje dowolny hierarchiczny format danych i może równie łatwo podlegać serializacji, jak zwracać w wyniku dane XML — za pomocą obiektów OutputStream lub File oraz klas wyjściowych JDOM. Implementacja JDOM org.jdom.input.Builder udostępnia również sprawny sposób tworzenia obiektu JDOM Document; obiekt ten może zostać utworzony z pliku właściwości w niestandardowym formacie lub w formacie XML. JDOM faktycznie reprezentuje dowolny zbiór danych w Javie. Na interfejs JDOM składa się specyfikacja autorstwa Bretta McLaughlina i Jasona Huntera (K&A Software), utworzona przy współpracy Jamesa Duncana Davidsona (autor specyfikacji JAXP). JDOM jest mało wymagającym mechanizmem do analizowania i przeglądania dokumentu XML. Opisano dane wejściowe i wyjściowe służące do stworzenia obiektu JDOM Document z istniejących danych XML i zwrócenia go w określonym celu. Stworzono działającą implementację w postaci pakietu org.jdom (wersja 1.0) — można go pobrać ze strony pod adresem http://www.jdom.org. Celem stworzenia JDOM było rozwiązanie problemów związanych z interfejsami SAX, DOM i JAXP. Zobaczymy, co oferuje nowy interfejs i czy w ogóle był potrzebny (mało jeszcze tych skrótów?). Twórcy JDOM mieli na celu stworzenie skupionego wokół Javy, wysoko wydajnego zamiennika interfejsów SAX i DOM w większości zastosowań1. Nie jest oparty na DOM-ie czy SAX-ie. Pozwala użytkownikowi pracować na dokumencie XML w postaci drzewiastej bez konieczności 1
Oczywiście, w niektórych przypadkach JDOM nie jest dobrym zamiennikiem większego interfejsu DOM; nie jest obsługiwany przez różne języki programowania i nie udostępnia ściśle określonej reprezentacji drzewiastej oferowanej przez DOM. Jednak przynajmniej w 80% przypadków JDOM potrafi rozwiązać problemy programistów związane z obróbką XML-a.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 181
182
Rozdział 8. JDOM
stosowania specyficznych rozwiązań typowych dla DOM-a. Jednocześnie jest tak wydajny jak SAX — przetwarza i zwraca wynik bardzo szybko. Jest „świadomy” przestrzeni nazw, obsługuje sprawdzanie poprawności poprzez DTD (i będzie obsługiwał schematy, gdy specyfikacja XML Schema zostanie sfinalizowana) i nigdy nie zwraca obiektów w formie NodeList lub Attributes; zamiast tego zwracane są klasy zbiorowe Java 2, takie jak List i Map. W pełni obsługiwane są dodatkowe implementacje, ale sam JDOM składa się z konkretnych (nieabstrakcyjnych) klas, a więc do stworzenia elementów, atrybutów, komentarzy i innych konstrukcji JDOM nie jest potrzebna informacja o producencie.
Spowalnianie specyfikacji To dobrze, że standardy takie jak DOM i SAX są tak rzetelnie weryfikowane przez — odpowiednio — konsorcjum W3C oraz Davida Megginsona i społeczność XML. Jednak często pomiędzy opublikowaniem kolejnych wersji mija dużo czasu. Ponadto coraz większe zainteresowanie oprogramowaniem open source i umożliwienie publicznego dostępu (i niejednokrotnie możliwości modyfikacji) kodu wymaga przyspieszenia procesu weryfikacji i tworzenia jak najświeższych specyfikacji związanych z Javą i XML-em. JDOM, będący w całości oprogramowaniem typu open source, to krok w kierunku przyspieszenia wdrażania standardów i specyfikacji. Na przykład JDOM już teraz obsługuje przestrzenie nazw XML we wszystkich obiektach Document (nawet jeśli Document został zbudowany za pomocą parsera nie znającego przestrzeni nazw!). JDOM zostanie również wkrótce przeniesiony do serwera CVS z dostępem publicznym. To umożliwi modyfikowanie kodu, lepsze jego zrozumienie oraz prostsze aktualizacje. JDOM będzie nieustannie ewoluował w kierunku rozwiązania odpowiadającego większości programistów Javy korzystających z XML-a (serwer CVS JDOM-a już działa — przyp. tłum.).
Zoptymalizowany pod kątem Javy Już wspomnieliśmy, że JDOM to pełny interfejs API oparty na Javie 2, korzystający z klas zbiorowych. Na razie nie ma planów przeniesienia JDOM na inny język; nie jest to więc interfejs standardowy w zakresie obsługi wielu języków, ale za to istotnie zwiększa funkcjonalność w samej Javie, a taki właśnie był cel projektu. Podstawowe klasy JDOM oparte są na języku Java 2, co umożliwiło korzystanie z klas Collection i „słabych” odwołań; dostępna jest jednak również wersja JDK 1.1. Interfejs JDOM został zaprojektowany z myślą o programiście. Jest prosty w nauce i w zastosowaniu, ponieważ utrzymuje zgodność ze sprawdzonymi wzorcami projektowymi Javy. Konstrukcje JDOM (elementy, komentarze, atrybuty itd.) tworzone są w wyniku budowania egzemplarzy obiektów. Dokument XML (jak i dowolny inny) może być postrzegany jako jedna całość i każdy element dokumentu jest przez cały czas dostępny. Tworzenie, usuwanie i modyfikację fragmentów danych XML przeprowadza się za pomocą prostych metod. Obsługę wejścia i wyjścia zapewniają klasy Javy (URL, InputStream, OutputStream, File itd.). Próba unowocześnienia interfejsów SAX i DOM zabrałaby z pewnością więcej czasu aniżeli stworzenie zupełnie nowego rozwiązania. W pozostałej części rozdziału zostanie omówione to właśnie nowe rozwiązanie — Czytelnik dowie się, jak korzystać z niego w celu manipulacji danymi XML z poziomu Javy.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 182
Uzyskiwanie dokumentu
183
Uzyskiwanie dokumentu Pierwszym zadaniem, jakie zawsze należy wykonać przy korzystaniu z JDOM-a, jest uzyskanie obiektu JDOM Document. Obiekt ten stanowi podstawową klasę JDOM, reprezentującą dokument XML. Jak wszystkie inne obiekty w modelu JDOM, klasa org.jdom.Document została opisana w dodatku A; wymieniono tam również sygnatury metod. Pod adresem http:// www.jdom.org dostępna jest też pełna dokumentacja Javadoc.
Istnieją dwa sposoby uzyskania obiektu JDOM Document: utworzenie obiektu od podstaw (kiedy nie ma potrzeby odczytywania istniejących danych XML) lub zbudowanie go z istniejących danych XML.
Tworzenie dokumentu od podstaw Kiedy nie ma potrzeby pobierania istniejących danych, tworzenie dokumentu JDOM Document ogranicza się do wywołania konstruktora: Document doc = new Document(new Element("root));
Jak wspomnieliśmy wcześniej, JDOM to zestaw konkretnych klas, a nie interfejsów. Nie trzeba więc korzystać z bardziej zawiłego kodu związanego z konkretnymi producentami i koniecznego do stworzenia obiektu org.w3c.dom.Element. Wystarczy wykonać operację new na obiekcie Document — powstaje poprawny obiekt JDOM Document, gotowy do użytkowania. Document nie jest też związany z żadnym konkretnym parserem. Dane XML często tworzy się z pustego szablonu, a nie z istniejących danych i JDOM udostępnia nam w tym zakresie konstruktor dla org.jdom.Document, wymagający tylko podania głównego elementu Element jako parametru. W przykładzie 8.3 powstaje właśnie taki dokument XML od podstaw. import org.jdom.Document; import org.jdom.Element; /** *
* Tworzenie obiektu JDOM Document od zera *
* * @version 1.0 */ public class FromScratch { /** *
* Tworzymy prosty dokument XML w pamięci *
*/ public static void main(String[] args) { Document doc = new Document(new Element("root")); System.out.println("Dokument został utworzony"); } }
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 183
184
Rozdział 8. JDOM
Ten fragment kodu tworzy nowy obiekt JDOM Document z elementem głównym (Element o nazwie „root”). Document ten może potem zostać przetworzony w pamięci i wyprowadzony na strumień wyjściowy.
Budowanie obiektu Document z XML-a Częściej jednak korzystamy z pewnych danych wejściowych i dopiero te przekształcamy w obiekt Document. Jest to równie proste. Ponieważ dokumenty JDOM mogą powstawać z wielu źródeł, udostępniony został oddzielny pakiet z klasami tworzącymi obiekt JDOM Document z różnych formatów wejściowych. W pakiecie tym, org.jdom.input, zdefiniowano interfejs Builder, którego metody przedstawione są w przykładzie 8.4.
Przykład 8.4. Interfejs org.jdom.input.Builder public interface Builder { // Tworzenie obiektu JDOM Document z InputStream public Document build(InputStream in) throws JDOMException; // Tworzenie obiektu JDOM Document z File public Document build(File file) throws JDOMException; // Tworzenie obiektu JDOM Document z URL public Document build(URL url) throws JDOMException; }
Programista otrzymuje mechanizm do tworzenia obiektu JDOM Document na podstawie różnych źródeł i budowania różnych implementacji dla różnych formatów wejściowych. Obecnie JDOM udostępnia dwie takie implementacje, SAXBuilder i DOMBuilder2. Umożliwiają one wykorzystanie istniejących obecnie standardowych parserów bez konieczności wprowadzania w nich żadnych zmian.
SAXBuilder Budowanie dokumentu JDOM z istniejącego źródła danych XML za pomocą klasy org.jdom.input.SAXBuilder jest całkiem proste. Konstruktor SAXBuilder pobiera dwa opcjonalne parametry — nazwę klasy parsera SAX do wykorzystania (powinna ona implementować org.xml.sax.XMLReader) oraz znacznik informujący, czy ma nastąpić sprawdzanie poprawności. Jeśli żadnego parametru nie podano, wykorzystywany jest parser domyślny (obecnie Apache Xerces), a sprawdzanie poprawności nie odbywa się. Stwórzmy prostą klasę SAXTest, pobierającą plik z wiersza poleceń i tworzącą obiekt JDOM Document na podstawie pliku i za pomocą klasy SAXBuilder: import java.io.File; import org.jdom.Document; import org.jdom.Element; 2
Być może w czasie, kiedy książka trafi do rąk Czytelników, w interfejsie JDOM 1.0 będą istniały dodatkowe implementacje Builder. Podstawowy interfejs 1.0 jest już „zamrożony”, ale pakiety pomocnicze (org.jdom.input, org. jdom.output i org.jdom.adapters) — nie. Możliwe jest więc, że w czasie powstawania książki pojawią się nowe implementacje (w czasie tłumaczenia, w styczniu 2001 r., wciąż istniały tylko dwie wspomniane implementacje interfejsu Builder — przyp. tłum.).
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 184
Uzyskiwanie dokumentu
185
import org.jdom.JDOMException; import org.jdom.input.Builder; import org.jdom.input.SAXBuilder; public class SAXTest { public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: SAXTest [plik do przetworzenia]"); return; } try { // Budowanie dokumentu bez sprawdzania poprawności Builder builder = new SAXBuilder(false); Document doc = builder.build(new File(args[0])); System.out.println("Dokument został odczytany"); } catch (JDOMException e) { e.printStackTrace(); } } }
Proste, prawda? SAXBuilder obsługuje wszystkie zawiłości związane z tworzeniem różnych klas procedur obsługi SAX, rejestrowaniem ich w implementacji XMLReader i budowaniem obiektu JDOM Document. Przypomina to sposób, w jaki wiele parserów DOM buduje obiekt DOM Document za pomocą parsera SAX; jednak w celu uproszczenia interfejs SAXBuilder w JDOM-ie obsługuje wszystkie wyjątki SAXException i konwertuje je na JDOMException. Kod SAX pozostaje odizolowany od procesu tworzenia obiektu Document, a JDOM gwarantuje, że przekonwertowane wyjątki zawierają informacje o specyficznych problemach, jakie wystąpiły w procesie przetwarzania (wraz z wierszem, w którym wystąpiły).
DOMBuilder Klasa org.jdom.input.DOMBuilder działa niemal tak samo jak SAXBuilder. Z jej pomocą również możemy uzyskać obiekt JDOM Document, ale tym razem za pomocą modelu DOM i parsera DOM. Ponieważ specyfikacja DOM nie definiuje standardu interfejsu parsera, stworzono pakiet org.jdom.adapters, który udostępnia abstrakcyjną warstwę dla parserów określonych producentów i tym samym sposób utworzenia obiektu DOM Document. Konstruktor DOMBuilder pobiera znacznik wskazujący, czy ma nastąpić sprawdzanie poprawności, oraz nazwę klasy adaptera. Może to być dowolna klasa (w tym zdefiniowana przez użytkownika), o ile tylko implementuje interfejs DOMAdapter zdefiniowany w org.jdom.adapters. Po stworzeniu DOMBuilder działa dokładnie tak jak SAXBuilder. Oto sposób tworzenia obiektu Document za pomocą interfejsu DOM: import java.io.File; import import import import import
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 185
186
Rozdział 8. JDOM public class DOMTest { public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: DOMTest [plik do przetworzenia]"); return; } try { // Budowanie dokumentu bez sprawdzania poprawności, parser Oracle Builder builder = new DOMBuilder("org.jdom.adapters.OracleV2DOMAdapter"); Document doc = builder.build(new File(args[0])); System.out.println("Dokument został odczytany"); } catch (JDOMException e) { e.printStackTrace(); } } }
Pakiet org.jdom.adapters został utworzony na potrzeby interfejsu JDOM, ale może być również wykorzystany w aplikacjach korzystających tylko z DOM-a, jako elastyczna alternatywa dla JAXP-a. Oferuje on pełne oddzielenie od procesu przetwarzania DOM i umożliwia dostarczenie danych wejściowych jako InputStream lub w postaci nazwy pliku. Udostępnia także znacznik informujący o sprawdzaniu poprawności. W wyniku działania pakietu zwracany jest gotowy obiekt DOM Document. Innymi słowy, tych klas można użyć w aplikacji w celu zapobieżenia konieczności importowania specyficznych dla DOM-a implementacji parsera. Co więcej, klasy adaptera wykorzystują refleksję, a więc nie wymagają obecności implementacji parsera w ścieżce dostępu do klas w czasie kompilacji. To zapewnia całkowitą konfigurowalność i przenośność aplikacji, jeśli chodzi o zastosowany parser DOM.
Drzewo DOM tworzone jest za pomocą parsera Oracle V2 XML. Następnie w oparciu o to drzewo budowany jest obiekt JDOM Document. Aby wykorzystać parser domyślny, wystarczy wywołać Builder builder = new DOMBuilder(). Wszystkie obecne implementacje parsera DOM do tworzenia drzewa DOM faktycznie wykorzystują SAX. Dlatego korzystanie z interfejsu DOMBuilder w JDOM nie ma raczej sensu; zawsze proces ten będzie wolniejszy niż w przypadku zastosowania SAXBuilder (w obu przypadkach korzystamy z SAX-a), zawsze pochłonie też więcej pamięci, ponieważ na czas konwersji na format JDOM musi w niej powstać pełny obraz drzewa DOM. DOMBuilder nie powinien być więc często wykorzystywany. Jego zasadnicza wartość leży w metodzie, jaką udostępnia do tworzenia obiektu JDOM Document z istniejącego drzewa DOM (np. uzyskanego jako dane wejściowe aplikacji z innej aplikacji, nie obsługującej JDOM-a). Metoda ta, build(org.w3c.dom.Document), jest opisana szczegółowo w dodatku A.
Po utworzeniu dokumentu JDOM Document dalsze działanie programu przebiega w zwykły sposób.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 186
Korzystanie z obiektu Document
187
Korzystanie z obiektu Document Kiedy mamy już obiekt Document (uzyskany albo poprzez bezpośrednie stworzenie egzemplarza, albo poprzez wykorzystanie którejś z klas wejściowych), możemy wykonywać na nim operacje niezależnie od formatu czy interfejsu API. Nie ma dowiązań do SAX-a, DOM-a czy do oryginalnego formatu danych. Jak zobaczymy dalej, nie ma także sprzężenia z formatem wyjściowym. Dowolny obiekt JDOM Document może na wyjściu przybrać postać dowolnego pożądanego formatu! Sam obiekt Document posiada metody do obsługi czterech składników, jakie może przyjąć: DocType (określa zewnętrzną DTD lub udostępnia definicje wewnętrzne), ProcessingInstruction, Element główny i Comment. Każdy z tych obiektów odwzorowuje jakiś fragment XML-a. W ten sposób powstaje reprezentacja tych konstrukcji XML w Javie.
Obiekt DocType Obiekt JDOM DocType to prosta reprezentacja deklaracji DOCTYPE dokumentu XML. Załóżmy, że mamy następujący plik XHTML:
Poniższy kod wydrukuje element, publiczny identyfikator ID oraz systemowy identyfikator ID obiektu JDOM DocType odwzorowującego powyższą deklarację: DocType docType = doc.getDocType(); System.out.println("Element: " + docType.getElementName()); System.out.println("Public ID: " + docType.getPublicID()); System.out.println("System ID: " + docType.getSystemID());
Wynik działania kodu wygląda następująco: Element: html Public ID: -//W3C//DTD XHTML 1.0 Transitional//EN System ID: http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
Interfejs JDOM 1.0 pozwala na odwoływanie się do zewnętrznych definicji DTD, ale nie umożliwia jeszcze wplatania definicji zawężeń (definicje „inline”). Obsługa ta zostanie prawdopodobnie dodana przy okazji kolejnej mniejszej zmiany numeru wersji JDOM. Obiekt DocType może zostać utworzony z nazwą zawężanego elementu (zazwyczaj elementu głównego dokumentu); możliwe jest też podanie publicznego identyfikatora określającego położenie zewnętrznej definicji DTD. Odwołanie do obiektu Document wyglądałoby następująco: Document doc = new Document(new Element("cos:tam")); doc.setDocType(new DocType( "html", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"));
Jeśli JDOM Document konstruowany jest z istniejących danych XML, obiekt DocType jest tworzony automatycznie przez wybraną implementację Builder.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 187
188
Rozdział 8. JDOM
Instrukcje przetwarzania Instrukcje PI w dokumencie XML reprezentuje w Javie klasa ProcessingInstruction, udostępniająca proste akcesory i mutatory. Listę wszystkich instrukcji przetwarzania3 w Document uzyskamy za pomocą następującego kodu: // Pobierz wszystkie PI List pis = doc.getProcessingInstructions(); // Przeanalizuj je wszystkie po kolei, drukując cel i dane for (int i=0, size=pis.size(); i
Możliwe jest wprowadzenie takich par za pomocą klasy ProcessingInstruction i wartości Map: Map map = new HashMap(); map.put("href", "XSL\\JavaXML.wml.xsl"); // znosimy specjalne znaczenie "\" map.put("type", "text/xsl"); map.put("media", "wap"); ProcessingInstruction pi = new ProcessingInstruction("xml-stylesheet", map);
Klasa ProcessingInstruction udostępnia również metody pozwalające na pobieranie danych z instrukcji PI w formacie par nazwa-wartość. Podstawową metodą jest getValue(). Pobiera ona nazwę z pary nazwa-wartość i zwraca odpowiednią wartość lub pusty String, jeśli odpowiednia para nie została znaleziona. Na przykład poniższy kod pozwala określić typ nośnika (medium) dla przedstawionej wyżej instrukcji xml-stylesheet: String mediaType = pi.getValue("media");
Powinien zostać zwrócony String „wap”, który może potem zostać wykorzystany w aplikacji. Ponieważ dane PI nie muszą mieć postaci par nazwa-wartość, istnieje również metoda getDa-
3
Interfejs JDOM obsługuje obiekty ProcessingInstruction zagnieżdżone w obiektach Element, umieszczonych w obiekcie Document. Te zagnieżdżone PI nie zostaną zwrócone przez metody poziomu obiektu Document; ponieważ zagnieżdżone instrukcje przetwarzania są stosunkowo rzadko spotykane, nie będziemy ich tutaj omawiać.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 188
Korzystanie z obiektu Document
189
ta(), zwracająca obiektowi ProcessingInstruction zwykły String. Dodanie ProcessingInstruction do obiektu JDOM Document może zostać wykonane na wiele sposobów: Document doc = new Document(new Element("root")); .addProcessingInstruction( new ProcessingInstruction("instrukcja-1", "jeden sposób")) .addProcessingInstruction("instrukcja-2", "wygodniejszy sposób");
Instrukcje PI dodaliśmy za pomocą metody: addProcessingInstruction(ProcessingInstruction pi)
(podając utworzony obiekt ProcessingInstruction) albo przy użyciu wygodniejszej metody: addProcessingInstruction(String target, String data)
wykonującej to samo zadanie z wykorzystaniem podanych danych.
Elementy Najważniejszymi informacjami w obiekcie Document są zawarte w nim dane, znajdujące się w elementach. Klasa JDOM Element stanowi reprezentację jednego takiego elementu. Zapewnia dostęp do wszystkich znajdujących się w nim danych. Egzemplarz JDOM Element „zna” przestrzenie nazw, dlatego każdą metodę operującą na klasie Element i jego obiektach Attribute można wywołać za pomocą zwykłej nazwy w postaci String lub lokalnej nazwy String odwołania do obiektu Element i Namespace (o tym za chwilę). Innymi słowy, w egzemplarzu Element mamy dostępne następujące metody: // Tworzenie elementu Element element = new Element("nazwaElementu"); // Tworzenie elementu z przestrzenią nazw Element element = new Element ("nazwaElementu", Namespace.getNamespace( "JavaXML", "http://oreilly.com/catalog/javaxml/")); // Dodanie atrybutu element.addAttribute("nazwaAtrybutu"); element.addAttribute("nazwaAtrybutu", Namespace.getNamespace( "JavaXML", "http://oreilly.com/catalog/javaxml/")); // Wyszukiwanie atrybutu o podanej nazwie List attributes = element.getAttributes("poszukiwanaNazwa");
Element główny dokumentu pobierany jest z obiektu JDOM Document za pomocą doc.getRootElement(). Każdy Element posiada metody udostępniające jego elementy potomne (metoda getChildren()). Dla wygody klasa Element udostępnia szereg wariacji metody getChildren() — pobieranie specyficznego elementu z wykorzystaniem jego przestrzeni nazw i nazwy lokalnej, pobieranie wszystkich elementów o określonej nazwie w domyślnej przestrzeni nazw oraz pobieranie wszystkich zagnieżdżonych elementów bez względu na nazwę: public class Element { // Pobierz wszystkie zagnieżdżone obiekty Element tego elementu public List getChildren(); // Pobierz wszystkie zagnieżdżone obiekty Element o określonej nazwie // (w domyślnej przestrzeni nazw)
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 189
190
Rozdział 8. JDOM public List getChildren(String name); // Pobierz wszystkie zagnieżdżone obiekty Element o określonej nazwie // i przestrzeni nazw public List getChildren(String name, Namespace ns); // Pobierz Element o określonej nazwie - jeśli istnieje wiele elementów // o tej nazwie, zwróć pierwszy public Element getChild(String name) throws NoSuchElementException; // Pobierz Element o określonej nazwie - jeśli istnieje wiele elementów // o tej nazwie, zwróć pierwszy public Element getChild(String name, Namespace ns) throws NoSuchElementException; // Inne metody
}
Wersje pobierające określony Element mogą zgłosić wyjątek NoSuchElementException, a jeśli zwracana jest lista List — mogą zwrócić listę pustą. Elementy potomne można pozyskiwać poprzez podanie nazwy (z lub bez przestrzeni nazw); można też pobrać wszystkie „potomki” bez względu na nazwę. Pierwszy sposób wymaga wywołania metody getChild(), a drugi — metody getChildren(). Przyjrzyjmy się następującemu dokumentowi XML: Enligtenment 0.16.2 KWM dla KDE 1.1.2 Sound Blaster Platinum 7 0
Kiedy — podobnie jak w tym przykładzie — struktura dokumentu znana jest z wyprzedzeniem, specyficzny Element, łącznie z jego wartością, może zostać w prosty sposób pobrany z obiektu JDOM Document: Element root = doc.getRootElement(); String menedzerOkien = root.getChild("gui") .getChild("menedzer-okien") .getChild("nazwa") .getContent();
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 190
Należy zauważyć, że w tym wypadku zwrócony zostanie tylko pierwszy element o nazwie menedzer-okien — takie jest domyślne zachowanie metody getChild(String name). Aby pobrać wszystkie elementy o danej nazwie, należy zastosować metodę getChildren(String name): List menedzeryOkien = root.getChild("gui") .getChildren("menadzer-okien");
Kiedy Element zawiera dane tekstowe, mogą one zostać pobrane za pomocą metody getContent(), tak jak w poprzednim przykładzie. Kiedy zawiera tylko elementy potomne, można pobrać je za pomocą metody getChildren(). W raczej rzadko spotykanym przypadku, gdy Element posiada zarówno zawartość tekstową, jak i elementy potomne, a nawet komentarze, mówi się, że element ma zawartość mieszaną (ang. mixed content). Zawartość mieszaną dokumentu można pobrać za pomocą metody getMixedContent(). Metoda ta zwraca listę zawartości zawierającą obiekty String, Element, ProcessingInstruction i Comment. Z technicznego punktu widzenia, metoda getContent() zwraca dane typu String zawarte w obiekcie Element. Te dane mogą się różnić od faktycznej zawartości elementu. Co więcej, metoda getChildren() zwraca tylko zagnieżdżone Elementy, a nie wszystkie obiekty potomne Elementu. Zadanie pobrania całej zawartości elementu wykonuje bardziej skomplikowana metoda getMixedContent(). To upraszcza manipulację plikami XML z punktu widzenia Javy — nie trzeba wykonywać operacji instanceof na wszystkich rezultatach zwracanych przez metody. Nazwy metod nie są więc może technicznie precyzyjne, ale za to odpowiadają „intuicyjnemu” zachowaniu programistów.
Elementy są często dodawane do innych przy użyciu metody addChild(Element). Można dodać wiele elementów naraz: element .addChild(new Element("synek").setContent("czyste zloto")) .addChild(new Element("corka").setContent("diabel wcielony") .addChild(new Element("wnuczek")) );
Taki skrótowy zapis jest możliwy, ponieważ addChild() zwraca Element, do którego dodano element. Należy bardzo uważnie wstawiać nawiasy. W przeciwnym razie to, co miało być rodzeństwem, może okazać się rodzicem i dzieckiem! Elementy potomne usuwa się za pomocą metod removeChild() i removeChildren(), przyjmujących takie same parametry jak getChild() i getChildren(). Elementy konstruowane są poprzez podanie nazwy. Obecność przestrzeni nazw wymagała utworzenia czterech różnych konstruktorów: // Pobierz odwołanie do przestrzeni nazw Namespace ns = Namespace.getNamespace( "JavaXML", "http://oreilly.com/catalog/javaxml/");
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 191
192
Rozdział 8. JDOM // Utwórz element JavaXML:Ksiazka Element element1 = new Element("Ksiazka", ns); // Utwórz element JavaXML:Ksiazka Element element2 = new Element("Ksiazka", "JavaXML", "http://oreilly.com/catalog/javaxml/"); // Utwórz element Ksiazka Element element3 = new Element("Ksiazka", "http://oreilly.com/catalog/javaxml/"); // Utwórz element Ksiazka Element element4 = new Element("Ksiazka");
Pierwsze dwa egzemplarze Element, element1 i element2, posiadają równoznaczne nazwy, jako że klasa Element obsłuży składowanie podanej nazwy i przestrzeni nazw. Trzeci egzemplarz, element3, przypisywany jest domyślnej przestrzeni nazw i przestrzeń ta opisywana jest identyfikatorem URI. Czwarty egzemplarz tworzy Element bez przestrzeni nazw. Zawartość elementu ustawiana jest za pomocą metody setContent(String content). Jej wywołanie powoduje nadpisanie istniejącej zawartości elementu, w tym elementów potomnych. Aby dodać String jako „dodatkowy fragment” mieszanej zawartości elementu, korzystamy z metody addChild(String content). Bardzo przydatną cechą interfejsu JDOM jest możliwość dodawania i usuwania elementów poprzez dokonywanie zmian w liście zwróconej z wywołania getChildren(). Poniżej usuniemy ostatnie „niegrzeczne dziecko” (i w ten sposób damy przykład innym): // Pobierz element główny Element root = doc.getRootElement(); // Pobierz wszystkie "niegrzeczne" potomki List niedobreDzieci = roog.getChildren("niegrzeczne"); // Pozbądź się ostatniego niegrzecznego dziecka if (niedobreDzieci.size() > 0) { niedobreDzieci.remove(niedobreDzieci.size()-1); }
Klasy zbiorowe Javy 2 udostępniają szybkie metody sortowania, a więc chociaż metody działające na obiektach JDOM są wygodne w użyciu, w zaawansowanych aplikacjach warto przetwarzać obiekty List bezpośrednio. Teraz możemy zająć się dodaniem odwzorowania przestrzeni nazw do obiektu Document oraz dodaniem i udostępnieniem atrybutów.
Przestrzenie nazw Zalecenie odnośnie przestrzeni nazw w XML-u definiuje odwzorowanie przedrostków na identyfikatory URI. Aby można było korzystać z przedrostka przestrzeni nazw, musi on zostać odwzorowany na identyfikator poprzez atrybut xmlns:[przedrostek]. Odwzorowanie to w JDOMie odbywa się automatycznie przy tworzeniu danych wyjściowych. Czytelnik już wie, że przestrzenie nazw XML obsługiwane są poprzez klasę org.jdom.Namespace: Namespace ns = Namespace.getNamespace("przedrostek", "uri");
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 192
Korzystanie z obiektu Document
193
Obiekt ns może zostać następnie wykorzystany przez obiekty Element i Attribute. Ponadto klasa Namespace utworzy nowe obiekty w razie potrzeby; zażądanie istniejącej przestrzeni nazw zwróci odwołanie do istniejącego obiektu.
Atrybuty Do pobrania atrybutu elementu służy metoda getAttribute(String name). Metoda ta zwraca obiekt Attribute, którego wartość pobierana jest za pomocą getValue(). W poniższym kodzie pobierany jest atrybut „rozmiar” danego elementu: element.getAttribute("rozmiar").getValue();
Dostęp do atrybutu jako specyficznej wartości można uzyskać za pomocą dodatkowych metod: getIntValue(), getFloatValue(), getBooleanValue() i getByteValue(). Metody te zwracają DataConversionException, jeśli wartość nie istnieje lub nie może zostać przekonwertowana na żądany typ. Metody te mają również swoje odpowiedniki umożliwiające przekazanie wartości domyślnej, która zostanie zwrócona zamiast zgłaszania powyższego wyjątku w razie niemożności przeprowadzenia konwersji. Poniższy fragment pobiera rozmiar jako int, a zwraca 0, jeśli nie można przeprowadzić konwersji: element.getAttribute("rozmiar") .getIntValue(0);
Dodanie atrybutów do elementu jest równie proste. Atrybut może zostać dodany za pomocą metody addAttribute(String name, String value) lub za pomocą bardziej formalnej metody addAttribute(Attribute attribute). Konstruktor Attribute pobiera nazwę atrybutu do utworzenia (jako parametr String lub przedrostek przestrzeni nazw i nazwę lokalną) oraz wartość, jaką należy przypisać utworzonemu atrybutowi: doc.getRootElement() .addAttribute("kernel", "2.2.14") // prosty sposób .addAttribute("new Attribute("dist", "Red Hat 6.1)); // formalny sposób
Komentarze Obiekt JDOM Comment reprezentuje dane nie stanowiące funkcjonalnych informacji obiektu Document, a służące jedynie do podwyższenia czytelności programu. W XML-u informacje takie oznacza się . Komentarze w JDOM reprezentowane są przez klasę Comment; egzemplarze tworzone są na poziomie dokumentu lub jako elementy potomne. Innymi słowy, zarówno obiekt JDOM Document, jak i elementy mogą posiadać komentarze. Aby uzyskać komentarze dokumentu, należy zastosować getContent(), zwracającą listę zawierającą wszystkie obiekty Comment dokumentu, jak również element główny. Komentarze umieszczone przed elementem głównym pojawią się na liście przed elementem głównym; a te po nim — znajdą się w dalszym miejscu listy. Aby uzyskać komentarze dla danego Elementu, należy wywołać metodę getMixedContent(), która zwraca wszystkie obiekty Comment, Element i String (dane tekstowe), zagnieżdżone wewnątrz elementu i w takiej kolejności, w jakiej występują w dokumencie. Załóżmy, że mamy następujący plik XML: Java i XML
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 193
194
Rozdział 8. JDOM Czytamy sobie spis treści!
Zazwyczaj komentarze nie są używane przez aplikację. Gdyby jednak zaszła taka potrzeba, poniższy kod pozwoli nam je „wyciągnąć” z dokumentu: List docContent = doc.getContent(); List elemContent = root.getMixedContent(); for (int i=0, size=docContent.size(); i Java i XML Wprowadzenie
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 199
200
Rozdział 8. JDOM Co to jest? ... ---------------------Testuję odczytywanie dokumentu z SAX... ----------------------
Java i XML Wprowadzenie ...
Taki wynik został uzyskany po uruchomieniu programu testującego na pliku contents.xml, utworzonym we wcześniejszych rozdziałach. Element JavaXML:Dodatkowe (i elementy potomne) został opatrzony komentarzami, ponieważ definicja DTD nie pozwala na jego istnienie. Jeśli nie byłoby komentarzy, program zgłosiłby następujący komunikat o błędzie (kiedy przy tworzeniu egzemplarza SAXBuilder lub DOMBuilder zażądano by sprawdzania poprawności): org.jdom.JDOMException: Error in building from stream: Error on line 59 of XML document: Element type "JavaXML:Dodatkowe" must be declared. at org.jdom.input.DOMBuilder.build(DOMBuilder.java, Compiled Code) at org.jdom.input.DOMBuilder.build(DOMBuilder.java, Compiled Code) at JDOMTest.domDocument(JDOMTest.java, Compiled Code) at JDOMTest.main(JDOMTest.java, Compiled Code) Error on line 59 of XML document: Element type "JavaXML:Dodatkowe" must be declared.
Jedną z istotnych cech interfejsu JDOM jest diagnozowanie błędów — powyższy komunikat dostarcza szczegółowych informacji o błędach w danych wejściowych XML i tym samym upraszcza sprawdzanie ich poprawności. Ponieważ metody wejścia i wyjścia zajmują tylko cztery wiersze kodu (patrz domDocument() i saxDocument()), JDOM może zostać wykorzystany właśnie do sprawdzania poprawności i formatowania dokumentów XML. Nadeszła pora, aby przypomnieć sobie programy SAXParserDemo i DOMParserDemo z rozdziałów 3. i 7. Oba te programy wyswietlały dokumenty XML pobrane z pliku wejściowego; SAXParserDemo udostępniał podgląd samego procesu przetwarzania, zaś DOMParserDemo to właściwie klasa „ładnie drukująca” dokument XML. Przykład 8.7 to kod źródłowy com.oreilly.xml.PrettyPrinter, klasy narzędziowej wykonującej to samo zadanie co DOMParserDemo, ale poprzez JDOM.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 200
Dokument wyjściowy
Uważny Czytelnik mógł zauważyć, że w powyższym kodzie nie zajęliśmy się sprawdzaniem poprawności ani przestrzeniami nazw. Ponieważ JDOM obsługuje przestrzenie nazw wewnętrznie (a nie polegając na informacjach dostarczanych przez DOM Level 2 czy SAX 2.0), sprawdzanie poprawności odbywa się przy jednoczesnej obsłudze przestrzeni nazw. JDOM faktycznie wyłącza „świadomość” przestrzeni nazw w klasach SAXBuilder i DOMBuilder! Nie tylko umożliwia to przeprowadzenie sprawdzania poprawności, ale także przyspiesza przetwarzanie dokumentów XML.
Przykład 8.7. Klasa narzędziowa com.oreilly.xml.PrettyPrinter package com.oreilly.xml; import java.io.File; import import import import
/** * PrettyPrinter wyświetla dokument XML spod podanego URI * * @author Brett McLaughlin * @author Jason Hunter * @version 1.0 */ public class PrettyPrinter { /** *
* Ładnie drukuje XML danego URI *
*/ public static void main(String[] args) { if (args.length != 1) { System.out.println("Użycie: " + "java com.oreilly.xml.PrettyPrinter [XML_URI]"); return; } String filename = args[0]; try { // Budowanie dokumentu z użyciem SAX i Xerces, bez sprawdzania poprawności Builder builder = new SAXBuilder(); // Tworzenie dokumentu (ze sprawdzaniem poprawności) Document doc = builder.build(new File(filename)); // Wyświetlenie dokumentu, standardowy program formatujący XMLOutputter fmt = new XMLOutputter(); fmt.output(doc, System.out); } catch (Exception e) { e.printStackTrace(); } } }
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 201
201
202
Rozdział 8. JDOM
Szybki przegląd interfejsu JDOM mamy już za sobą. Czytelnik poznał jedynie ułamek jego możliwości. Pełna dokumentacja znajduje się w dodatku A — opisane są tam klasy i interfejsy DOM oraz dostępne w nich metody. Opisano tam również pakiety dodatkowe dla JDOM-a, org. jdom.adapters, org.jdom.input i org.jdom.output. Interfejsu JDOM będziemy używali w przykładach w pozostałej części książki. Wielokrotnie powrócimy też do różnic pomiędzy interfejsami JDOM, SAX i DOM, aby w razie potrzeby Czytelnik umiał skorzystać z dowolnego z nich i aby na koniec sam wyrobił sobie opinię na temat ich przydatności. Najnowsza wersja JDOM i odpowiadająca jej dokumentacja Javadoc znajduje się pod adresami: http://www.jdom.org i http://www.newInstance.com.
Co dalej? W niniejszym rozdziale Czytelnik poznał interfejs JDOM. Znając również JAXP, może przystąpić już do tworzenia specyficznych aplikacji korzystających z XML-a. Nadal korzystając z interfejsów SAX, DOM i JDOM, w drugiej połowie książki omówimy struktury publikacji WWW, aplikacje typu firma-firma, XML-RPC, Rich Site Summary (RSS), konfiguracje XML i szereg spokrewnionych z nimi zagadnień. Poznawane wiadomości pozwolą Czytelnikowi tworzyć nowe, bardziej efektywne aplikacje.
C:\WINDOWS\Pulpit\Szymon\Java i XML\08-08.doc — strona 202
9 Struktury publikacji WWW Czytelnik poznał już podstawy korzystania z XML-a z poziomu języka Java. Omówione zostały interfejsy SAX i DOM, umożliwiające manipulację i tworzenie danych XML. Czytelnik poznał również JDOM, udostępniający bardziej naturalny dla programistów Javy sposób korzystania z danych XML. Skoro Czytelnik wie już, jak używać języka XML z poziomu programów, pora zająć się konkretnymi aplikacjami. W następnych sześciu rozdziałach przedstawione zostaną najistotniejsze zastosowania języka XML i, co najważniejsze, omówione zostaną sposoby implementowania tych aplikacji w Javie. Są dosłownie setki, a wkrótce będą tysiące ważnych zastosowań języka XML, ale tutaj opiszemy tylko te najważniejsze — te, które mogą zrewolucjonizować proces tworzenia oprogramowania. Jako pierwsze zostaną omówione aplikacje będące przedmiotem najgorętszych dyskusji w społecznościach XML-a i Javy — struktury publikacji WWW. Jak to już zostało wspomniane, możliwość tworzenia prezentacji z zawartości jest raczej przereklamowana — dużo ważniejsza jest przenośność danych oferowana przez XML. Nie zmienia to jednak faktu, że XML świetnie nadaje się również do generowania wartości prezentacyjnej. Jest to cecha szczególnie istotna w aplikacjach WWW. Za pięć lat praktycznie dowolna duża aplikacja będzie albo całkowicie wykorzystywała możliwości WWW, albo będzie przynajmniej udostępniała „webowy” interfejs. Użytkownicy domagają się coraz bogatszych zestawów funkcji, a działy marketingowe — coraz większej elastyczności w wyglądzie i zachowaniu aplikacji. Powstał nowy zawód — artysta WWW; to nie to samo co webmaster — nie musi dobrze znać Perla, ASP, JavaScriptu czy innych języków skryptowych. Cały dzień nic nie robi, tylko tworzy, modyfikuje i projektuje dokumenty HTML. Gwałtowne zmiany na rynku wymagają często kompletnej przebudowy witryny nawet raz w tygodniu, więc takie stanowisko jest bardzo potrzebne. Style kaskadowe (CSS) okazały się tutaj bardzo pomocne, ale i tak utrzymanie spójności wielu stron wymaga bardzo dużo czasu. Taką sytuację na siłę można by zaakceptować, ale na pewno żaden programista nie chciałby spędzić całego życia na wprowadzaniu zmian w stronach HTML. Wraz z popularyzacją programów Javy uruchamianych po stronie serwera, ten problemstał się bardziej dokuczliwy. Twórcy serwletów długie godziny spędzają wyłącznie na poprawianiu instrukcji out.println() zwracających dane HTML. Ta sytuacja doprowadziła do powstania
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 203
204
Rozdział 9. Struktury publikacji WWW
specyfikacji Java Server Pages (JSP); jednakże JSP nie stanowi rozwiązania — przenosi jedynie ciężar na programistę HTML-a, który cały czas musi unikać wprowadzania zmian w zagnieżdżonym w stronach kodzie Javy. Co więcej, w JSP nie ma obiecanego jednoznacznego oddzielenia warstwy danych i prezentacji. Oczekiwano więc sposobu generowania czystych danych i — później — nakładania jednolitego stylu o określonej porze (generowanie statyczne) lub bezpośrednio w czasie działania (dynamiczne generowanie zawartości). Czytelnik już wie, że powyższe problemy rozwiązują technologie XSL i XSLT. Jest jednak pewien problem — aby generować zawartość, szczególnie dynamicznie, trzeba dysponować odpowiednim mechanizmem. Co z tego, że na serwerze mamy setki dokumentów XML, jeśli nie ma mechanizmu umożliwiającego przekształcenie ich do potrzebnej postaci. Jeśli dodamy do tego potrzebę istnienia serwletów i innych elementów znajdujących się po stronie serwera, umożliwiają tworzenie wynikowych danych XML, to otrzymujemy zespół wymagań określający właśnie strukturę publikacji WWW. W niniejszym rozdziale przyjrzymy się takim właśnie strukturom. Czytelnik będzie mógł przekonać się, że dzięki nim zniknie potrzeba „ślęczenia nad HTML-em”. „Artyści WWW” staną się specjalistami od XML-a i XSL-a, co na pewno wyjdzie im na dobre, zaś wygląd i zachowanie aplikacji będzie można zmienić w dowolnej chwili i w dowolny sposób. Struktura publikacji WWW (ang. web publishing framework) stanowi rozwiązanie wszystkich opisywanych wyżej problemów. Tak jak serwer WWW odpowiada na żądanie pobrania pliku, struktura publikacji WWW odpowiada na żądanie publikowanej wersji pliku. Publikowany plik (ang. published file) to plik przekształcony za pomocą XSLT, stworzony na poziomie aplikacji lub przekształcony z innego formatu (np. PDF). Żądający nie widzi danych, z których powstał taki plik; nie musi również jawnie żądać wykonania publikacji. Często z identyfikatora URI (np. http://naszHost.com/publish) wynika, że w serwerze WWW uruchomiony jest mechanizm publikacji. Jak można podejrzewać, sama koncepcja jest dużo prostsza niż faktyczna implementacja, a wybranie odpowiedniej struktury publikacji dla własnych potrzeb to wcale nie łatwe zadanie.
Wybór struktury Czytelnik z pewnością zaczyna już doceniać znaczenie struktur publikacji. Rozwiązań jest jednak bardzo wiele. Język Java oferuje prosty sposób współpracy z rozmaitymi narzędziami XML wykorzystywanymi w strukturach publikacji. Serwlety Javy umożliwiają natomiast prostą obsługę żądań i odpowiedzi WWW. Ale lista struktur publikacji jest krótka, a lista tych dobrych i stabilnych — jeszcze krótsza. Jednym z najlepszych zasobów informacji o tego typu oprogramowaniu jest lista XML Software dostępna pod adresem http://xmlsoftware.com/publishing. Ta lista zmienia się tak często, że nie warto jej tutaj przytaczać. Tym niemniej istnieje szereg istotnych kryteriów, którymi programista powinien się kierować przy wyborze struktury publikacji — i właśnie one zostaną tutaj omówione.
Stabilność Obecnie programiści mają problemy ze znalezieniem produktu w wersji o numerze wyższym niż 2.x. Mało tego — ze świecą trzeba szukać struktury publikacji nawet drugiej generacji! Oczywiście, wyższy numer wersji nie jest jeszcze gwarancją stabilności, ale często odzwierciedla ilość czasu, wysiłków i rzetelność testowania, jakiemu poddano daną strukturę. System publikacji XML to na tyle nowa technologia, że rynek zalewany jest produktami w wersjach 1.0 i 1.1 — często po prostu niewystarczająco stabilnymi do praktycznego użycia.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 204
Wybór struktury
205
O stabilności produktu mogą również świadczyć inne, stabilne produkty tego samego producenta. Często producent wypuszcza cały pakiet narzędzi; jeśli inne narzędzia nie obsługują interfejsów SAX 2.0 i DOM Level 2 albo wszystkie są w wersjach 1.0 lub 1.1, to strukturę publikacji pochodzącą z takiego pakietu najlepiej na razie wykluczyć z wyboru i poczekać, aż nieco „dojrzeje” i będzie zgodna z nowszymi standardami XML. Należy również unikać technologii przeznaczonych dla konkretnej platformy — jeśli struktura publikacji została scalona z konkretnym systemem (np. Windows), to nie jest to już „czyste” rozwiązanie w Javie. Pamiętajmy, że struktura publikacji ma obsługiwać klientów ze wszystkich platform; dlaczego więc mamy zadowalać się produktem, który nie może zostać uruchomiony na dowolnej platformie?
Integracja z innymi narzędziami i interfejsami XML Oprócz stabilności, struktura publikacji musi również obsługiwać różne parsery i procesory XML. Jeśli jest związana z konkretnym parserem lub procesorem, to tak naprawdę jest to XML-owa wersja Microsoftu — ograniczamy się do jednej konkretnej implementacji technologii. Struktury publikacji mogą dobrze „integrować się” z parserem konkretnego producenta, ale zawsze należy sprawdzić, czy w razie potrzeby parser można zmienić. Jeśli mamy jakiś ulubiony procesor (albo musimy korzystać z procesora przejętego po kimś razem z projektem), trzeba upewnić się, czy struktura z nim współpracuje. Obsługa interfejsów SAX i DOM to bezwzględna konieczność. Warto też szukać takiej struktury, której producent monitoruje specyfikacje XML Schema, XLink, XPointer i inne technologie XML. Od takiego producenta można oczekiwać poprawek struktury mających na celu obsłużenie tych nowych technologii; ponadto mamy gwarancję, że takiej publikacji nie będziemy musieli zmieniać przez długi czas. Należy śmiało pytać o to, kiedy zostanie uruchomiona obsługa nowej specyfikacji.
Wdrożenia w środowiskach produkcyjnych Jedną z najważniejszych przesłanek ułatwiających wybór struktury publikacji jest fakt zastosowania jej w aplikacjach produkcyjnych. Jeśli producent nie potrafi pokazać nam przynajmniej kilku aplikacji referencyjnych lub serwisów wykorzystujących określoną strukturę, to całkiem możliwe, że... po prostu takie nie istnieją. Producenci (oraz programiści z „branży” open source) uwielbiają chwalić się miejscami, w których ich aplikację można zobaczyć w działaniu. Jeśli wyczuwamy tutaj pewne wahanie, to być może aplikacja jest jeszcze zupełnie pionierska — a przecież nie o to nam chodzi.
Podejmujemy decyzję Kiedy już Czytelnik rozpatrzy wszystkie powyższe kryteria, wybór struktury publikacji nie będzie szczególnie trudny — tylko niewiele rozwiązań jest zgodnych z postawionymi wymaganiami, a dochodzą przecież jeszcze wymogi naszej konkretnej aplikacji. W czasie pisania tej książki struktury publikacji obsługujące najświeższe wersje SAX-a, DOM-a i JAXP-a, wdrożone przynajmniej w jednej aplikacji produkcyjnej i mające za sobą przynajmniej trzy znaczące poprawki kodu, można było policzyć na palcach jednej ręki. Nie wymieniamy ich tutaj, bo — co tu dużo mówić — po pół roku mogą już nie istnieć, mogą też zostać zupełnie zmienione. Krajobraz struktur publikacji zmienia się tak gwałtownie, że próba rekomendacji tego czy tamtego produktu może raczej przeszkodzić niż pomóc. Jednakże jedna struktura publikacji nieustannie się sprawdza i jest szczególnie doceniana przez programistów Javy i XML-a. Struktura ta jest szczególnie istotna dla programistów pracujących na zasadach open source oraz piszących w Javie. Chodzi o projekt Apache Cocoon, zapoczątkowany
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 205
206
Rozdział 9. Struktury publikacji WWW
przez Stefano Mazzocchiego — od samego „poczęcia” niezwykle solidną strukturę publikacji. Została opracowana dość dawno, kiedy większość z nas próbowała rozgryźć, co to w ogóle jest ten XML. Cocoon, struktura publikacji oparta w całości na Javie, wchodzi już w drugą fazę rozwoju. Stanowi część projektu Apache XML i domyślnie obsługuje Apache Xerces i Apache Xalan. Pozwala na użycie dowolnego standardowego parsera XML i wykorzystuje niezwykle popularną architekturę serwletów Javy. Jest wykorzystywana w wielu serwisach produkcyjnych (w wersji 1.x) i sprawuje się świetnie. Dlatego właśnie — a także znów konsekwentnie wybierając oprogramowanie open source — w tej książce wybieramy Apache Cocoon. W poprzednich rozdziałach kwestia wyboru parsera i procesora XML była w znacznym stopniu otwartą; przykłady wymagałyby tylko niewielkich modyfikacji w przypadku korzystania z innych implementacji. Jednakże struktury publikacji WWW nie zostały znormalizowane — każda z nich implementuje różne funkcje i konwencje. Dlatego opisane w niniejszym rozdziale przykłady wykorzystujące Apache Cocoon nie są przenośne; jednakże popularność koncepcji i wzorców projektowych zastosowanych w Apache Cocoon usprawiedliwiają poświęcenie całego rozdziału tej właśnie strukturze. Jeśli Czytelnik wybiera inną strukturę publikacji, to powinien przynajmniej przejrzeć opisane tutaj przykłady — ogólne koncepcje są na pewno przenośne, nawet jeśli kod będzie działał tylko w jednej implementacji.
Instalacja We wcześniejszych rozdziałach instrukcja instalacji ograniczała się do wskazania strony WWW, z której należało pobrać oprogramowanie, oraz do przypomnienia o konieczności dodania odpowiedniego pliku jar do ścieżki dostępu do klas. Instalacja struktury publikacji, takiej jak Cocoon, nie jest już tak banalnym zadaniem, dlatego cała procedura została opisana szczegółowo. Jeśli chcemy uzyskać najświeższą wersję struktury, powinniśmy pobrać ją z systemu współbieżnych wersji CVS. Będzie to kod prosto z repozytorium kodów źródłowych, a nie publikowana rzadziej wersja „oficjalna”. Oprogramowanie CVS można pobrać spod adresu http://www.cyclic.com/cyclic-pages/howget.html. W tej książce zajmiemy się wersją 1.x struktury Cocoon. Kiedy książka będzie w sprzedaży, wersja beta 2.0 będzie już prawdopodobnie dostępna (według informacji na stronie http://xml.apache.org/cocoon/cocoon2.html w czasie tłumaczenia książki Cocoon 2.0 był wciąż w fazie „alfa” i twórcy stanowczo odradzali jego użycie w aplikacjach produkcyjnych — przyp. tłum.); jednak z powodu dużych zmian planowanych w tej nowej generacji tutaj skupimy się na wersjach 1.x, obecnie używanych najpowszechniej. Na końcu rozdziału zostaną pokrótce omówione nowe funkcje, jakie pojawią się w Cocoon 2. Jeśli w czasie instalacji czytelnik napotka problemy, może skorzystać z zasobów online. Projekt Cocoon gości na stronach projektu Apache XML, pod adresem http://xml.apache.org. Dostępne są tam listy adresowe (http://xml.apache.org/mail.html), a także bardzo przydatne odpowiedzi na najczęściej zadawane pytania FAQ (http://xml.apache.org/cocoon/faqs.html). Nie trzeba obawiać się zadawania pytań — instalacja złożonych struktur publikacji to niełatwe zadanie, a zawsze istnieje duża szansa, że ktoś już przez to przebrnął i może służyć radą.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 206
Instalacja
207
Korzystanie z narzędzia Ant Weterani systemów uniksowych i Linuksa znają na pamięć poniższe polecenia wykonywane przy kompilacji oprogramowania: /home/bmclaugh (mejis)> ./configure /home/bmclaugh (mejis)> make /home/bmclaugh (mejis)> make install
Współpraca kodu źródłowego i programów make oraz autoconf ma długie tradycje. Ale make nie współpracuje zbyt dobrze z kodem w Javie — użytkownicy Windows muszą korzystać z dodatkowych narzędzi przy kompilacji na tej platformie; uruchomienie dokumentacji Javadoc i innych dodatkowych poleceń wymaga długiego procesu konfiguracji; kompilacja RMI (rmic) jest także złożona itd. Rozwiązanie świetnie spisujące się w przypadku Perla, skryptów powłoki czy kodu C, w zetknięciu z Javą traci na użyteczności. Na szczęście James Duncan Davidson (związany z projektami Jakarta, JAXP i specyfikacją serwletów) nie poprzestał na narzekaniu na make. Zapoczątkował projekt znany obecnie pod nazwą Ant i stanowiący część projektu Apache Jakarta. Ant to narzędzie do budowania oprogramowania oparte na Javie. Jego konfiguracja oparta jest na XML-u i jest to narzędzie niezależne od platformy. Kompilacje RMI, Javadoc, polecenia zewnętrzne — wszystko to jest obsługiwane w tym jednym środowisku. Do „zbudowania” źródeł Cocoon posłuży nam właśnie Ant. Nowa wersja narzędzia Ant została zawarta w pakiecie Cocoon, w katalogu lib/. Ant można również pobrać ze strony Jakarty, http://jakarta.apache.org. Sposób użycia Anta w przypadku oprogramowania Cocoon został zawarty w tym ostatnim; informacje bardziej ogólne znaleźć można pod wymienionym adresem projektu Jakarta.
Uzyskanie pakietu Cocoon Kiedy narzędzie Ant mamy już pod ręką, możemy pobrać źródła Cocoon 1.x. Oprócz możliwości pobrania ich ze stron projektu Apache XML (http://xml.apache.org), najświeższą wersję, wyposażoną we wszystkie nowe funkcje, znaleźć można także w systemie CVS. Jeśli Czytelnik dopiero zaczyna używać oprogramowania Cocoon, może skorzystać z pakietu przygotowanego do pobrania; jednak teraz już Czytelnik zna kod Javy i XML-a, więc może warto pobrać zupełnie najświeższą wersję 1.x.dev z repozytorium CVS. Można to zrobić w następujący sposób: cvs -d :pserver:[email protected]:/home/cvspublic login Password: ******* (Haslo: 'anoncvs') cvs -d :pserver:[email protected]:/home/cvspublic checkout xml-cocoon
W katalogu xml-cocoon pojawi się najnowsza wersja oprogramowania Cocoon. Zawiera ona plik sterujący kompilacją przez Anta, wszystkie wymagane biblioteki i właściwe źródła. Wystarczy teraz przejść do tego katalogu i już można rozpocząć budowanie struktury Cocoon.
Budowanie struktury Cocoon Znajdujemy się w katalogu głównym oprogramowania Cocoon. Aby zbudować strukturę w systemie Windows, należy wpisać następujące polecenie: D:\dev\xml-cocoon> build.bat
natomiast w systemach Unix i Linux: $ sh build.sh
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 207
208
Rozdział 9. Struktury publikacji WWW
Podkatalog lib/ zawiera wszystkie biblioteki konieczne do zbudowania projektu Cocoon. Skrypty instalacyjne dodadzą każdy plik jar tego katalogu do ścieżki dostępu do klas, zawierającej najnowsze wersje Apache Xerces, Apache Xalan i innych programów niezbędnych do prawidłowego działania Cocoon 1.x. Nawet jeśli Czytelnik już ma te biblioteki (Xerces czy Xalan), zalecane jest zastosowanie tych dostarczonych z Cocoon — producent gwarantuje ich współpracę z wersją pobraną z CVS-u. Po skończeniu instalacji ścieżka dostępu do klas będzie obejmowała następujące biblioteki: • JDK Tools: tools.jar • Jakarta Ant: ant.jar • Servlet API 2.2: servlet_2_2.jar • Apache Xerces: xerces_x_y_z.jar • Apache Xalan: xalan_x_y_z.jar • Apache FOP1: fop_x_y_z.jar • Apache Stylebook2: stylebook-x.y-z.jar Skrypt budujący informuje następnie narzędzie Ant, że projekt ma zostać zainstalowany z wykorzystaniem pliku build.xml, znajdującego się w bieżącym katalogu. Po jego wykonaniu Czytelnik powinien uzyskać następujący wynik: Cocoon Build System ------------------Building with classpath lib/ant_1_1.jar:lib/bsf.jar:lib/bsfengines.jar:lib/fop_0_13_0.jar:lib/ servlet_2_2.jar:lib/stylebook-1.0-b2.jar:lib/turbine-pool.jar:lib/ xalan_1_2_D02.jar:lib/xerces_1_2.jar:/usr/local/jdk1.2.2/lib/tools.jar: Starting Ant... Buildfile: build.xml init: ------------------- Cocoon 1.8 [1999-2000] --------prepare: [mkdir] Created dir: /home/adam/cocoon-1.8/build prepare-projectx: prepare-xt: prepare-ecma: prepare-ldap: prepare-src: [mkdir] Created dir: /home/adam/cocoon-1.8/build/src [mkdir] Created dir: /home/adam/cocoon-1.8/build/classes [copydir] Copying 126 files to /home/adam/cocoon-1.8/build/src
1
Chodzi o ten sam procesor FOP (procesor obiektów formatujących), który został przedstawiony w rozdziale 6., Przekształcanie XML-a, przy okazji opisywania konwersji XML-a na formaty nietekstowe.
2
Stylebook obsługuje tworzenie bardzo złożonych dokumentów (w tym w HTML-u); służy do generowania dokumentacji i strony WWW Cocoon. Projekt Stylebook ma być zintegrowany z Cocoon w wersji 2.0.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 208
Instalacja
209
compile: [javac] Compiling 108 source files to /home/adam/cocoon-1.8/build/classes [javac] Copying 18 support files to /home/adam/cocoon-1.8/build/classes package: [jar] Building jar: /home/adam/cocoon-1.8/build/cocoon.jar BUILD SUCCESSFUL Total time: 3 minutes 14 seconds
Uzyskany wynik może się nieco różnić numerami wersji lub liczbą plików, ale nie powinny wystąpić żadne błędy; jeśli wystąpiły, należy poprawić ścieżkę dostępu do klas zgodnie z komunikatami zgłaszanymi przez program i ponownie uruchomić skrypt budujący. Po zakończeniu procesu cała dystrybucja Cocoon powinna się znaleźć w jednym pliku cocoon.jar w podkatalogu build/. Należy również sprawdzić, czy posiadamy przykładowy plik właściwości dostarczany z projektem, cocoon.properties, w katalogu bin/. Jeśli go tam nie ma, być może znajduje się w podkatalogu build/classes/org/apache/cocoon/. Z pomocą Anta można także wygenerować dokumentację Javadoc i wykonać inne zadania związane z projektem. Wykonuje się je poprzez określenie celu (ang. target) polecenia budującego. Cele to słowa kluczowe dostarczane jako argumenty programu Ant; kompletna lista obsługiwanych celów dla oprogramowania Cocoon znajduje się w pliku build.xml. Celem do utworzenia dokumentacji jest docs, a dla Javadoc — javadocs. Na przykład, aby zbudować dokumentację, należy wykonać polecenie: Building with classpath lib/ant_1_1.jar:lib/bsf.jar:lib/bsfengines.jar:lib/fop_0_13_0.jar:lib/ servlet_2_2.jar:lib/stylebook-1.0-b2.jar:lib/turbine-pool.jar:lib/ xalan_1_2_D02.jar:lib/xerces_1_2.jar:/usr/local/jdk1.2.2/lib/tools.jar: Starting Ant... Buildfile: build.xml init: ------------------- Cocoon 1.8 [1999-2000] --------prepare-docs: [replace] Replacing ./xdocs/dtd/ --> dtd/ [replace] Replacing ./xdocs/dtd/ --> dtd/ docs: [mkdir] Created dir: /home/adam/cocoon-1.8/build/docs ...
W ten sposób pełna dokumentacja projektu znajdzie się w podkatalogu build/docs/. Po przygotowaniu projektu można przejść do konfigurowania mechanizmu serwletów pod kątem korzystania z niego.
Konfiguracja mechanizmu serwletów Trzeba poinformować mechanizm serwletów o oprogramowaniu Cocoon oraz wskazać, które żądania ma do tego oprogramowania przekazywać. Skonfigurujemy Cocoon do współpracy z mechanizmem serwletów Jakarta Tomcat3 — jest to implementacja referencyjna interfejsu Java 3
W przykładach wykorzystano wersję 3.1, ale Cocoon wymaga tylko wersji 2.x tego interfejsu serwletów. Opcje konfiguracyjne nie powinny się zmienić w innych wersjach; innymi słowy, instrukcje podane w tym rozdziale powinny „działać” w dowolnej wersji 3.x Tomcata.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 209
210
Rozdział 9. Struktury publikacji WWW
Servlet API 2.2. Czytelnik powinien potrafić uzyskać podobne wyniki także w innych mechanizmach serwletów. Struktura Cocoon działa na poziomie mechanizmu serwletów, a nie jako kolejny serwlet. Dlatego trzeba dodać Cocoon oraz współpracujące z nim mechanizmy do ścieżki dostępu do klas samego mechanizmu serwletów, a nie do określonej strefy serwletów czy kontekstu. Kopiujemy pliki jar Cocoon, Xerces, Xalan i FOP do podkatalogu lib/ głównego katalogu instalacyjnego Tomcat. Następnie biblioteki te dodajemy do ścieżki dostępu do klas mechanizmu serwletów; w tym celu w mechanizmie Tomcat edytujemy plik inicjalizacyjny. W systemie Windows jest to plik /bin/tomcat.bat, a w systemach Unix /bin/tomcat.sh. W plikach tych znajdziemy wiersze ustawiające ścieżkę dostępu do klas odczytywane przez Tomcat w czasie uruchamiania. Należy tam dodać ścieżki dostępu do klas Cocoon i programów towarzyszących — tak, by odczytywane były przed innymi wpisami w tym pliku4. W systemie Windows wpisy te wyglądają następująco: set CLASSPATH=. set CLASSPATH=%TOMCAT_HOME%\classes rem set set set set
Klasy i biblioteki Cocoon CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xerces_1_2.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xalan_1_2_D02.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\fop_0_13_0.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\cocoon.jar
a w systemach uniksowych: set CLASSPATH=. # Klasy i biblioteki Cocoon CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xerces_1_2.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xalan_1_2_D02.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/fop_0_13_0.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/cocoon.jar for i in ${TOMCAT_HOME}/lib/* ; do CLASSPATH=${CLASSPATH}:$i done
Ważne jest, aby klasy Cocoon poprzedzały pozostałe klasy Tomcat, a w szczególności xml.jar. Xerces, Xalan i Cocoon wykorzystują interfejs SAX 2 i DOM Level 2, zaś parser Sun Project X zawarty w xml.jar — jeszcze nie. Jeśli ta klasa i jej wersje SAX-a i DOM-a zostaną znalezione jako pierwsze, wystąpi błąd w strukturze publikacji Cocoon. Po dodaniu bibliotek trzeba jeszcze „poinformować” Cocoon o położeniu jego pliku właściwości (potem Czytelnik dowie się, do czego faktycznie służy ten plik). Należy skopiować plik cocoon.properties z katalogu Cocoon do /conf/. W tym samym katalogu jest umieszczony plik web.xml, opisujący właściwości i odwzorowania globalne serwletów w mechanizmie. Tutaj należy wskazać konfigurację struktury Cocoon. Wpisujemy tam następujące dane: 4
W najnowszych wersjach dla systemów uniksowych Tomcat sam odnajduje klasy skopiowane do lib/, dlatego podawanie ścieżek dostępu nie jest konieczne; trzeba jednak wtedy pamiętać, że pliki z tego katalogu będą odczytywane w kolejności zgodnej z kolejnością alfabetyczną nazw i w razie potrzeby odpowiednio zmienić te nazwy — przyp. tłum.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 210
Nie ma znaczenia, w którym miejscu pliku zostanie wstawiony powyższy fragment, o ile tylko nie naruszy to zagnieżdżenia elementów (ostateczny plik musi być poprawnym plikiem XML). Należy także zmienić na właściwą ścieżkę dostępu do pliku cocoon.properties, będącą wartością parametru properties. Parametr ten jest przekazywany przez mechanizm serwletów do serwleta Cocoon, co umożliwia poprawną jego konfigurację. Parametr servlet-mapping informuje mechanizm, że żądania kończące się rozszerzeniem .xml mają być przeadresowywane do struktury Cocoon. Po wprowadzeniu zmian należy wykonać restart serwera Tomcat i sprawdzić, czy w czasie uruchamiania nie występują żadne błędy. Jeśli wszystko zostało skonfigurowane poprawnie, można wyświetlić w przeglądarce stronę http:// :/Cocoon.xml. Jeśli nie wystąpiły żadne błędy, czytelnik powinien ujrzeć stronę podobną do tej na rysunku 9.1 (w zależności od rodzaju przeglądarki może się różnić formatowanie tabeli). Jeśli wystąpiły jakieś błędy, Czytelnik powinien ujrzeć komunikaty definiujące problem. Zazwyczaj kłopoty wynikają z nieobecności wymaganych klas w ścieżce dostępu do klas, w nieprawidłowym określeniu położenia pliku właściwości lub w niemożności jego odczytania przez serwlet. Ewentualne błędy naprawić i ponownie uruchomić mechanizm. Po uzyskaniu żądanego efektu Czytelnik jest gotów do wypróbowania działania struktury Cocoon i skonfigurowania jej w taki sposób, by obsługiwała różne typy żądań.
Korzystanie ze struktury publikacji Korzystanie z dobrej struktury publikacji w rodzaju Cocoon nie wymaga jakichś specjalnych instrukcji — nie jest to złożona aplikacja, której obsługi trzeba się najpierw nauczyć. Wszystko właściwie sprowadza się do wpisania prostego adresu URL do standardowej przeglądarki WWW. Generowanie dynamicznych stron HTML z XML-a, przeglądanie danych XML przekształconych na format PDF, a nawet aplikacje VRML generowane z XML-a — wszystko to uzyskać można poprzez wpisanie adresu URL pliku XML do przeglądarki; resztą zajmie się już Cocoon.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 211
212
Rozdział 9. Struktury publikacji WWW
Rysunek 9.1. Strona konfiguracji Cocoon
Przeglądanie plików XML przekonwertowanych na HTML Mając poprawnie działającą strukturę publikacji, możemy za jej pomocą wyświetlić posiadane pliki XML. Wraz z oprogramowaniem Cocoon dostarczane są przykładowe pliki XML i towarzyszące im arkusze stylów XSL (w podkatalogu samples/). My jednak mamy już własnoręcznie utworzone pliki XML i XSL, a więc spróbujmy przekształcić fragment spisu treści XML za pomocą arkusza stylów XSL utworzonego w rozdziale 6. Plik XML powinien nosić nazwę contents.xml (można go również pobrać ze strony WWW niniejszej książki). Należy skopiować go do katalogu głównego dokumentów mechanizmu serwletów. W przypadku domyślnej instalacji Tomcata jest to katalog /webapps/ROOT/. Dokument odwołuje się do arkusza stylów XSL/ JavaXML.html.xsl, a więc trzeba jeszcze w katalogu głównym utworzyć katalog XSL i skopiować tam arkusz utworzony w rozdziale 6. Należy sprawdzić, czy definicja DTD w dokumencie XML jest opatrzona komentarzami (pamiętajmy, sprawdzanie poprawności rzadko ma sens w zastosowaniu produkcyjnym), a także zamienić encję OReillyCopyright na HTML, tak jak to zostało opisane w rozdziale 6. Cocoon obsługuje sprawdzanie poprawności i encje zewnętrzne, ale na razie nie należy zaprzątać sobie głowy zbyt wieloma szczegółami. Teraz można już załadować plik, wpisując adres http://:/contents.xml. Jeśli Czytelnik wprowadził modyfikacje opisywane w rozdziale 6., przekształcony plik XML powinien wyglądać tak jak na rysunku 9.2.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 212
Korzystanie ze struktury publikacji
213
Rysunek 9.2. Plik XML z rozdziału 6. po przekształceniu
Pliki PDF utworzone z danych XML Do tej pory mówiliśmy niemal wyłącznie o przekształcaniu dokumentów XML na HTML; w pozostałych przypadkach zakładaliśmy, że dane będą przesyłane pomiędzy dwoma aplikacjami. Format był całkowicie umowny, jako że zarówno aplikacja wysyłająca, jak i otrzymująca przekształcały dane XML za pomocą określonej definicji DTD lub schematu. Jednakże struktura publikacji oferuje o wiele więcej możliwości. Nie tylko obsługiwane jest przekształcanie na liczne języki znaczników; dostępne są również biblioteki przekształcające XML na niektóre formaty nie wykorzystujące znaczników. Najbardziej popularną i stabilną biblioteką tej kategorii jest Formatting Objects Processor (FOP) grupy Apache XML, który pokrótce został już omówiony w rozdziale 6. Pozwala on na przekształcanie dokumentów XML na pliki PDF, których oglądanie umożliwia oprogramowanie Adobe Acrobat (http://www.adobe.com). Trudno przecenić możliwość przekształcania danych XML na dokumenty PDF; szczególnie w serwisach operujących na dokumentach, takich jak firmy drukarskie czy wydawnictwa, technologia ta może zrewolucjonizować operacje na danych. Poniższy przykład przedstawia dokument XML opisujący fragment oryginalnej, angielskiej wersji rozdziału 1. niniejszej książki (przykład 9.1).
Przykład 9.1. Wersja XML rozdziału 1.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 213
214
Rozdział 9. Struktury publikacji WWW Java and XML Brett McLaughlin XML. These three letters have brought shivers to almost every developer in the world today at some point in the last two years. While those shivers were often fear at another acronym to memorize, excitement at the promise of a new technology, or annoyance at another source of confusion for today's developer, they were shivers all the same. Surprisingly, almost every type of response was well merited with regard to XML. It is another acronym to memorize, and in fact brings with it a dizzying array of companions: XSL, XSLT, PI, DTD, XHTML, and more. It also brings with it a huge promise-what Java did for portability of code, XML claims to do for portability of data. Sun has even been touting the rather ambitious slogan "Java + XML = Portable Code + Portable Data" in recent months. And yes, XML does bring with it a significant amount of confusion. We will seek to unravel and demystify XML, without being so abstract and general as to be useless, and without diving in so deeply that this becomes just another droll specification to wade through. This is a book for you, the Java developer, who wants to understand the hype and use the tools that XML brings to the table. Today's web application now faces a wealth of problems that were not even considered ten years ago. Systems that are distributed across thousands of miles must perform quickly and flawlessly. Data from heterogeneous systems, databases, directory services, and applications must be transferred without a single decimal place being lost. Applications must be able to communicate not only with other business components, but other business systems altogether, often across companies as well as technologies. Clients are no longer limited to thick clients, but can be web browsers that support HTML, mobile phones that support Wireless Application Protocol (WAP), or handheld organizers with entirely different markup languages altogether. Data, and the transformation of that data, has become the crucial centerpiece of every application being developed today.
Czytelnik wie już, jak przekształcać taki dokument na dokument HTML. Ale przekształcenie całego rozdziału książki do tego formatu dałoby w wyniku ogromny dokument HTML, bardzo trudny w odczycie; potencjalny Czytelnik na pewno wolałby otrzymać dokument PDF. Z drugiej strony, jeśli generujemy statycznie PDF, to musimy zapewnić, że zmiany wprowadzone w rozdziale zostaną odzwierciedlone w nowym pliku PDF. Przechowywanie pliku w jednolitym formacie XML umożliwia prostą jego aktualizację (za pomocą dowolnego edytora XML), formatowanie do SGML-a w celu wydrukowania, przesyłanie do innych firm i aplikacji oraz dołączanie do innych książek lub kompendiów. Jeśli dodamy do tego prostotę użytkowania (wystarczy wpisać w przeglądarce adres URL), to otrzymujemy kompletny system publikacji.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 214
Korzystanie ze struktury publikacji
215
Nie będziemy szczegółowo omawiali obiektów formatujących i interfejsu FOP — definicja takich obiektów znajduje się w specyfikacji XSL pod adresem http://www.w3.org/TR/xsl/. W przykładzie 9.2 pokazano arkusz stylu XSL, który umożliwia przekształcenie danych XML na dokument PDF.
Przykład 9.2. Arkusz XSL konwertujący dane z przykładu 9.1 na dokument PDF type="text/xslfo"
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 215
216
Rozdział 9. Struktury publikacji WWW
Po utworzeniu obu powyższych plików i zapisaniu rozdziału jako plik chapterOne.xml oraz arkusza jako JavaXML.fo.xsl w katalogu XSL/, można obejrzeć wyniki transformacji w przeglądarce. Przeglądarka musi być wyposażona we wtyczkę (ang. plug-in) Adobe Acrobat Reader, co umożliwi bezpośrednie obejrzenie pliku w przeglądarce (inaczej trzeba byłoby go zachować i przejrzeć przy użyciu innego programu). Wynikowy dokument PDF przedstawiony jest na rysunku 9.3.
Styl zależny od przeglądarki Oprócz jawnego określania typu transformacji (np. na format PDF), Cocoon umożliwia także przekształcanie w zależności od rodzaju żądania. Typowym przykładem takiego zachowania jest formatowanie zależne od programu klienta. W tradycyjnym środowisku WWW dokument XML może więc zostać przekształcony w różny sposób, zależnie od tego, czy klient korzysta np. z przeglądarki Internet Explorer czy Netscape. Biorąc pod uwagę ostatnie przepychanki firm Netscape i Microsoft odnośnie wersji HTML-a i DHTML-a, jest to na pewno bardzo przydatna funkcja. Cocoon fabrycznie obsługuje wiele popularnych typów przeglądarek. Poniżej ptrzedstawiony jest końcowy fragment pliku cocoon.properties. ########################################## # User Agents (Browsers) # ########################################## # # # # # #
NOTE: numbers indicate the search order. This is very important since some words may be found in more than one browser description. (MSIE is presented as "Mozilla/4.0 (Compatible; MSIE 4.01; ...") for example, the "explorer=MSIE" tag indicates that the XSL stylesheet associated to the media type "explorer" should be mapped to those browsers
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 216
Korzystanie ze struktury publikacji
217
Rysunek 9.3. Dokument PDF powstały z przykładów 9.1 i 9.2 # that have the string "MSIE" in their "user-Agent" HTTP header. browser.0 = explorer=MSIE browser.1 = pocketexplorer=MSPIE browser.2 = handweb=HandHTTP browser.3 = avantgo=AvantGo browser.4 = imode=DoCoMo browser.5 = opera=Opera browser.6 = lynx=Lynx browser.7 = java=Java browser.8 = wap=Nokia browser.9 = wap=UP browser.10 = wap=Wapalizer browser.11 = mozilla5=Mozilla/5 browser.12 = mozilla5=Netscape6/ browser.13 = netscape=Mozilla
Słowa kluczowe znajdujące się po pierwszych znakach równości (np. explorer, opera, lynx, java, netscape) określają agenty użytkowników i kody, jakie są przez nie wysyłane wraz z żądaniem. Spróbujmy wykorzystać te właściwości w przykładowym pliku arkusza XSL, stosowanym w przypadku, gdy klientem jest Internet Explorer. Kopiujemy nasz oryginalny arkusz stylów JavaXML.html.xsl do JavaXML.explorer-html.xsl i wprowadzamy zmiany przedstawione w przykładzie 9.3.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 217
218
Rozdział 9. Struktury publikacji WWW
Przykład 9.3. Arkusz stylu dla przeglądarki Internet Explorer (wersja dla Explorera) Table of Contents (wersja dla Explorera) Natychmiast pobierz Netscape! ...
To oczywiście dość banalny przykład, ale dobrze obrazuje prostotę zmiany sposobu wyświetlania dokumentu i np. wysyłania innych danych do przeglądarki Internet Explorer 5.0, która szerzej obsługuje DHTML niż Netscape Navigator. Konieczne jest jeszcze poinformowanie dokumentu XML, że jeśli typ agenta użytkownika pasuje do zdefiniowanego we właściwościach typu explorer, to należy użyć innego arkusza XSL. Można to wykonać za pomocą instrukcji przetwarzania przedstawionej w przykładzie 9.4.
Przykład 9.4. Dokument XML odwołujący się do wielu arkuszy stylów w zależności od klienta
Jeśli dokument obejrzymy za pomocą Netscape Navigatora, uzyskamy takie wyniki jak poprzednio, a jeśli za pomocą Internet Explorera — uzyskamy dokument przekształcony za pomocą innego arkusza stylu (rysunek 9.4.).
WAP i WML Potężne możliwości takich dynamicznych arkuszy stylów wiążą się również z wykorzystaniem urządzeń bezprzewodowych. Oto znany już Czytelnikowi fragment pliku właściwości Cocoon:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 218
Rysunek 9.4. „Explorerowa” wersja wygenerowanej strony HTML Wyróżnione wpisy pozwalają wykryć agenta bezprzewodowego, np. telefon z dostępem do Internetu. Cocoon, tak jak wcześniej rozpoznawał przeglądarkę, teraz wysyła arkusz stylu odpowiedni dla urządzenia WAP. Do tej pory nie przywiązywaliśmy dużej wagi do poniższego fragmentu naszego pliku XML, określającego arkusz stylu używanego w przypadku takiego urządzenia:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 219
220
Rozdział 9. Struktury publikacji WWW
Teraz jednak omówimy go szczegółowo. W urządzeniach WAP wykorzystywany jest zazwyczaj język Wireless Markup Language (WML). Jest to odmiana HTML-a, różniąca się sposobem reprezentacji poszczególnych stron. Kiedy urządzenie bezprzewodowe wysyła żądanie pobrania pliku spod adresu URL, zwrócona treść musi być zawarta w znaczniku wml. W tym elemencie głównym definiuje się szereg kart (ang. cards) — każdą opisuje element WML card. Urządzenie pobiera wiele kart jednocześnie (określane są one mianem pliku kart (ang. deck of cards)), dzięki czemu nie musi ponownie odwoływać się do serwera w celu wyświetlenia kolejnych ekranów. W przykładzie 9.5 pokazano przykładową stronę XML wykorzystującą takie konstrukcje.
Przykład 9.5 Prosta strona WML
Menu główne Strona tytułowa Moja strona
Witam na mojej stronie domowej! Tak miło was tu gościć.
Hello World!
Na żądanie treści WML zwrócona zostałaby odpowiedź w postaci menu i dwóch ekranów zawartości, dostępnych poprzez odsyłacze z menu. Pełna specyfikacja WML 1.1 dostępna jest pod adresem http://updev.phone.com/dev/ts/; wcześniej trzeba się nieodpłatnie zarejestrować pod adresem http://updev.phone.com. Z tej witryny można również pobrać oprogramowanie UP.SDK, emulujące urządzenie bezprzewodowe. Wystarczy w nim wpisać adres http://:/contents.xml i obejrzeć wynik działania arkusza XSL dla urządzenia WAP. Ponieważ wyświetlacze telefonów są bardzo małe, będziemy chcieli wyświetlić jedynie podzbiór informacji zawartych w naszym spisie treści. W przykładzie 9.6 przedstawiono arkusz wyświetlający trzy karty w WML-u. Pierwsza to menu z odsyłaczami do dwóch pozostałych. Druga karta to wynik generacji spisu treści z dokumentu contents.xml. Trzecia to prosty ekran z prawami autorskimi. Arkusz stylów zachowujemy jako JavaXML.wml.xsl w podkatalogu XSL/ katalogu głównego serwera.
Przykład 9.6. Arkusz XSL umożliwiający wyświetlenie spisu treści w urządzeniu WAP
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 220
Korzystanie ze struktury publikacji
221
type="text/wml"
Spis Copyright
Copyright 2000, O'Reilly & Associates
Spis
Poza nowymi znacznikami WML, powyższy przykład nie powinien sprawiać Czytelnikowi żadnych trudności. Wprowadzono nową funkcję XSL, position() oraz nowy element xsl:number. W ten sposób w pętli xsl:for-each dodawana jest numeracja: 1: Wprowadzenie 2: Pisanie w XML-u ...
Dodano także instrukcje przetwarzania dla struktury Cocoon, w której cel określono jako cocoonformat. Przesyłane dane, type="text/wml", instruują Cocoon, że arkusz stylu ma być wyświetlony z nagłówkiem zawartości w postaci text/wml (a nie text/html lub text/plain, jak to się dzieje standardowo). I jeszcze jeden istotny dodatek w postaci atrybutu w elemencie głównym arkusza:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 221
222
Rozdział 9. Struktury publikacji WWW
Domyślnie każda deklaracja przestrzeni nazw XML inna niż przestrzeń XSL jest dodawana do elementu głównego danych wyjściowych transformacji. W naszym przykładzie element główny danych wyjściowych, wml, zostałby opatrzony następującą deklaracją przestrzeni nazw: ...
Spowodowałoby to błąd przeglądarki WAP, jako że xmlns:JavaXML nie jest dozwolonym atrybutem elementu wml. Przeglądarki tego typu nie są tak tolerancyjne jak przeglądarki HTML i reszta zawartości po prostu nie zostałaby wyświetlona. Jednakże my musimy zadeklarować przestrzeń nazw, aby arkusz XSL mógł dopasować szablon dla dokumentu wejściowego — a to właśnie wykonujemy poprzez przestrzeń JavaXML. Problem można rozwiązać poprzez dodanie atrybutu exclude-result-prefixes do elementu xsl:stylesheet. Przedrostek przestrzeni nazw określony w tym atrybucie nie będzie dodawany do danych wyjściowych — a tego właśnie sobie życzymy. Teraz dane wyjściowe będą „otoczone” znacznikami: ...
Taki zapis przeglądarka WAP rozumie doskonale. Jeśli pobraliśmy przeglądarkę UP.SDK, możemy wpisać w niej adres naszego spisu treści i obejrzeć wynik (do wyświetlenia stron WAP tłumacz wykorzystał emulator Deck-It firmy PyWeb, który można nieodpłatnie pobrać ze strony http://PyWeb.com; przeglądarka UP.SDK w czasie tłumaczenia nie była dostępna w wersji dla systemu Linux — przyp.tłum.). Na rysunku 9.5 widoczne jest menu główne wyświetlane wtedy, gdy pliku contents.xml zażąda poprzez Cocoon urządzenie WAP. Na rysunku 9.6 widzimy wygenerowany spis treści, dostępny po odwiedzeniu odsyłacza „Spis”, pokazanego na poprzednim rysunku. Więcej informacji o standardach WML i WAP można znaleźć na stronach http://www.phone.com i http://www.wapforum.org; oba te miejsca są obszernymi źródłami wiedzy o urządzeniach bezprzewodowych. Teraz już Czytelnik powinien dość dobrze orientować się, jak różne dane wyjściowe można uzyskać dzięki oprogramowaniu Cocoon. Niewielki wysiłek, jakiś dodatkowy arkusz stylu — i już ten sam dokument można serwować w wielu formatach rozmaitym klientom. To między innymi dlatego struktury publikacji są tak potężnym narzędziem. Bez XML-a i podobnego oprogramowania, dla każdego typu klienta trzeba byłoby tworzyć oddzielny serwis. Skoro jednak znamy już możliwości struktury Cocoon, przejdźmy do omówienia sposobu dynamicznego tworzenia i dostosowania danych wejściowych transformacji przy użyciu oprogramowania Cocoon.
XSP XSP to Extensible Server Pages, czyli rozszerzalne strony serwera. To chyba najważniejsza nowinka mająca swoje źródła w projekcie Cocoon. Z pewnością Czytelnik spotkał się już z terminem Java Server Pages (JSP). JSP to (w skrócie) znaczniki i zagnieżdżony kod Javy wstawiany do, skądinąd zupełnie zwyczajnej, strony HTML. Po otrzymaniu żądania takiej strony zawarty w niej kod
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 222
XSP
223
Rysunek 9.5. Główne menu WML
Rysunek 9.6. Spis treści WML
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 223
224
Rozdział 9. Struktury publikacji WWW
jest wykonywany, a wynik wstawiany bezpośrednio do danych wynikowych HTML5. Technologia ta zawładnęła Javą i ASP, upraszczając programowanie w Javie po stronie serwera i umożliwiając separację danych wyjściowych i procesów logicznych. Jednakże nie uniknięto pewnych istotnych problemów. Po pierwsze, JSP tak naprawdę nie oddziela zawartości i prezentacji. Problem ten już był sygnalizowany w książce — zmiana bannera, koloru fontu czy rozmiaru tekstu wymaga modyfikacji JSP (wraz z wpisanymi odwołaniami do Javy i JavaBean). Zawartość (czyste dane) miesza się z prezentacją, podobnie jak w statycznym HTML-u. Po drugie, nie ma możliwości przekonwertowania JSP na jakikolwiek inny format lub użycia go do komunikacji pomiędzy aplikacjami — JSP stworzono głównie do prezentacji danych wyjściowych. XSP stanowi remedium na oba te problemy. Po pierwsze, XSP to właściwie zwykły XML. W przykładzie 9.7 przedstawiona jest strona XSP.
Przykład 9.7. Przykładowa strona XSP private static int numHits = 0; private synchronized int getNumHits() { return ++numHits; } Licznik odwiedzin
Pobierano mnie getNumHits() razy.
Utrzymano zgodność ze wszystkimi konwencjami XML; chwilowo element xsp:logic odczytujemy jako „zakaz wstępu” dla parsera — element ten zostanie omówiony w dalszej części rozdziału. Poza tym cały dokument to zwykłe dane XML zawierające parę nowych elementów. Jest tu nawet odwołanie do arkusza stylu, który nie zawiera żadnych nowości (przykład 9.8).
Przykład 9.8. Arkusz stylu XSL dla przykładu 9.7 5
To oczywiście karygodne uproszczenie; JSP jest najpierw prekompilowane do postaci serwleta, a wyjście obsługuje PrintWriter. Więcej informacji o JSP znaleźć można w książce Java Servlet Programming Jasona Huntera (O'Reilly & Associates).
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 224
XSP
225 type="text/html"
Zatem XSP w prosty sposób likwiduje więc pierwszy problem związany z JSP — umożliwia oddzielenie zawartości od prezentacji. Dzięki takiej separacji programista może zająć się generacją treści (strona XSP może być statyczna, ale może być także generowana z serwletu lub innego kodu Javy), zaś autorzy XML i XSL zajmują się prezentacją i stylami poprzez modyfikację arkusza stylu. W podobny sposób rozwiązywana jest druga bolączka JSP — ponieważ przetwarzanie XSP wykonywane jest przed zastosowaniem arkuszy stylów, wynikowy dokument XML może zostać przetworzony na dowolny inny format. Strona XSP może więc być przekazywana pomiędzy aplikacjami równie łatwo, jak wykorzystywana w prezentacji.
Tworzenie strony XSP Teraz, kiedy Czytelnik poznał już XSP, spróbujmy stworzyć własną stronę w tym standardzie. Dalej jako przykład będziemy stosowali utworzony już wcześniej dokument XML. Zamiast po prostu wyświetlać nasz dokument, załóżmy, że autor chciałby pokazywać go redaktorowi w miarę pisania. Oprócz tekstu książki redaktor powinien widzieć komentarze autora; komentarze te nie są jednak przeznaczone dla oczu zwykłego czytelnika — na przykład dotyczą formatowania i stylu. Najpierw dodajmy taki komentarz do stworzonego wcześniej pliku chapterOne.xml. Chapter 1: Introduction XML. These three letters have brought shivers to almost every developer in the world today at some point in the last two years. While those shivers were often fear at another acronym to memorize, excitement at the promise of a new technology, or annoyance at another source of confusion for today's developer, they were shivers all the same. Surprisingly, almost every type of response was well merited with regard to XML. It is another acronym to memorize, and in fact brings with it a dizzying array of companions: XSL, XSLT, PI, DTD, XHTML, and more. It also brings with it a huge promise-what Java did for portability of code, XML claims to do for portability of data. Sun has even been touting the rather ambitious slogan "Java + XML = Portable Code + Portable Data" in recent months. And yes, XML does bring with it a significant amount of confusion. We will seek to unravel and demystify XML, without being so abstract and general as to be useless, and without diving in so deeply that this becomes just
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 225
226
Rozdział 9. Struktury publikacji WWW another droll specification to wade through. This is a book for you, the Java developer, who wants to understand the hype and use the tools that XML brings to the table. Czy ten akapit jest poprawnie sformatowany, czy rozdzielamy go na dwa oddzielne akapity? Mike, poinformuj mnie o swojej decyzji w tym zakresie. Today's web application now faces a wealth of problems that were not even considered ten years ago. Systems that are distributed across thousands of miles must perform quickly and flawlessly. Data from heterogeneous systems, databases, directory services, and applications must be transferred without a single decimal place being lost. Applications must be able to communicate not only with other business components, but other business systems altogether, often across companies as well as technologies. Clients are no longer limited to thick clients, but can be web browsers that support HTML, mobile phones that support Wireless Application Protocol (WAP), or handheld organizers with entirely different markup languages altogether. Data, and the transformation of that data, has become the crucial centerpiece of every application being developed today.
Po wstawieniu do dokumentu takiego komentarza dodajmy odpowiedni wpis w arkuszu stylów, JavaXML.fo.xsl.
Po wprowadzeniu powyższego wpisu komentarze będą nieco mniejsze niż reszta tekstu, niebieskie i pisane kursywą. Teraz nasz dokument XML należy przekształcić na stronę XSP (jak w przykładzie 9.9) — dodając wymagane instrukcje przetwarzania dla struktury Cocoon i otaczając elementy nowym elementem głównym, xsp:page.
Przykład 9.9. Wersja XSP przykładu 9.1 Java i XML Brett McLaughlin
Zanim zajmiemy się kodem XSP odpowiedzialnym za wyświetlenie komentarza, zbudujmy prostą stronę HTML umożliwiającą określenie, czy odwiedzający jest redaktorem książki. W prawdziwej aplikacji na tej stronie odbywałoby się uwierzytelnianie i określanie roli użytkownika; w naszym przypadku użytkownik będzie mógł określić się jako autor, edytor lub po prostu ciekawski czytelnik i podać hasło. Odpowiednia strona HTML przedstawiona jest w przykładzie 9.10. Zapiszmy ją jako entry.html w katalogu głównym serwera WWW.
Przykład 9.10. Interfejs HTML do wyboru „roli” użytkownika Witamy na stronie roboczej wersji Java i XML Java i XML wersja robocza Wybierz: Jestem autorem Jestem redaktorem Jestem czytelnikiem Wpisz haslo:
Należy zauważyć, że formularz HTML przekazujemy bezpośrednio do strony XSP. W tym przykładzie strona XSP działa jak serwlet. Chcemy, aby odczytała parametry żądania, określiła, jaki rodzaj użytkownika został podany, przeprowadziła uwierzytelnianie za pomocą hasła i wyświetliła (lub nie) komentarz. Zacznijmy od zdefiniowania zmiennej typu boolean; będzie ona zawierała wynik porównania podanego hasła z właściwym. Następnie sprawdzimy wartość tej zmiennej i jeśli ta wartość to true, wyświetlimy element authorComment:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 227
228
Rozdział 9. Struktury publikacji WWW Java and XML Brett McLaughlin ... is a book for you, the Java developer, who wants to understand the hype and use the tools that XML brings to the table. boolean authorOrEditor = false; // Tu sprawdzimy, czy to autor, czy redaktor. if (authorOrEditor) { Czy ten akapit jest poprawnie sformatowany, czy rozdzielamy go na dwa oddzielne akapity? Mike, poinformuj mnie o swojej decyzji w tym zakresie. } Today's web application now faces a wealth of problems that were not even considered ten years ago. Systems that are ...
Czytelnik nie powinien mieć żadnych trudności — poza znacznikami specyficznymi dla XSP definiujemy tylko zmienną i sprawdzamy jej wartość. Jeśli zmienna ma wartość true, element authorComment jest dodawany do danych wyjściowych XSP; w przeciwnym razie — nie. Interesujące jest to, że otaczamy faktyczne dane wyjściowe XML zawarte w xsp:logic elementem xsp: page. To gwarantuje, że procesor XSP nie będzie próbował interpretować żadnych elementów ani tekstu wewnątrz tego bloku jako struktury XSP. I znów widać tutaj przewagę standardu XSP nad JSP — ten sam kod w tym ostatnim standardzie wyglądałby następująco: Czy ten akapit jest poprawnie sformatowany, czy rozdzielamy go na dwa oddzielne akapity? Mike, poinformuj mnie o swojej decyzji w tym zakresie.
Jest to mniej zrozumiała struktura — blok JSP kończy się, zanim jeszcze element authorComment się zaczyna; następnie po elemencie dołączany jest kolejny blok, zamykający nawias otwarty w bloku pierwszym. Bardzo łatwo pomylić sposoby kodowania lub zapomnieć o „zamykających” blokach JSP; XSP wymusza zamknięcie każdego otwartego elementu (standardowe poprawne formatowanie XML-a), a jeden blok kodu odpowiada jednemu elementowi. Po wprowadzeniu struktur logicznych należy już tylko zinterpretować parametry żądania. Do tego celu służy wbudowana zmienna XSP request, ktora „naśladuje” obiekt HttpServletRequest. Kod po wprowadzeniu poniższych modyfikacji odczytuje wartość parametrów userRole i password (jeśli istnieją); wartość ta następnie jest porównywana z rolami, które mają prawo do obejrzenia komentarza (autor i redaktor). Jeśli dopasowanie się powiedzie, sprawdzane jest jeszcze hasło. Jeśli hasło odpowiada kluczowi podanej roli, zmienna boolowska jest włączana (wartość true), a element authorComments stanowi część danych wyjściowych XML:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 228
XSP
229 boolean authorOrEditor = false; // Poniżej sprawdzimy, czy to autor czy redaktor } if (authorOrEditor) { Czy ten akapit jest poprawnie sformatowany, czy rozdzielamy go na dwa oddzielne akapity? Mike, poinformuj mnie o swojej decyzji w tym zakresie. }
Czytelnik winien zauważyć, że spory fragment instrukcji logicznych został ujęty w znacznik CDATA. Trzeba pamiętać, że XSP zawsze jest przetwarzane jako XML i musi być zgodne z zasadami tego standardu; ale cudzysłowy i „ampersandy” (symbole &apm;) używane we fragmentach w Javie nie są dozwolone w XML-u; zamiast więc znosić ich specjalne znaczenie i wprowadzać ogromne zamieszanie, należy użyć znacznika CDATA, w którym można sobie spokojnie pisać w Javie. Bez niego kod wyglądałby następująco: boolean authorOrEditor = false; String[] roleValues = request.getParameterValues("userRole"); String[] passwordValues = request.getParameterValues("password"); if ((roleValues != null) && (passwordValues != null)) { String userRole = roleValues[0]; String password = passwordValues[0]; if (userRole.equals("author") && password.equals("brett")) { authorOrEditor = true; } else if (userRole.equals("editor") && password.equals("mike")) { authorOrEditor = true; } } ...
Teraz można przetestować stronę „wejściową” i dokument PDF powstały z danych XML. Po „skierowaniu” przeglądarki na stronę http://://entry.html Czytelnik powinien uzyskać wynik podobny do tego na rysunku 9.7.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 229
230
Rozdział 9. Struktury publikacji WWW
Rysunek 9.7. „Wejściowa” strona HTML Wybieramy rolę autora i podajemy hasło „brett” albo rolę redaktora i hasło „mike”. W obu przypadkach uzyskamy dokument PDF przedstawiony na rysunku 9.8.
Rysunek 9.8. Wygenerowany dokument PDF z komentarzami Trzeba już tylko odizolować instrukcje logiczne od samej zawartości. Podobnie jak w JSP możliwe jest włączanie obiektów JavaBean i tym samym oddzielanie zawartości i prezentacji od logiki aplikacji, XSP umożliwia tworzenie bibliotek znaczników. Później, za pomocą znaczników XML, uruchamiany jest odpowiedni kod znajdujący się wewnątrz takiej biblioteki.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 230
XSP
231
Biblioteki znaczników XSP Oprócz wyświetlenia komentarzy, należy również zaznaczyć, że oglądany rozdział to wersja robocza; oprócz tego powinna zostać wyświetlona data zmian w wersji roboczej (która zostanie „zamrożona” po ukończeniu rozdziału). Zamiast dopisywać kod Javy ładujący bieżącą datę, można stworzyć do tego celu własną bibliotekę znaczników. Przy okazji Czytelnik dowie się, jak stworzyć element XSP pobierający numer i tytuł rozdziału, a następnie formatujący odpowiedni nagłówek. Za pomocą tej funkcji można obsłużyć również wspomnianą operację na dacie. Najpierw konieczne jest utworzenie biblioteki znaczników dostępnej z naszej strony XSP. Większa część takiej biblioteki oparta jest na arkuszu stylu XSL. Zaczniemy od szkieletu pokazanego w przykładzie 9.11, który po prostu przekazuje wszystkie otrzymane dane na wyjście. Szkielet ten zachowamy w podkatalogu XSL/, w pliku JavaXML.xsp.xsl. Trzeba pamiętać o dołączeniu deklaracji przestrzeni nazw JavaXML, ponieważ wykorzystamy ją do dopasowania elementów na naszych stronach XSP.
Przykład 9.11. Arkusz logiki XSP
Dopasowując znacznik xsp:page gwarantujemy, że wszystkie elementy zostaną dopasowane i obsłużone wewnątrz tego arkusza stylu (czy też, zgodnie z nazewnictwem XSP, arkusza logiki). Teraz dodajemy metody Javy do wywołania przez szablony wewnątrz arkusza: java.util.Date java.text.SimpleDateFormat
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 231
Wprowadzono tutaj szereg nowych elementów XSP. Po pierwsze, element xsp:structure otacza elementy xsp:include. Te ostatnie działają podobnie jak include w języku Java — udostępniają określone klasy Javy, których można teraz używać za pomocą niekwalifikowanych nazw (zamiast pełnych nazw pakietów). Następnie definiujemy i implementujemy dwie metody — jedna tworzy tytuł rozdziału z numeru i tekstowej postaci tytułu, a druga zwraca bieżącą datę w postaci sformatowanej zmiennej String. Metody te dostępne są z poziomu dowolnego elementu w tym arkuszu logiki. Teraz należy zdefiniować element określający, kiedy dane XSP mają zastąpić element XML. Już zdefiniowaliśmy przestrzeń nazw JavaXML w elemencie głównym dokumentu, a więc używamy jej w elementach naszej biblioteki znaczników. Dodajemy następujący szablon: getTitle(, "") - getDraftDate()
Kiedy w dokumencie odwołującym się do takiej biblioteki znaczników użyjemy elementu JavaXML:draftTitle, do wyniku metody getTitle() zostanie dołączony łącznik (-), a następnie wartość zwracana przez getDate(). Element JavaXML:draftTitle również wymaga zadeklarowania dwóch atrybutów: numeru rozdziału i tytułu w postaci tekstowej. Procesor XSP otrzymuje informację, że wywołujemy zdefiniowaną metodę, poprzez ujęcie wywołania tej metody w znaczniki . Aby zaznaczyć, że drugi argument (tytuł rozdziału) jest zmienną String, ujmujemy go w cudzysłowy. Ponieważ tytuł rozdziału powinien być traktowany jako int, zostawiamy go bez cudzysłowów. Po stworzeniu arkusza logiki XSP (można go również pobrać ze strony WWW niniejszej książki) trzeba go udostępnić strukturze Cocoon. Można to zrobić na dwa sposoby. Pierwszy z nich polega na określeniu położenia pliku (w postaci URI), aby mógł zostać odnaleziony przez mechanizm serwletów (a więc również przez Cocoon). Na przykład, aby dodać w ten sposób arkusz XSP do zasobów Cocoon, należy wpisać następujący wiersz do pliku cocoon.properties. Wersja uniksowa:
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 232
Takie rozwiązanie wystarcza na potrzeby testowania, ale arkusze logiczne są wtedy silnie „związane” z mechanizmem serwletów, a dodawanie nowych jest kłopotliwe (konieczne jest wpisywanie kolejnych wierszy w pliku właściwości)6. Alternatywna metoda ładowania arkuszy logiki polega na określeniu zasobu w ścieżce dostępu do klas mechanizmu serwletów. Arkusze logiki dodawane są do pliku jar, a plik ten — do ścieżki dostępu do klas. W ten sposób w pliku powstaje centralna baza własnych arkuszy logiki XSP użytkownika. W podkatalogu XSL/ wykonujemy następujące polecenie, które spowoduje utworzenie pliku jar, zawierającego nasz arkusz logiki: jar cvf logicsheets.jar JavaXML.xsp.xsl
6
Co więcej, zdarzają się kłopoty (wersje 1.7.x) z załadowaniem arkusza wpisanego w postaci file://...; problemu tego można uniknąć stosując zapis w postaci resource://
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 233
234
Rozdział 9. Struktury publikacji WWW
Utworzone w ten sposób archiwum logicsheets.jar przenosimy do katalogu /lib/ — tam, gdzie znajdują się pozostałe biblioteki Cocoon. Teraz musimy dodać tę bibliotekę do ścieżki dostępu do klas serwera Tomcat; edytujemy plik tomcat.sh lub tomcat.bat zlokalizowany w katalogu /bin/. W systemie Unix plik ten ma wyglądać następująco: set CLASSPATH=. # Klasy i biblioteki Cocoon CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xerces_1_2.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xalan_1_2_D02.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/fop_0_13_0.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/cocoon.jar CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/logicsheets.jar for i in ${TOMCAT_HOME}/lib/* ; do CLASSPATH=${CLASSPATH}:$i done CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/tools.jar
Natomiast w Windows: set CLASSPATH=. set CLASSPATH=%TOMCAT_HOME%\classes rem set set set set set
Klasy i biblioteki Cocoon CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xerces_1_2.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xalan_1_2_D02.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\fop_0_13_0.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\cocoon.jar CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\logicsheets.jar
Teraz informujemy Cocoon, gdzie ma, szukać przestrzeni nazw JavaXML na stronach XSP. Edytujemy plik cocoon.properties, który wcześniej umieściliśmy w katalogu /conf. Odnajdujemy część mówiącą o zasobach XSP i dodajemy odwołanie do nowego arkusza logiki: processor.xsp.logicsheet.context.java = resource://org/apache/cocoon/processor/xsp/library/java/context.xsl processor.xsp.logicsheet.cookie.java = resource://org/apache/cocoon/processor/xsp/library/java/cookie.xsl processor.xsp.logicsheet.global.java = resource://org/apache/cocoon/processor/xsp/library/java/global.xsl processor.xsp.logicsheet.request.java = resource://org/apache/cocoon/processor/xsp/library/java/request.xsl processor.xsp.logicsheet.response.java = resource://org/apache/cocoon/processor/xsp/library/java/response.xsl processor.xsp.logicsheet.session.java = resource://org/apache/cocoon/processor/xsp/library/java/session.xsl processor.xsp.logicsheet.util.java = resource://org/apache/cocoon/processor/xsp/library/java/util.xsl processor.xsp.logicsheet.sql.java = resource://org/apache/cocoon/processor/xsp/library/sql/sql.xsl processor.xsp.logicsheet.fp.java = resource://org/apache/cocoon/processor/xsp/library/fp/fp.xsl processor.xsp.logicsheet.JavaXML.java
= resource://JavaXML.xsp.xsl
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 234
Cocoon 2.0 i dalej
235
Ponieważ nasz arkusz logiki nie jest zagnieżdżony w podkatalogach pliku logicsheets.jar, jako ścieżkę dostępu wystarczy określić tutaj nazwę pliku. Po tych wszystkich operacjach restartujemy mechanizm serwletów — plik cocoon.properties zostanie ponownie załadowany, a arkusz logiki udostępniony do użycia. Ponieważ żądania obsługuje mechanizm Cocoon, każda strona XSP, na której zadeklarowano użycie JavaXML, będzie miała dostęp do arkusza logiki określonego jako biblioteka JavaXML. Tak więc do naszej strony XSP musimy dodać deklarację przestrzeni nazw JavaXML: ...
Skoro biblioteka znaczników jest już udostępniona, dodajemy element JavaXML:draftTitle do naszego dokumentu XML, chapterOne.xml:
Zakodowany „na stałe” tytuł rozdziału zamieniamy na element zdefiniowany w naszej bibliotece znaczników. W ten sposób zostanie wygenerowany tytuł i numer rozdziału oraz data wersji roboczej. Teraz można już zobaczyć efekt naszej pracy, wpisując adres dokumentu do przeglądarki WWW. To, oczywiście, proste przykłady zastosowania standardu XSP. Ale już tutaj widać potężne możliwości XSP — tytuł może przybrać inną postać po ukończeniu rozdziału bez konieczności modyfikacji zawartości czy warstwy prezentacyjnej strony, a jedynie arkusza XSP. Logika aplikacji jest odseparowana od zawartości i prezentacji. Do tego wszystkiego można jeszcze dołączyć logikę biznesową za pomocą komponentów Javy uruchamianych po stronie serwera, np. Enterprise JavaBeans. Zastosowanie standardu XSP to rozwiązanie bardziej elastyczne niż JSP połączony z HTML-em — poszczególne komponenty nie są związane ze sobą tak ściśle. Standard XSP ma stanowić kluczowy element powstającej wersji 2.0 Cocoon.
Cocoon 2.0 i dalej Kolejna wersja Cocoon, o numerze 2.0 stanowi ogromny krok naprzód w porównaniu z wersjami wcześniejszymi. Cocoon 1.x, w którym przede wszystkim przetwarza się XML za pośrednictwem interfejsu XSL, jest wciąż w szerokim stopniu ograniczony. Po pierwsze, oprogramowanie to na razie nie przyczynia się do znacznego zmniejszenia kosztów utrzymania dużych serwisów. Dokument XML może zostać przekształcony w różne formy prezentacji, ale i tak na serwerze musi istnieć spora liczba dokumentów. Konieczne jest więc korzystanie z długich identyfikatorów URI
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 235
236
Rozdział 9. Struktury publikacji WWW
(np. /content/publishing/books/javaxml/contents.xml), wielu odwzorowań wirtualnych ścieżek (/javaxml odwzorowane na /content/publishing/books/javaxml/) albo z połączenia obu. Co więcej, wciąż nie jest łatwo odseparować prezentację od zawartości i logiki aplikacji. W projekcie Cocoon 2 skoncentrowano się na rozdzieleniu tych różnych warstw — ma to przyczynić się do zmniejszenia kosztów zarządzania witryną; całość ma skupiać się wokół standardu XSP. Plan witryny (o którym za chwilę) spowoduje, że użytkownik nie będzie widział różnicy pomiędzy stronami XSP, XML i statycznymi HTML. Wprowadzone zostaną również istotne zmiany w kompilacji i zarządzaniu pamięcią; Cocoon 2 będzie o tyle bardziej zaawansowany od Cocoon 1.x, o ile Cocoon 1.x był bardziej zaawansowany od zwykłego serwera WWW.
Odwzorowania w mechanizmie serwletów Istotna zmiana w Cocoon 2 polega na tym, że nie będzie już trzeba stosować prostego odwzorowywania dokumentów XML. Odwzorowanie takie spisuje się nieźle w modelu 1.x, ale zarządzanie dokumentami innymi niż XML zrzucane jest na barki webmastera — osoby niekoniecznie związanej z administratorem odpowiedzialnym za dokumenty XML. Cocoon 2 ma umożliwić zarządzanie całą witryną. Główny serwlet Cocoon (org.apache.cocoon.servlet.CocoonServlet w modelu 2.0) jest zazwyczaj odwzorowywany na identyfikator URI, np. /Cocoon. Możliwe jest także odwzorowanie na katalog główny całego serwera (czyli /) i tym samym sterowanie całą witryną. Adres URL przyjmuje postać wynikającą z takiego odwzorowania, np. http://mojHost.com/ Cocoon/mojaStrona.xml lub http://mojHost.com/Cocoon/mojaDynamicznaStrona.xsp. Przy takim odwzorowaniu nawet statyczne dokumenty HTML można grupować z dokumentami XML. Możliwe jest więc centralne zarządzanie wszystkimi plikami umieszczonymi na serwerze. Jeśli dokumenty HTML i XML pojawią się w jednym katalogu, nie będzie kłopotu z ich oddzielną obsługą ani z tworzeniem identyfikatorów URI. Cocoon 2 będzie obsługiwał HTML oraz dane wszelkiego innego typu; po odwzorowaniu głównego katalogu serwera na Cocoon cała struktura publikacji staje się niewidoczna dla klienta.
Plan witryny Kolejną ważną nowością w Cocoon 2 jest plan witryny (ang. sitemap). W strukturze Cocoon plan witryny stanowi centralne miejsce administracji serwisem. Na podstawie tego planu Cocoon decyduje o tym, jak przetwarzać otrzymany identyfikator URI. Na przykład, kiedy otrzymuje żądanie w rodzaju http://mojaWitrynaCocoon.com/Cocoon/javaxml/chapterOne.html, serwlet Cocoon bada żądanie i określa, że faktycznie żądano /javaxml/chapterOne.html. Załóżmy jednak, że chapterOne.html nie ma być odwzorowywany na statyczny plik HTML, ale na wynik transformacji dokumentu XML (jak w naszych wcześniejszych przykładach). Tu właśnie przydaje się plan witryny. Spójrzmy na plan witryny w przykładzie 9.12.
Przykład 9.12. Przykładowy plan witryny w Cocoon 2.0
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 236
Cocoon 2.0 i dalej
237
Definicja DTD planu witryny jest już na ukończeniu, ale w trakcie testowania beta mogą zostać wprowadzone zmiany. Powyższy przykład należy traktować jako zarys możliwości Cocoon 2, a nie faktycznie działający kod.
Cocoon dopasowuje identyfikator URI /javaxml/chapterOne.html do instrukcji planu witryny /javaxml/*.html. Następnie określa, że jest to faktyczny plik i że odnaleziony on zostanie po użyciu odwzorowania /docs/javaxml/*.xml, tłumaczonego na /docs/javaxml/chapterOne.xml (nazwa pliku, którą chcemy przetworzyć). Następnie stosowany jest filtr XSLT; wykorzystywany arkusz stylu, JavaXML.html.xsl, jest także określany w planie witryny. Wynik transformacji wyświetlany jest użytkownikowi. Co więcej, plik XML może być plikiem XSP, przetwarzanym przed konwersją na XML. Według tego samego schematu generowany jest plik PDF z żądania http://mojaWitrynaCocoon.com/Cocoon/javaxml/chapterOne.pdf — wystarczy tylko dodać parę wierszy do planu witryny (patrz wyżej). Oznacza to również, że instrukcje przetwarzania w indywidualnych dokumentach można usunąć, co stanowi istotną zmianę w stosunku do Cocoon 1.x. Stosowanie arkuszy stylów i przetwarzania odbywa się na podstawie położenia katalogu. Wystarczy stworzyć dokument XML i umieścić go w katalogu /docs/javaxml (zgodnie z powyższym przykładem), a dokument ten będzie można pobrać jako HTML lub PDF. Zmiana arkuszy stylów wykorzystywanych w dokumentach jest również banalna — w poprzedniej wersji był to proces kłopotliwy. Zamiast zmieniać każdy dokument XML, wystarczy zmienić jeden wiersz w planie witryny. Plan witryny jest wciąż w trakcie rozwoju i prawdopodobnie będzie jeszcze zmieniany. Aby przyłączyć się do prac, należy zapisać się na listy adresowe [email protected] i [email protected]. Pod adresem http://xml.apache.org można znaleźć szczegółowe informacje o całym projekcie Cocoon.
Producenty i procesory Ostatnim istotnym ulepszeniem w strukturze Cocoon 2 jest wprowadzenie wstępnie kompilowanych i skoncentrowanych wokół zdarzeń producentów i procesorów (ang. producers, processors). W strukturze Cocoon producent obsługuje transformację żądania URI na strumień danych XML. Procesor pobiera następnie strumień wejściowy (obecnie w postaci dokumentu XML w drzewie DOM) i wysyła dane wyjściowe możliwe do odczytania przez klienta. Nie omówiliśmy tych komponentów w modelu Cocoon 1.x, ponieważ w modelu 2.0 ulegną one bardzo dużym zmianom; te, które używane są obecnie, staną się prawdopodobnie bezużyteczne i trzeba będzie je przepisać. W Cocoon 2 zrezygnowano z używania w tych strukturach modelu DOM na rzecz bardziej skoncentrowanego wokół zdarzeń SAX-a (a nawet JDOM-a!), „opakowanego” strukturą DOM. Do tej pory producent musiał generować dokument XML w pamięci i odpowiadająca mu struktura DOM mogła rozrosnąć się do wielkich rozmiarów. To powodowało wyczerpanie zasobów systemowych,
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 237
238
Rozdział 9. Struktury publikacji WWW
szczególnie w tak skomplikowanych zadaniach jak złożone transformacje czy obsługa obiektów formatujących (generowanie PDF). Dlatego DOM będzie teraz tylko „interfejsem” do zdarzeń SAX, a producenty i procesory zyskają na wydajności. Producenty i procesory będą kompilowane z innych formatów. Na przykład arkusze XSL można skompilować do procesorów, a strony XSP — do producentów. To jeszcze bardziej zwiększa wydajność i zdejmuje ciężar przetwarzania z klienta. Tutaj i wszędzie indziej wykorzystywany będzie podział na poszczególne komponenty — nowy Cocoon będzie dzięki temu bardziej elastyczny i łatwy w rozbudowie. Najnowsze wiadomości dotyczące nowego projektu można znaleźć na stronie WWW Cocoon.
Co dalej? W następnym rozdziale zostanie omówiona technologia umożliwiająca wykorzystanie XML-a jako formatu danych w modelu żądań i odpowiedzi XML-RPC. Rozproszony system wywoływania zdalnych procedur XML-RPC umożliwia wykonywanie zadań na serwerze (lub serwerach) znajdujących się w innej części sieci. Do niedawna nie cieszył się on zbytnią popularnością, co spowodowane było przede wszystkim napływem technologii opartych na RMI i związanych z Javą (szczególnie EJB). Jednakże przy wykorzystaniu XML-a jako formatu danych, XML-RPC umożliwia rozwiązanie wielu problemów.
C:\WINDOWS\Pulpit\Szymon\Java i XML\09-08.doc — strona 238
10 XML-RPC W tym rozdziale zostanie omówione kolejne ciekawe zastosowanie języka XML — model żądań i odpowiedzi XML-RPC. XML-RPC to po prostu specyficzna odmiana RPC, czyli technologii wywoływania zdalnych procedur. Jeśli Czytelnik nie jest doświadczonym programistą, bądź też do tej pory posługiwał się tylko językiem Java, pojęcie zdalnego wywoływania procedur może być mu obce; dla innych zaś poruszanie tego tematu może wydać się dziwne — RPC w ostatnich czasach stracił na popularności. W trakcie lektury niniejszego rozdziału Czytelnik dowie się, dlaczego te trzy niepozorne literki przed skrótem RPC zrewolucjonizowały coś, co wydawało się być dinozaurem w świecie programistycznym. Czytelnik nauczy się również korzystać z XML-RPC z poziomu programów w Javie. Zakończenie rozdziału poświęcone jest praktycznym zastosowaniom modeli XML-RPC. Jeśli Czytelnik poddał się fali programowania obiektowego, tak popularnego w ostatnich latach, to pewnie wzdryga się na dźwięk słowa „procedura”. Języki proceduralne, takie jak PL/SQL czy ANSI C, nie są „modne” i jest ku temu wiele powodów. Zapewne niejednokrotnie Czytelnikowi zdarzyło się „oberwać” za nazwanie metody Javy funkcją lub procedurą; wie też, jak unikać „kodu spaghetti” — czyli kodu polegającego na łączeniu wielu metod w jeden długi łańcuch. Wraz z tymi językami i sposobami programowania odsunięto na bok technologię RPC — nowe, obiektowe techniki umożliwiają osiągnięcie tych samych rezultatów przy bardziej przejrzystej strukturze programu i większej wydajności. Jednak, co ciekawe, popularyzacja XML-a przyczyniła się do upowszechnienia interfejsów API zbudowanych z myślą o XML-RPC oraz do coraz częstszego wykorzystywania XML-RPC mimo niedobrych skojarzeń, jakie się z tym skrótem wiążą. Zanim Czytelnik zacznie stosować te interfejsy, powinien przyjrzeć się technologii RPC i porównać ją z podobnymi rozwiązaniami Javy, a szczególnie z technologią RMI (Remote Method Invocation). Jeśli zdecydujemy się na wykorzystanie XML-RPC w aplikacji (a na pewnym etapie pracy z pewnością tak się stanie), na pewno będziemy musieli uzasadnić swój wybór innym programistom — szczególnie tym, którzy mają właśnie za sobą lekturę o EJB czy RMI. Wszystkie te technologie mają na pewno swoje miejsce, ale zrozumienie właściwego ich stosowania jest bardzo istotne. Najbardziej popularnymi sposobami operowania na obiektach poprzez sieć są RPC i RMI.
RPC kontra RMI Technologia RMI zyskuje ogromną popularność wśród programistów Javy. Cała specyfikacja EJB (Enterprise JavaBeans) została oparta na bazie RMI; umiejętność pisania trzywarstwowych aplika-
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 239
240
Rozdział 10. XML-RPC
cji RMI jest koniecznością. Jeśli Czytelnik jeszcze tego nie potrafi, powinien sięgnąć po Java Enterprise in a Nutshell (autorzy: David Flanagan, Jim Farley, William Crawford i Kris Magnusson) lub Java Distributed Computing Jima Farleya (obie książki wydawnictwa O'Reilly & Associates).
Co to jest RMI? Technologia RMI to wywoływanie zdalnych metod (remote method invocation). Koncepcja wydaje się dość prosta — RMI umożliwia wywoływanie metod na obiekcie znajdującym się na innym komputerze niż program. Na tym opiera się całe programowanie rozproszone w Javie i to właśnie stanowi szkielet technologii EJB oraz wielu implementacji aplikacji dla przedsiębiorstw. Bez zagłębiania się w detale można powiedzieć, że RMI za pomocą procedur pośredniczących (ang. stub) oraz szkieletów opisuje metody dostępne do zdalnego wywołania. Klient wykorzystuje owe procedury pośredniczące (zazwyczaj mające postać interfejsów Javy), a RMI obsługuje całą „magię” tłumaczenia żądań wysyłanych do procedury pośredniczącej na wywołania sieciowe. Metoda działa na faktycznym obiekcie komputera zdalnego; wynik przesyłany jest z powrotem drogą sieciową. Na koniec procedura pośrednicząca zwraca wynik klientowi, który jako pierwszy wywołał metodę, i klient może działać dalej. Przede wszystkim trzeba pamiętać, że klienta „nie interesują” szczegółowe zasady działania RMI i sieci; procedura pośrednicząca wykorzystywana jest tak, jak gdyby był to faktyczny obiekt implementujący określone metody. Technologia RMI (za pomocą protokołu zdalnego JRMP™) działa tak, że cała komunikacja sieciowa odbywa się poza kulisami; klient ma dostęp do ogólnego wyjątku (java.rmi.RemoteException), a programista może skupić się na regułach biznesowych i logice aplikacji. Technologia RMI umożliwia również korzystanie z różnych protokołów (np. Internet Inter-ORB Protocol) — dzięki temu można uruchomić komunikację pomiędzy obiektami Java i CORBA, często także w innych językach, np. C lub C++. Technologia RMI ma również jednak pewne wady. Po pierwsze, intensywnie wykorzystuje zasoby. Używanych jest całkiem sporo klas i choć stanowią one standardowe wyposażenie pakietu JDK, to przecież stworzenie ich egzemplarza pochłania pamięć i inne zasoby. Protokół JRMP charakteryzuje się słabą wydajnością, a zastąpienie go wcale nie jest prostym zadaniem. Kiedy klienty wysyłają żądania RMI, muszą zostać otwarte i utrzymane gniazda, a im jest ich więcej, tym bardziej spada wydajność systemu — szczególnie wtedy, gdy system dostępny jest w sieci (wtedy dla dostępu HTTP konieczne jest otworzenie dalszych gniazd). Technologia RMI wymaga również istnienia serwera lub usługodawcy, do których można dowiązać obiekty. Dopóki obiekt nie zostanie dowiązany do nazwy odpowiadającej takiemu usługodawcy, dopóty nie będzie dostępny z poziomu innych programów. To wymaga użycia rejestru RMI, serwera usług katalogowych LDAP lub innych usług typu Java Naming and Directory Interface (JNDI). I jeszcze jedno — korzystanie z RMI może wiązać się z koniecznością pisania dużej ilości kodu, nawet biorąc pod uwagę to, jak wiele pomocnych klas udostępnia JDK. Konieczne jest zaprogramowanie zdalnego interfejsu opisującego metody dostępne do wywołania oraz (jeśli korzystamy z EJB) szeregu innych interfejsów. Oznacza to również, że oddanie kolejnej metody do klasy serwera powoduje zmianę interfejsu i przekompilowanie procedur pośredniczących, co często nie jest pożądane (a niejednokrotnie jest po prostu niemożliwe).
Co to jest RPC? Technologia RPC to wywoływanie zdalnych procedur (remote procedure calls). RMI umożliwia bezpośrednie działanie na obiekcie Javy, natomiast RPC pozwala korzystać z metod samodzielnych (tak, tutaj nazywane są one procedurami!) za pośrednictwem sieci. To ogranicza interaktywność, ale upraszcza interfejs, z którym komunikuje się klient. Technologię RPC można sobie wyobrazić
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 240
RPC kontra RMI
241
jako sposób korzystania z „usług” komputerów zdalnych, z kolei RMI umożliwia korzystanie z „serwerów”. Z tej subtelnej różnicy wynika fakt, że proces RMI jest zazwyczaj sterowany w całości przez klienta — zdarzenia następują po zdalnym wywołaniu metod. RPC to częściej klasa lub zestaw klas wykonujących zadania bez interwencji klienta; jednakże czasami klasy te obsługują żądania przesłane przez klienty i wykonują ich „minizadania”. Przedstawione w dalszej części rozdziału przykłady pomogą zrozumieć te teoretyczne wywody. RPC nie jest środowiskiem tak interaktywnym jak RMI, ale oferuje szereg zalet względem tego ostatniego. Umożliwia współpracę oddzielnych systemów. Technologia RMI pozwala co prawda na łączenie Javy z serwerami i klientami CORBA (poprzez IIOP), ale RPC umożliwia komunikację dosłownie dowolnych aplikacji — protokołem transportowym może być bowiem HTTP. Ponieważ niemal dowolny wykorzystywany obecnie język oferuje sposób komunikacji za pośrednictwem HTTP, RPC stanowi niezwykle atrakcyjne rozwiązanie w przypadku tych zastosowań, w których konieczne jest porozumiewanie się z systemami zastanymi. RPC jest zazwyczaj również mniej „zasobożerny” niż RMI (szczególnie wtedy, gdy do kodowania wykorzystywany jest XML — o czym za chwilę); RMI często wymaga przesłania przez sieć całych klas Javy (np. kodów apletów czy własnych klas pomocniczych EJB), a RPC musi tylko przekazać przez sieć parametry żądania i wynik działania, zazwyczaj w postaci danych tekstowych. RPC pasuje również świetnie do modelu interfejsów API — systemy nie stanowiące części określonej aplikacji i tak mogą pobierać z niej informacje; oznacza to, że zmiany poczynione w serwerze nie przekładają się na zmiany w kodzie aplikacji klientów. Przy transferze opartym na zwykłym tekście dodatkowe metody można dodawać bez konieczności rekompilacji klienta; użycie tych metod wymaga zaś tylko niewielkich zmian. Odwieczny problem z technologią RPC związany jest z kodowaniem przesyłanych danych. Wyobraźmy sobie, że chcemy stworzyć tekstową i niewielką objętościowo reprezentację struktur Javy Hastable lub Vector. Jeśli weźmiemy pod uwagę, że te struktury mogą z kolei zawierać inne obiekty Javy, taka reprezentacja nagle przestaje być zadaniem banalnym; stworzony format musi ponadto nadawać się do użycia przez dowolne języki programowania — inaczej umniejszymy korzyści płynące z zastosowania technologii RPC. Do niedawna zachodziła odwrotnie proporcjonalna relacja pomiędzy jakością i użytecznością kodowania w RPC a jego prostotą; innymi słowy, im prościej reprezentowane były złożone obiekty, tym trudniej było wykorzystać takie kodowanie w wielu językach bez tworzenia dodatkowego, własnego kodu. Rozbudowane tekstowe reprezentacje danych nie zostały ustandaryzowane i wymagały całkowicie nowych implementacji w poszczególnych językach. W tej chwili Czytelnik zapewne domyśla się już, do czego zmierzam.
XML-RPC Największą przeszkodą w używaniu technologii RPC był zawsze sposób kodowania. Kiedy pojawił się XML-RPC, wszystko uległo zmianie. XML oferował nie tylko prostą, tekstową reprezentację danych; stanowił także standard strukturyzacji danych. Kiedy grupa W3C opublikowała specyfikację XML 1.0, zniknęły obawy o konieczność stosowania rozwiązań własnych — korzystający z RPC mieli pewność, że XML w najbliższym czasie nie sprawi im żadnej niespodzianki. Co więcej, standard SAX stworzył możliwość uzyskania dostępu do XML-a w sposób wydajny i standardowy; to uprościło implementację bibliotek RPC. Pozostała więc już tylko do uruchomienia transmisja poprzez HTTP (coś, co robimy już od ponad 10 lat) oraz specyficzne interfejsy kodowania i dekodowania. Po kilku implementacjach beta bibliotek XML-RPC stało się jasne, że oprócz pozostałych zalet, XML jest również szybki i mało wymagający — wydajność bibliotek XML-RPC była większa niż oczekiwano. Model XML-RPC stanowi obecnie prężne i stabilne rozwiązanie zdalnego wywoływania procedur.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 241
242
Rozdział 10. XML-RPC
Czytelnikowi, jako programiście Javy, XML-RPC oferuje sposób prostego tworzenia „punktów zaczepienia” w aplikacji i usługach, zarówno do użytku własnego, jak i do wykorzystania przez klienty aplikacji w różnych oddziałach, a nawet firmach. Interfejsy API są odseparowane od Javy, klienty nie muszą więc korzystać bezpośrednio z tego języka. Ponadto dzięki XML-RPC nie trzeba już się uczyć o RMI, aby korzystać z usług rozproszonych (przynajmniej na początku). Niniejszy rozdział stanowi opis implementacji serwera i klienta XML-RPC. Czytelnik przekona się również, że serwer może działać niezależnie od klientów, wciąż jednak udostępniając interfejsy współpracujące z XML-RPC i pozwalając na komunikację i pobieranie danych. Porównanie technologii RMI z XML-RPC pozwoli wykazać wyższość tego ostatniego w wielu zastosowaniach.
Powiedział „Witaj!” Jeśli Czytelnikowi udało się przebrnąć przez te kilka stron wywodów o zdalnym wywoływaniu procedur, z pewnością jest przekonany, że XML-RPC to narzędzie przydatne i że może rozwiązać wiele problemów programistycznych. Aby rozwinąć temat, spróbujemy teraz zbudować prawdziwy program wykorzystujący tę technologię. Zgodnie z tradycją, zaczniemy od prostego programu „Witaj świecie!”. W naszym serwerze XML-RPC zostanie zarejestrowana procedura obsługi. Procedura ta pobiera parametr String Javy (nazwę użytkownika) i zwraca łańcuch zawierający „Witaj” oraz pobraną nazwę, np. „Witaj Brett”. Następnie procedura ta zostanie udostępniona przez serwer klientom XML-RPC. Na koniec stworzymy prostego klienta łączącego się z serwerem i żądającego wywołania tej metody. W rzeczywistości serwer i procedura obsługi XML-RPC znajdowałyby się na jednym komputerze (zazwyczaj wydajnym serwerze), a klient na innym i cała operacja odbywałaby się zdalnie. Jednakże, jeśli nie mamy pod ręką kilku komputerów, przykłady możemy przećwiczyć lokalnie. Proces będzie przebiegał wtedy szybciej niż w rzeczywistym zastosowaniu, ale i tak będzie można zaobserwować, jak poszczególne elementy układają się w jedną całość i jak ten cały XML-RPC działa.
Skąd wziąć biblioteki XML-RPC? Jak to zostało powiedziane wcześniej, w rozwój RPC, a ostatnio XML-RPC, włożono już wiele pracy. Podobnie jak w przypadku używania interfejsów SAX, DOM i JDOM do obsługi XML-a, nie trzeba drugi raz wynajdywać koła — na pewno istnieją dobre, a nawet świetne pakiety Javy spełniające nasze potrzeby. Centrum informacji o XML-RPC, a także zasób odsyłaczy do odpowiednich bibliotek Javy i innych języków stanowi witryna pod adresem http://www.xml-rpc.com. Serwis ten, sponsorowany przez Userland (http://www.userland.com), zawiera publiczną specyfikację XML-RPC, informacje o obsługiwanych typach danych i samouczki. Co ważniejsze jednak, można tam znaleźć odsyłacz do miejsca w sieci, z którego pobierzemy pakiet XML-RPC dla Javy, a mianowicie do strony Hannesa Wallnofera http://helma.at/hannes/xmlrpc. Na stronie tej znajduje się opis klas zawartych w pakiecie XML-RPC Hannesa, a także instrukcja ich obsługi. Pobieramy archiwum i rozpakowujemy je w katalogu roboczym lub środowisku programistycznym IDE. Następnie kompilujemy klasy; jeden z przykładów serwletów wymaga obecności klas serwleta (servlet.jar dla interfejsu Servlet PI 2.2). Odpowiednią klasę dla mechanizmu serwletów Tomcat znaleźć można na stronie http://jakarta.apache.org. Jeśli Czytelnik nie zamierza „bawić się” przykładem z serwletem, to nie musi go kompilować — nie jest on wymagany do wykonania przykładów w tym rozdziale.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 242
Powiedział „Witaj!”
243
Klasy XML-RPC są spakowane w pliku zip, xmlrpc-java.zip. Z tego archiwum trzeba wydobyć cały kod źródłowy znajdujący się w katalogu xmlrpc-java/src/. Nie dołączono dystrybucji w postaci jar, więc wymagana jest ręczna kompilacja klas. Po skompilowaniu Czytelnik może sam utworzyć archiwum jar, co uprości włączanie klas do ścieżki. (W wersji pobranej przez tłumacza w styczniu 2001 r. odpowiedni plik jar był już dołączony — przyp. tłum.).
Zasadnicza dystrybucja (nie zawierająca przykładów apletów i wyrażeń regularnych) składa się z ośmiu klas pakietu helma.xmlrpc: klasa zasadnicza XmlRpc, klient XmlRpcClient, serwer XmlRpcServer, XmlRpcHandler (precyzyjne sterowanie kodowaniem i przetwarzaniem XML-a) oraz szereg klas pomocniczych. Klasy zgłaszają wyjątek XmlRpcException; natomiast XmlRpcServlet demonstruje użycie serwleta jako procedury obsługi odpowiedzi HTTP; z kolei WebServer to „lekki” serwer HTTP zbudowany specjalnie do obsługi żądań XML-RPC; zaś Benchmark umożliwia zmierzenie czasu obsługi żądania XML-RPC z wykorzystaniem specyficznego sterownika SAX. Wymagane do pracy całego mechanizmu, a nie zawarte w dystrybucji, są klasy SAX (które Czytelnik powinien posiadać po wykonaniu wcześniejszych przykładów) oraz sterownik SAX; innymi słowy, trzeba mieć kompletną implementację parsera XML obsługującą SAX. W naszych przykładach będziemy ponownie korzystali z parsera Apache Xerces, ale biblioteki obsługują dowolny parser zgodny z SAX 1.0. Po skompilowaniu plików źródłowych należy upewnić się, czy klasy XML-RPC, SAX i parsera XML znajdują się w ścieżce dostępu do klas. Po tym Czytelnik powinien już potrafić napisać pierwszy program. Warto trzymać gdzieś pod ręką pliki źródłowe XML-RPC, bo pozwalają one na podejrzenie, co dzieje się „pod maską” naszego przykładu.
Pisanie procedury obsługi Po pierwsze, konieczne jest napisanie klasy i metody, która ma być uruchamiana zdalnie. Nazywa się ją zazwyczaj procedurą obsługi. Pamiętajmy jednak, że mechanizm XML-RPC obsługujący żądania często określany jest taką samą nazwą — znów daje się we znaki dwuznaczność przyjętego nazewnictwa. Procedura obsługi XML_RPC to metoda lub metody pobierające żądanie XML-RPC, dekodujące jego zawartość i oddelegowujące to żądanie do jakiejś klasy lub metody. Procedura obsługi odpowiedzi, czy też po prostu procedura obsługi, to metoda wywoływana przez procedurę obsługi XML-RPC. Posiadając biblioteki XML-RPC dla Javy, nie musimy pisać procedury obsługi XML-RPC, ponieważ jest już ona zawarta w klasie helma.xmlrpc.XmlRpcServer. Trzeba jedynie napisać klasę z jedną lub dwoma metodami, które zamierzamy zarejestrować w serwerze. Niespodzianką jest fakt, że tworzenie procedury obsługi nie wymaga budowania podklas ani innych specjalnych zabiegów w kodzie. Poprzez XML-RPC można wywołać dowolną metodę, o ile tylko XML-RPC obsługuje jej parametry i zwracany typ danych (potrafi je zakodować). W tabeli 10.1 przedstawiono wszystkie obecnie obsługiwane typy Javy, jakie można wykorzystać w sygnaturach metod XML-RPC. Lista zawiera tylko nieliczne typy, ale — jak się okazuje — umożliwiają one obsługę większości żądań XML-RPC wysyłanych poprzez sieć. Ponieważ my chcemy pobrać tylko jeden parametr String i również String zwrócić, w naszym programie powyższe typy zupełnie wystarczą. Zbudujmy więc teraz prostą klasę procedury obsługi (przykład 10.1).
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 243
244
Rozdział 10. XML-RPC
Tabela 10.1. Typy Javy obsługiwane przez XML-RPC Typ danych XML-RPC
Typ danych Javy
int boolean string double dateTime.iso8601 struct array base64
int boolean java.lang.String double java.util.Date java.util.Hashtable1 java.util.Vector1 byte[]
Przykład 10.1. Klasa procedury obsługi z metodą uruchamianą zdalnie /** * HelloHandler to prosta procedura obsługi * rejestrowana w serwerze XML-RPC. * * @author Brett McLaughlin * @version 1.0 */ public class HelloHandler { /** *
* * @param name String nazwa użytkownika. * @return String - komunikat Witaj. */ public String sayHello(String name) { return "Witaj " + name; } }
I to naprawdę tylko tyle. Metoda pobiera i zwraca dozwolone parametry XML-RPC, a więc możemy spokojnie zarejestrować ją w serwerze XML-RPC — mamy gwarantowane, że będzie ją można przez XML-RPC wywołać.
Budowanie serwera Skoro procedura obsługi jest już gotowa, musimy napisać program uruchamiający serwer XMLRPC, nasłuchujący żądań i przekazujący je do procedury obsługi. W naszym przypadku w funkcji procedury obsługującej żądania wykorzystamy klasę helma.xmlrpc.WebServer. Można byłoby w tym celu użyć serwletu Javy, ale skorzystanie z „lekkiej” implementacji serwera pozwoli uniknąć konieczności uruchamiania mechanizmu serwletów. W zakończeniu rozdziału zostaną omówione serwlety w kontekście serwera XML-RPC. Wracając do naszego serwera — chcemy pozwolić na określenie portu, na którym serwer zostanie uruchomiony, a następnie spowodować, aby 1
Oczywiście, typy struct i array muszą zawierać tylko inne dozwolone typy XML-RPC.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 244
Powiedział „Witaj!”
245
serwer nasłuchiwał żądań XML-RPC dopóty, dopóki nie zostanie wyłączony. Następnie zarejestrujemy w tym serwerze stworzoną wcześniej klasę i określimy inne parametry specyficzne dla naszej aplikacji. Stwórzmy teraz szkielet tej klasy (przykład 10.2); konieczne jest zaimportowanie klasy WebServer oraz pobranie portu z wiersza poleceń.
Przykład 10.2. Szkielet serwera XML-RPC „Witaj!” import helma.xmlrpc.WebServer; /** * HelloServer to prosty serwer XML-RPC, * który udostępni klasę HelloHandler * wywołaniom XML-RPC. * * @author Brett McLaughlin * @version 1.0 */ public class HelloServer { /** *
* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi. *
*/ public static void main(String[] args) { if (args.length < 1) { System.out.println( "Użycie: java HelloServer [port]"); System.exit(-1); } // Tu uruchomimy serwer na określonym porcie } }
Przed uruchomieniem serwera trzeba podać sterownik SAX, jaki zostanie wykorzystany do przetworzenia i kodowania XML. Domyślny sterownik SAX dla tych bibliotek to parser XP Jamesa Clarka (http://www.jclark.com). Jednak w naszym przykładzie zastosujemy parser Apache Xerces, określając w mechanizmie XML-RPC implementację SAX Parser2. Czynność tę wykonuje się poprzez wywołanie statycznej metody setDriver(), należącej do klasy XmlRpc. Klasa ta leży u podstaw klasy WebServer, ale w celu użycia określonych sterowników SAX konieczne jest oddzielne jej zaimportowanie i bezpośrednie użycie. Metoda zgłasza wyjątek ClassNotFoundException, a więc trzeba go przechwycić w celu określenia, czy klasa sterownika została odnaleziona. Wprowadźmy teraz opisywane zmiany:
2
Obecnie nie istnieją biblioteki XML-RPC obsługujące SAX 2.0 i implementujące interfejs XMLReader. Oczekuje się, że wkrótce ten stan rzeczy ulegnie zmianie; ponieważ klasa Apache Xerces SAXParser implementuje zarówno stary, jak i nowy interfejs, po aktualizacji XML-RPC do SAX-a 2.0 nie trzeba będzie wprowadzać w kodzie żadnych zmian. Jeśli jednak korzystamy z parsera innego producenta, może się okazać konieczne jawne określenie klasy SAX 2.0.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 245
Teraz można już wstawić główny fragment kodu, nasłuchujący HTTP i obsługujący żądania XML-RPC, a następnie rejestrujący klasy procedur obsługi dostępne do wywoływania zdalnego. Tworzenie modułu nasłuchującego jest niezwykle proste; egzemplarz klasy pomocniczej WebServer, o której mówiliśmy, można utworzyć poprzez podanie jej portu do nasłuchiwania — i już nasz serwer będzie obsługiwał żądania XML-RPC! Choć nie mamy jeszcze klas dostępnych do wywołania, to serwer XML-RPC jest już gotowy do pracy. Dodamy linijkę tworzącą i uruchamiającą serwer oraz kod wyświetlający komunikat o stanie. Dodamy także kolejną dyrektywę import dla wyjątku java.io.IOException. Ponieważ serwer musi uruchamiać się na pewnym porcie, wyjątek ten może zostać zgłoszony w przypadku niedostępności portu lub innych problemów z uruchomieniem serwera. Zmodyfikowany kod wygląda następująco: import java.io.IOException; import helma.xmlrpc.WebServer; import helma.xmlrpc.XmlRpc; ... *
* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi. *
*/ public static void main(String[] args) { if (args.length < 1) { System.out.println( "Użycie: java HelloServer [port]"); System.exit(-1); }
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 246
Powiedział „Witaj!”
247
try { // Korzystamy ze sterownika Apache Xerces SAX XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser"); // Uruchamiamy serwer System.out.println("Uruchamianie serwera XML-RPC..."); WebServer server = new WebServer(Integer.parseInt(args[0])); } catch (ClassNotFoundException e) { System.out.println("Nie odnaleziono sterownika SAX"); } catch (IOException e) { System.out.println("Uruchomienie serwera nie jest możliwe: " + e.getMessage()); } } ...
Możemy teraz skompilować i wypróbować powyższą klasę. Powinien zostać wyświetlony komunikat o stanie, a następnie program powinien zatrzymać się, oczekując na żądania. Teraz trzeba dodać klasę procedury obsługi przyjmującą żądania. Jedną z najistotniejszych różnic pomiędzy technologiami RMI i RPC jest sposób udostępniania metod. W RMI zdalny interfejs posiada sygnatury metod odpowiadające wszystkim metodom zdalnym. Jeśli metoda implementowana jest na klasie serwera, ale do zdalnego interfejsu nie dodano pasującej sygnatury, nowa metoda nie będzie mogła zostać wywołana przez klienta RMI. Z całym procesem łączy się więc konieczność modyfikacji sporej ilości kodu i rekompilacji klas RMI. W RPC wszystko to wygląda inaczej — a sposób obsługi uznawany jest za elastyczniejszy i prostszy. Serwer RPC otrzymuje żądanie zawierające parametry i wartość tekstową (zazwyczaj w postaci „nazwaklasy.nazwametody”). Serwer RPC „widzi”, że metoda znajduje się w klasie „nazwaklasy” i nosi nazwę „nazwametody”. Próbuje więc odnaleźć pasującą klasę i metodę pobierającą parametr takiego typu, jaki jest w żądaniu RPC. Po znalezieniu określona metoda zostaje wywołana, a rezultat tego wywołania jest kodowany i odsyłany klientowi. Całe to nieco zawiłe wyjaśnienie sprowadza się do jednego — żądana metoda nigdy nie jest jawnie definiowana w serwerze XML-RPC, a jedynie w żądaniu otrzymanym od klienta. W serwerze rejestrowany jest jedynie egzemplarz klasy. Do klasy tej można dodawać metody, a ich udostępnienie wymaga jedynie ponownego uruchomienia serwera — a nie zmian w kodzie. O ile tylko potrafimy określić i wysłać poprawne parametry do serwera, to metoda taka jest od razu dostępna. To właśnie jedna z zalet XML-RPC względem RMI — interfejs API jest reprezentowany w sposób bliższy rzeczywistości; w kliencie nie trzeba aktualizować żadnych procedur pośredniczących, szkieletów czy interfejsów. Po dodaniu metody jej sygnatura może zostać opublikowana klientom i natychmiast wykorzystana. Skoro wiemy już, w jak prosty sposób wykorzystywana jest procedura obsługi RPC, zarejestrujmy taką procedurę w naszym przykładzie. Klasa WebServer umożliwia dodanie procedury poprzez metodę addHandler(). Metoda ta jako dane wejściowe pobiera nazwę, pod jaką procedura obsługi zostanie zarejestrowana, oraz sam egzemplarz klasy tej procedury. Zazwyczaj tworzy się w tym celu egzemplarz nowej klasy za pomocą jej konstruktora (poprzez słowo kluczowe new), choć w dalszej części Czytelnik zobaczy, w jaki sposób można zrobić to inaczej (gdy egzemplarz ma być współdzielony przez wiele klientów, a nie tworzony dla każdego oddzielnie). Ale w poniższym przykładzie stworzenie egzemplarza nowej klasy jest dobrym rozwiązaniem. Zarejestrujemy klasę HelloHandler pod nazwą „hello”. Dodamy także instrukcje wyświetlające dalsze komunikaty, tak by możliwe było „podejrzenie”, co dzieje się w serwerze w czasie dodawania procedury obsługi:
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 247
248
Rozdział 10. XML-RPC import java.io.IOException; import helma.xmlrpc.WebServer; import helma.xmlrpc.XmlRpc; /** * HelloServer to prosty serwer XML-RPC, * który udostępni klasę HelloHandler * wywołaniom XML-RPC. * * @author Brett McLaughlin * @version 1.0 */ public class HelloServer { /** *
* Uruchamiamy serwer XML-RPC i rejestrujemy procedurę obsługi. *
*/ public static void main(String[] args) { if (args.length < 1) { System.out.println( "Użycie: java HelloServer [port]"); System.exit(-1); } try { // Korzystamy ze sterownika Apache Xerces SAX XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser"); // Uruchamiamy serwer System.out.println("Uruchamianie serwera XML-RPC..."); WebServer server = new WebServer(Integer.parseInt(args[0])); // Rejestrujemy klasę procedury obsługi server.addHandler("hello", new HelloHandler()); System.out.println( "Klasa procedury obsługi zarejestrowana jako \"hello\""); System.out.println("Teraz czekamy na żądania..."); } catch (ClassNotFoundException e) { System.out.println("Nie odnaleziono sterownika SAX"); } catch (IOException e) { System.out.println("Uruchomienie serwera nie jest możliwe: " + e.getMessage()); } } }
Po skompilowaniu powyższego kodu źródłowego można uruchomić serwer. Wynik powinien być podobny do tego z przykładu 10.33.
3
Jeśli Czytelnik korzysta z systemu Unix i chce uruchomić usługę na porcie niższym niż 1024, musi być zalogowany jako root. Aby uniknąć problemu, warto skorzystać z wyższego portu, tak jak to pokazano na przykładzie.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 248
Powiedział „Witaj!”
249
Przykład 10.3. Uruchamianie klasy serwera XML-RPC HelloServer [adam@maly ch10]$ java HelloServer 8585 Uruchamianie serwera XML-RPC... Klasa procedury obsługi zarejestrowana jako "hello" Teraz czekamy na żądania...
Tak, to właśnie jest takie proste! Teraz możemy utworzyć klienta i „bawić się” komunikacją w sieci poprzez XML-RPC. To właśnie kolejna zaleta XML-RPC — niewiele potrzeba, aby taki mechanizm uruchomić, szczególnie jeśli porównamy to rozwiązanie z technologią RMI. W trakcie lektury kolejnego podrozdziału Czytelnik przekona się, że utworzenie klienta jest równie łatwe.
Budowanie klienta Skoro serwer działa i przyjmuje żądania, to mamy już za sobą najtrudniejszy etap programowania aplikacji XML-RPC (tak, to była ta trudniejsza część!). Teraz zbudujemy prostego klienta wywołującego zdalnie naszą metodę sayHello(). Korzystamy w tym celu z klasy helma.xmlrpc.XmlRpcClient. Odpowiedzialna jest ona za wiele czynności odbywających się po stronie klienta; jej odpowiednikami po stronie klienta są XmlRpcServer i WebServer. Aby utworzyć klienta, oprócz wspomnianej klasy będziemy jeszcze potrzebowali klasy XmlRpc — klient musi umieć zakodować żądanie, a więc znów musimy ustawić klasę sterownika SAX do wykorzystania w metodzie setDriver(). Zacznijmy tworzenie klienta właśnie od tych ważnych instrukcji, sprawdzając argument przekazywany do metody sayHello() na serwerze oraz wykonując podstawową obsługę wyjątków. Tworzymy plik źródłowy Javy HelloClient.java — taki jak w przykładzie 10.4.
Przykład 10.4. Klient XML-RPC import helma.xmlrpc.XmlRpc; import helma.xmlrpc.XmlRpcClient; /** * HelloClient to prosty klient XML-RPC * wysyłający żądanie do HelloServer. * * @author Brett McLaughlin * @version 1.0 */ public class HelloClient { /** *
* Łączymy się z serwerem XML-RPC i wysyłamy żądanie. *
*/ public static void main(String args[]) { if (args.length < 1) { System.out.println( "Użycie: java HelloClient [twoje imie]"); System.exit(-1); } try { // Korzystamy ze sterownika Apache Xerces SAX XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 249
Tak jak cały kod w tym rozdziale, powyższy przykład nie powinien sprawiać Czytelnikowi żadnych trudności. Zbudowanie klienta XML-RPC wymaga najpierw stworzenia klasy XmlRpcClient, której dostarcza się nazwę serwera XML-RPC. Nazwa tego serwera powinna mieć postać pełnego adresu URL, łącznie z przedrostkiem http://. Jeśli adres ten ma nieodpowiedni format, to w czasie tworzenia klienta zostanie zgłoszony wyjątek java.net.MalformedURLException. Dodajmy wspomnianą klasę do listy klas importowanych, stwórzmy egzemplarz naszego klienta i dodajmy wymaganą procedurę obsługi: import java.io.IOException; import helma.xmlrpc.XmlRpc; import helma.xmlrpc.XmlRpcClient; /** * HelloClient to prosty klient XML-RPC * wysyłający żądanie do HelloServer. * * @author Brett McLaughlin * @version 1.0 */ public class HelloClient { /** *
* Łączymy się z serwerem XML-RPC i wysyłamy żądanie. *
*/ public static void main(String args[]) { if (args.length < 1) { System.out.println( "Użycie: java HelloClient [twoje imie]"); System.exit(-1); } try { // Korzystamy ze sterownika Apache Xerces SAX XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser"); // Określamy serwer XmlRpcClient client = new XmlRpcClient("http://localhost:8585/"); // Tworzymy żądanie // Wysyłamy żądanie i drukujemy wynik
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 250
Choć faktyczne żądania RPC nie są jeszcze wysyłane, to jest to już w pełni funkcjonalna aplikacja klienta. Można ją skompilować i uruchomić, choć nie dostrzeżemy żadnych objawów jej działania — przecież aż do wysłania żądania nie następuje komunikacja pomiędzy obydwoma komponentami naszego rozwiązania. Oczywiście, w kodzie źródłowym należy wpisać numer portu, na jakim zamierzamy uruchomić serwer. Rzecz jasna, nie jest to rewelacyjny sposób uzyskiwania komunikacji pomiędzy klientem a serwerem; zmiana portu, na którym nasłuchuje serwer, wymaga zmiany kodu klienta! W następnym rozdziale przedstawiony zostanie sposób rozwiązania tego problemu.
Widać teraz, jak proste jest tworzenie serwera i klienta oraz jak niewiele potrzeba, aby uruchomić mechanizm XML-RPC. Jednak z naszego programu nie będzie dużego pożytku, dopóki nie zdefiniujemy, jak ma wysyłać żądania i otrzymywać odpowiedzi. Zakodowanie żądania wymaga wywołania na egzemplarzu XmlRpcClient metody execute(). Pobiera ona dwa parametry: nazwę identyfikatora klasy oraz metodę do wywołania (mające postać pojedynczego parametru String) oraz Vector parametrów przekazywanych do określonej metody. Identyfikator klasy to nazwa, jaką zarejestrowaliśmy dla klasy HelloHandler na serwerze XML-RPC. Identyfikator ten może być po prostu nazwą klasy, ale zazwyczaj korzysta się z bardziej czytelnej i zrozumiałej dla klienta nazwy — tak jak u nas „hello”. Dalej dołączana jest nazwa wywoływanej metody, oddzielona od identyfikatora klasy kropką — [identyfikator klasy].[nazwa metody]. Parametry muszą mieć postać wektora Vector Javy i powinny zawierać wszystkie obiekty parametrów wymagane przez określoną metodę. W naszej prostej metodzie sayHello() jest to String zawierający nazwę użytkownika, którą podajemy w wierszu poleceń. Kiedy już klient XML-RPC zakoduje żądanie, wysyła je do serwera. Serwer odnajduje klasę pasującą do podanego identyfikatora i szuka odpowiedniej metody. Jeśli zostanie ona znaleziona, przyjmowane przez nią parametry porównywane są z tymi z żądania. Jeśli parametry się zgadzają, następuje wykonanie metody. W przypadku znalezienia wielu metod o tej samej nazwie, metodę do wywołania rozpoznaje się po parametrach; jest to zwykły proces w Javie, określany mianem przeładowania (ang. overloading). Wynik działania metody kodowany jest przez serwer XML-RPC i odsyłany klientowi jako Object Javy (który z kolei może być wektorem obiektów!). Wynik ten może być potem rzutowany na odpowiedni typ Javy i zwyczajnie wykorzystywany w kliencie. Jeśli nie zostanie znaleziona pasująca sygnatura identyfikatora lub metody lub parametrów, klientowi zgłasza się wyjątek XmlRpcException. To gwarantuje, że klient nie będzie próbował wywoływać nieistniejących metod lub metod istniejących, ale z niewłaściwymi parametrami. Cała powyższa procedura zawarta jest w zaledwie kilku wierszach kodu Javy. Przede wszystkim konieczne jest zaimportowanie klasy XmlRpcException oraz java.io.IOException; ten
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 251
252
Rozdział 10. XML-RPC
ostatni wyjątek zgłaszany jest w przypadku nieprawidłowej komunikacji pomiędzy klientem a serwerem. Następnie dodajemy klasę Vector i tworzymy jej egzemplarz, podając mu jeden parametr String. To umożliwi wywołanie metody execute() z nazwą procedury obsługi, metodą do wywołania i jej parametrami; wynik jej działania rzutowany jest na String, ten zaś zostaje wyświetlony na ekranie. W niniejszym przykładzie zakładamy, że serwer XML-RPC działa na komputerze lokalnym, na porcie 8585: import java.io.IOException; import java.net.MalformedURLException; import java.util.Vector; import helma.xmlrpc.XmlRpc; import helma.xmlrpc.XmlRpcClient; import helma.xmlrpc.XmlRpcException; ... /** *
* Łączymy się z serwerem XML-RPC i wysyłamy żądanie. *
To już wszystko! Przykład kompilujemy i uruchamiamy zgodnie z poniższym opisem.
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 252
Po co się męczyć? Niech serwer nas wyręczy!
253
Mów do mnie... Należy sprawdzić, czy w ścieżce dostępu do klas znajdują się klasy XML-RPC i klasy opisywanych wyżej przykładów. Musi się tam znaleźć również Apache Xerces lub inny sterownik SAX —z jego pomocą programy wykonują przetwarzanie. Kiedy klasy są już dostępne, uruchamiamy serwer z odpowiednim numerem portu. W systemie Windows, aby uruchomić serwer jako oddzielny proces, korzystamy z polecenia start: D:\prod\Java and XML\WEB-INF\classes>start java HelloServer 8585 Uruchamianie serwera XML-RPC... Klasa procedury obsługi zarejestrowana jako "hello" Teraz czekamy na żądania...
W systemach Unix uruchamiamy proces w tle (wstawiając na końcu &), co umożliwi nam uruchomienie na tej samej konsoli również klienta; ewentualnie klienta można uruchomić na innej konsoli, kopiując wcześniej środowisko uruchomieniowe: $ java HelloServer 8585 & Uruchamianie serwera XML-RPC... Klasa procedury obsługi zarejestrowana jako "hello" Teraz czekamy na żądania...
Teraz uruchamiamy klienta, jako argument wywołania podając własne imię. Wkrótce pojawi się odpowiedź podobna do tej w przykładzie 10.5 — serwer HelloServer otrzymał i obsłużył żądanie oraz zwrócił wynik działania metody sayHello():
Przykład 10.5. Działanie klasy HelloClient $ java HelloClient Brett Odpowiedź serwera: Witaj Brett
I tak właśnie działa XML-RPC. Oczywiście, aplikacja taka jak powyższa nie jest zbyt przydatna, ale na pewno dobrze ilustruje podstawy działania i łatwość budowania serwera i klienta XML-RPC w Javie. Po takim wprowadzeniu możemy przejść do bardziej realistycznego przykładu. W dalszej części rozdziału zbudujemy praktyczniejszy i pożyteczniejszy serwer; Czytelnik będzie mógł również zobaczyć, jak wyglądają procedury obsługi XML-RPC. Następnie stworzymy klienta (podobnego do HelloClient) i przetestujemy działanie nowego kodu.
Po co się męczyć? Niech serwer nas wyręczy! Opisany wyżej przykład Hello dobrze demonstruje sposób korzystania z XML-RPC w Javie, ale nie jest zbyt realistyczny. Kod jest banalny, serwer mało elastyczny, a sama procedura obsługi nie ukazuje praktycznego zastosowania technologii XML-RPC. Poniżej przedstawione zostanie zastosowanie technologii XML-RPC w środowisku produkcyjnym — zbudujemy bardziej użyteczną procedurę obsługi i bardziej praktyczny serwer. Kod taki, choć może wciąż jeszcze nieprzydatny w „prawdziwym” projekcie, na pewno zilustruje, do czego Czytelnik mógłby wykorzystać model XML-RPC w swojej przyszłej pracy.
Współużytkowana procedura obsługi Utworzona wcześniej klasa HelloHandler była prosta i... bezużyteczna. Trzeba pamiętać, że z technologii XML-RPC w dużej mierze korzysta się po to, aby zrzucić ciężar wykonywania bardziej złożonych zadań na serwer — uproszczony klient ma tylko żądać wywołania procedury i ko-
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 253
254
Rozdział 10. XML-RPC
rzystać ze zwróconych wyników. Co więcej, możliwe jest, że część, a nawet wszystkie obliczenia wymagane do zwrócenia odpowiedzi na żądanie można wykonać zawczasu; inaczej mówiąc, klasa procedury obsługi sama wykonuje zadania i kiedy nadchodzi wywołanie metody, wyniki są już gotowe do odesłania. W tej chwili programistom Javy powinny przyjść na myśl wątki i współużytkowane egzemplarze danych. Właśnie te zagadnienia zostaną poniżej zilustrowane za pomocą prostej klasy Scheduler (rozkład zadań). Do stworzonego rozkładu zadań klienty będą mogły dodawać i usuwać zdarzenia. Będzie również możliwość pobrania z rozkładu listy wszystkich zdarzeń w kolejce. Aby aplikację jeszcze bardziej zbliżyć do rzeczywistości (i żeby serwer miał co robić), nakażemy sortowanie zwróconych zadań według kolejności ich wykonywania. Zdarzenia będą miały po prostu postać łańcucha String, zawierającego nazwę i czas zdarzenia w formacie java.util.Date. Nie jest to może kompletna implementacja rozkładu zadań, ale dobrze demonstruje działanie serwera, wykonuje pewne zadania „za kulisami”. Po pierwsze, stworzymy metody addEvent() i removeEvent() (odpowiednio: dodaj i usuń zdarzenie). Ponieważ obie te metody są wywoływane przez klienta, nie ma tutaj nic nowego; ciekawe jest natomiast to, jak zachowamy te zadania w klasie Scheduler. Serwer XML-RPC stworzy egzemplarz tej klasy i egzemplarz ten będzie potem wykorzystywany we wszystkich żądaniach przychodzących do serwera; jest jednak możliwe, a nawet prawdopodobne, że z naszym rozkładem zajęć będą komunikowały się inne klasy lub wręcz inne serwery XML-RPC. Jeśli listę zdarzeń będziemy składować jako zmienną przynależną do klasy, nie będzie możliwe współużytkowanie tych danych przez wiele egzemplarzy. Trzeba więc stworzyć dane składowane statycznie i współużytkowane przez wszystkie egzemplarze klasy Scheduler. Do przechowania nazwy i czasu zdarzenia najbardziej odpowiedni wydaje się typ Hashtable, w którym dane przechowywane są w parach klucz-wartość. Oprócz tego nazwy zdarzeń zachowamy w wektorze Vector. Wymaga to wykorzystania dodatkowej pamięci masowej (oraz pamięci w wirtualnej maszynie Javy), ale za to sortować będziemy Vector, a nie Hashtable — zaleta takiego rozwiązania polega na tym, że będziemy zamieniać miejscami pozycje wektora (za każdym razem tylko pojedyncza zamiana), a nie Hashtable (każdorazowo dwie zamiany). Stwórzmy szkielet takiej klasy i dodajmy dwie pierwsze metody, pozwalające dodać lub usunąć zdarzenie. Dodamy także miejsce składowania zdarzeń, ale implementację pobierania i sortowania zdarzeń zostawimy na później. Odpowiedni kod można obejrzeć w przykładzie 10.6.
Przykład 10.6. Klasa rozkładu zadań Scheduler import java.util.Date; import java.util.Hashtable; import java.util.Vector; /** * Scheduler to klasa umożliwiająca * dodawanie, usuwanie i pobieranie posortowanych według czasu * występowania zadań. * * @author Brett McLaughlin * @version 1.0 */ public class Scheduler { /** Lista nazw zdarzeń (do sortowania) */ private static Vector events = null; /** Szczegóły zdarzeń (nazwa, czas) */
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 254
* Tutaj inicjalizujemy miejsce składowania zdarzeń. *
*/ public Scheduler() { events = new Vector(); eventDetails = new Hashtable(); } /** *
* Tutaj dodajemy żądane zdarzenie. *
* * @param eventName String nazwa zdarzenia do dodania. * @param eventTime Date data zdarzenia. * @return boolean - wskazuje, czy zdarzenie zostało dodane. */ public boolean addEvent(String eventName, Date eventTime) { // Dodajemy tę listę do listy zdarzeń if (!events.contains(eventName)) { events.addElement(eventName); eventDetails.put(eventName, eventTime); } return true; } /** *
* Tutaj usuwamy żądane zdarzenie. *
* * @param eventName String nazwa zdarzenia do usunięcia. * @return boolean - wskazuje, czy usunięto zdarzenie. */ public synchronized boolean removeEvent(String eventName) { events.remove(eventName); eventDetails.remove(eventName); return true; }
}
Metoda addEvent() dodaje nazwę zdarzenia do obu obiektów, w których składujemy dane, a czas zdarzenia — tylko do obiektu Hashtable. Metoda removeEvent() ma działanie przeciwne. Obie zwracają wartość boolean. Choć w przykładzie ta wartość to zawsze true, w bardziej złożonych aplikacjach można ją wykorzystać do informowania o problemach związanych z dodawaniem lub usuwaniem zdarzeń. Skoro dodawanie i usuwanie zdarzeń jest już możliwe, teraz musimy dodać metodę zwracającą listę zdarzeń. Metoda ta zwraca wszystkie składowane zdarzenia, bez względu na to, który klient (lub aplikacja) to zdarzenie dodał. Mogą więc to być zdarzenia dodane przez innego klienta XMLRPC, inny serwer XML-RPC, inną aplikację albo samodzielną implementację tego samego rozkładu zadań. Ponieważ zwracane dane to pojedynczy Object, można zwrócić Vector sformatowa-
C:\WINDOWS\Pulpit\Szymon\Java i XML\10-08.doc — strona 255
256
Rozdział 10. XML-RPC
nych wartości String, zawierających nazwę i czas każdego zdarzenia. Oczywiście, w aplikacji bliższej rzeczywistości mógłby zostać zwrócony Vector zdarzeń albo jakiś inny typ danych (z datą w obiekcie Date itd.). Nasza metoda ma jednak umożliwić podgląd danych, a nie wykonywanie na nich dalszych operacji. Do zwrócenia listy zdarzeń wykorzystamy klasę java.text.SimpleDateFormat, umożliwiającą tekstowe formatowanie obiektów Date. Odpowiedni String, zawierający nazwę i czas zdarzenia, tworzony jest w pętli dla każdego zdarzenia; potem dołączany jest do listy wynikowej Vector, która to lista zostanie zwrócona klientowi. Dodajmy więc wymaganą instrukcję importującą oraz kod zwracający zdarzenia: import java.text.SimpleDateFormat; import java.util.Date; import java.util.Hashtable; import java.util.Vector; ... /** *
* Tutaj zwracamy bieżącą listę zdarzeń. *
* * @return Vector - lista zdarzeń. */ public Vector getListOfEvents() { Vector list = new Vector(); // Tworzymy format danych. SimpleDateFormat fmt = new SimpleDateFormat("hh:mm a MM/dd/yyyy"); // Dodajemy każde zdarzenie do listy for (int i=0; i