Uwaga! Pułapka! 175
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ć!
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:
-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
========== 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.
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.
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.
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:
<xsl:template match:"elementMacierzysty">
<!-- Tutaj zawartość drzewa wynikowego -->
<xsl:apply-templates select="elementPotomny1|elementPotomny2" />
<!-- Tutaj dalsza zawartość drzewa wynikowego -->
</xsl:template>
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.
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.
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.
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.
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.
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.
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 skoncen...
lukaszwalda