|
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 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.
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);
} 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.
}
}
}
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ć 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ń. Nie jest oparty na DOM-ie czy SAX-ie. Pozwala użytkownikowi pracować na dokumencie XML w postaci drzewiastej bez konieczności 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.
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;
/**
* <p>
* Tworzenie obiektu JDOM Document od zera
* </p>
*
* @version 1.0
*/
public class FromScratch {
/**
* <p>
* Tworzymy prosty dokument XML w pamięci
* </p>
*/
public static void main(String[] args) {
Document doc = new Document(new Element("root"));
System.out.println("Dokument został utworzony");
}
}
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 DOMBuilder. 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;
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 org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.Builder;
import org.jdom.input.DOMBuilder;
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.
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:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<!-- itd -->
</html>
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.
Instrukcje przetwarzania
Instrukcje PI w dokumencie XML reprezentuje w Javie klasa ProcessingInstruction, udostępniająca proste akcesory i mutatory. Listę wszystkich instrukcji przetwarzania 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<size; i++) {
ProcessingInstruction pi = (ProcessingInstruction)pis.get(i);
String cel = pi.getTarget();
String dane = pi.getData();
}
Można również pobrać listę wszystkich instrukcji PI zawierających określony cel. Służy do tego metoda getProcessingInstructions(String target).
Instrukcje PI tworzymy poprzez podanie celu i danych do konstruktora ProcessingInstruction:
ProcessingInstruction pi =
new ProcessingInstruction("cocoon-process", "type=\"xslt\"");
Powyższy fragment kodu spowodowałby utworzenie następującej instrukcji PI:
<?cocoon-process type="xslt"?>
Istnieje szereg metod pomocniczych. Bardzo często dane do instrukcji przetwarzania dostarcza się w postaci par nazwa-wartość:
<?xml-stylesheet href="XSL\JavaXML.wml.xsl" type="text/xsl" media="wap"?>
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 getData(), 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)
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:
<?xml version="1.0"?>
<linux-config>
<gui>
<menedzer-okien>
<nazwa>Enligtenment</nazwa>
<wersja>0.16.2</wersja>
</menedzer-okien>
<menedzer-okien>
<nazwa>KWM dla KDE</nazwa>
<wersja>1.1.2</wersja>
</menedzer-okien>
</gui>
<dzwiek>
<karta>
<nazwa>Sound Blaster Platinum</nazwa>
<irq>7</irq>
<dma>0</dma>
<io start="D800" stop="D81F" />
</karta>
</dzwiek>
</linux-config>
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();
String przerwanieKarty = root.getChild("dzwiek")
.getChild("karta")
.getChild("irq")
.getContent();
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/");
// 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 JDOM-ie 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");
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ę <!-- taką składnią -->. 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:
<?xml version="1.0"?>
<!-- Komentarz na najwyższym poziomie: Java i XML, Brett McLaughlin -->
<JavaXML:Ksiazka xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/">
<JavaXML:Tytul>Java i XML</JavaXML:Tytul>
<!-- Komentarz zagnieżdżony w elemencie JavaXML:Ksiazka: Spis treści -->
<JavaXML:Spis>
Czytamy sobie spis treści!
</JavaXML:Spis>
</JavaXML:Ksiazka>
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<size; i++) {
Object o = docContent.get(i);
if (o instanceof Comment) {
Comment o = (Comment)o;
String text = c.getText();
}
}
for (int i=0, size=elemContent.size(); i<size; i++) {
Object o = elemContent.get(i);
if (o instanceof Comment) {
Comment o = (Comment)o;
String text = c.getText();
}
}
Konstruktor Comment jako jedyny argument pobiera tekst komentarza. Obiekt Document umożliwia dodawanie komentarzy metodą addComment(Comment), a klasa Element udostępnia do tego samego celu metodę addChild(Comment):
// Utwórz komentarz
Comment docComment = new Comment("Komentarz na najwyższym (głównym) poziomie");
// Dodaj komentarz do obiektu Document
doc.addComment(docComment);
// Utwórz jeszcze jeden komentarz
Comment elemComment = new Comment("Komentarz zagnieżdżony w elemencie");
// Dodaj komentarz do elementu
doc.getRootElement()
.getChild("Spis")
.addChild(elemComment);
Dokument wyjściowy
Proces uzyskiwania wyjściowego obiektu JDOM Document jest jeszcze prostszy niż jego tworzenie. Uzyskanie takiego obiektu dla różnych źródeł umożliwia pakiet klas narzędziowych i pomocniczych org.jdom.output. Nie ma interfejsu do definiowania wymaganego zachowania, ponieważ dane wyjściowe mogą zostać wykorzystane na rozmaite sposoby — od prostego zapisania do pliku aż po tak złożone zastosowania, jak np. powodowanie zdarzeń w innej aplikacji.
Standardowe wyjście XML
Najczęściej dane wyjściowe z obiektu JDOM Document przekazywane są jako kod XML do pliku lub innego składnika aplikacji za pomocą metody OutputStream. Oczywiście, strumień taki może być skierowany na konsolę, do pliku, pod adres URL lub do dowolnego innego celu potrafiącego odbierać dane. Zadanie to obsługiwane jest w JDOM przez klasę org.jdom.output.XMLOutputter. Klasa ta udostępnia następujące konstruktory i metody:
public class XMLOutputter {
// Akceptujemy ustawienia domyślne: wcięcie 2 spacje, obecne znaki nowego wiersza
public XMLOutputter()
// Określamy wcięcie, akceptujemy domyślnie włączone znaki nowego wiersza
public XMLOutputter(String indent);
// Określamy wcięcie oraz to, czy znaki nowego wiersza mają być wstawiane
public XMLOutputter(String indent, boolean newlines);
// Wyrzucamy JDOM Document na wyjście
public void output(Document doc, OutputStream out)throws IOException;
}
Domyślny konstruktor zwraca „ładnie wydrukowany” obiekt JDOM Document; dzięki opcjom można „ścisnąć” plik (wyłączyć znaki nowego wiersza i usunąć wcięcia). Poniżej widoczna jest przedstawiana wcześniej klasa SAXTest, tym razem drukująca dokument na wyjście standardowe:
import java.io.File;
import java.io.IOException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.Builder;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
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]));
printDocument(doc);
} catch (JDOMException e) {
if (e.getRootCause() != null) {
e.getRootCause().printStackTrace();
}
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void printDocument(Document doc) throws IOException {
XMLOutputter fmt = new XMLOutputter();
fmt.output(doc, System.out);
}
}
Należy zauważyć, że w metodach tworzących obiekty JDOM Document za pomocą SAXBuilder i DOMBuilder nie wykonaliśmy żadnych operacji na danych; zbudowany Document jest od razu gotowy do wysłania na wyjście, co czyni proces odczytywania i zapisywania danych XML (np. z jednego źródła do drugiego) niezwykle prostym.
Uruchamianie zdarzeń SAX
Poznaliśmy już zastosowania JDOM — nawet gdy oryginalne dane XML są dostępne jedynie w postaci gotowego drzewa DOM; DOMBuilder konwertuje to drzewo na dużo „lżejszy” obiekt JDOM Document, a do operacji na danych XML można wykorzystać interfejs JDOM API. W podobny sposób JDOM potrafi się komunikować z innymi aplikacjami, oczekującymi jako wejścia zdarzeń SAX. Klasa org.jdom.SAXOutputter umożliwia uruchamianie zdarzeń SAX z poziomu dostarczonego obiektu JDOM Document. Składniki aplikacji są więc całkowicie odizolowane — korzystamy z JDOM-a, ale nie tracimy interakcji z aplikacją nie znającą tego standardu (albo jeszcze nie znającą!). Co więcej, istnieje klasa DOMOutputter, umożliwiająca wykonanie tych samych zadań, ale przy konwersji dokumentu JDOM na drzewo DOM.
Bardzo istotna jest elastyczność, jaką charakteryzują się obie klasy: nie są związane ze specyficznym formatem, tzn. umożliwiają wykorzystanie dowolnego źródła wejściowego i typu wyjściowego. Na przykład klasa ApacheOutputter może służyć do uzyskania obiektu wyjściowego Document stworzonego poprzez ApacheBuilder i zapisania go w postaci pliku konfiguracyjnego serwera Apache. Formaty wynikowe mogą być tak różne, jak różne są formaty wejściowe
— JDOM to tylko model obiektowy, a nie specyficzny model XML.
JDOM w działaniu
Przed nami bardziej kompletny przykład wykorzystania interfejsu JDOM. W przykładzie 8.5 przedstawiony jest „pakiet testowy” budujący od podstaw obiekt JDOM Document, korzystając zarówno z klasy SAXBuilder, jak i z DOMBuilder.
Przykład 8.5. Pakiet testowy JDOM
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import org.jdom.Attribute;
import org.jdom.Comment;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.ProcessingInstruction;
import org.jdom.input.Builder;
import org.jdom.input.DOMBuilder;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
/**
* <p>
* Demonstruje budowanie obiektów JDOM Documents od zera i z istniejących
* źródeł danych.
* </p>
*
* @version 1.0
*/
public class JDOMTest {
public JDOMTest() {
}
/**
* <p>
* Buduje JDOM <code>Document</code> od zera
* </p>
*
* @param out <code>OutputStream</code> tu zapisany utworzony XML
* @throws <code>IOException</code> kiedy pojawi się błąd.
*/
public void newDocument(OutputStream out)
throws IOException, JDOMException {
Namespace ns = Namespace.getNamespace("linux", "http://www.linux.org");
Document doc =
new Document(new Element("config", ns))
.setDocType(new DocType("linux:config",
"DTD/linux.dtd"))
.addProcessingInstruction("cocoon-process",
"type=\"xsp\"")
.addProcessingInstruction(
new ProcessingInstruction("cocoon-process",
"type=\"xslt\""));
doc.getRootElement()
.addAttribute("kernel", "2.2.14")
.addAttribute(
new Attribute("dist", "RedHat 6.1"))
.addChild(new Element("gui", ns)
.setContent("Nie zainstalowano menadżera okien"))
.addChild(new Comment("Konfiguracja karty dźwiękowej"))
.addChild(new Element("dzwiek")
.addChild(new Comment("Karta Sound Blaster"))
.addChild(new Element("karta")
.addChild(new Element("nazwa")
.setContent("Sound Blaster Platinum")))
);
XMLOutputter fmt = new XMLOutputter();
fmt.output(doc, out);
}
public void domDocument(File file, OutputStream out)
throws IOException, JDOMException {
Builder builder = new DOMBuilder(true);
Document doc = builder.build(file);
XMLOutputter fmt = new XMLOutputter();
fmt.output(doc, out);
}
public void saxDocument(File file, OutputStream out)
throws IOException, JDOMException {
Builder builder = new SAXBuilder(true);
Document doc = builder.build(file);
XMLOutputter fmt = new XMLOutputter();
fmt.output(doc, out);
}
/**
* <p>
* Statyczny punkt wyjściowy do przeprowadzenia testu JDOM.
* </p>
*/
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Użycie: JDOMTest [plik do przetworzenia]");
System.exit(-1);
}
try {
JDOMTest test = new JDOMTest();
System.out.println(
"\n\n----------------------");
System.out.println(
"Testuję tworzenie dokumentu od zera...");
System.out.println(
"----------------------\n\n");
test.newDocument(System.out);
System.out.println(
"\n\n----------------------");
System.out.println(
"Testuję odczytywanie dokumentu z DOM...");
System.out.println(
"----------------------\n\n");
test.domDocument(new File(args[0]), System.out);
System.out.println(
"\n\n----------------------");
System.out.println(
"Testuję odczytywanie dokumentu z SAX...");
System.out.println(
"----------------------\n\n");
test.saxDocument(new File(args[0]), System.out);
System.out.println(
"\n\n----------------------");
System.out.println(
"Test powiódł się. Zbudowano odpowiednie obiekty.");
} catch (Exception e) {
e.printStackTrace();
if (e instanceof JDOMException) {
System.out.println(((JDOMException)e).getRootCause()
.getMessage());
} else {
System.out.println(e.getMessage());
}
}
}
}
Skompilujmy powyższy program i spójrzmy na zwracany wynik. Plik wynikowy z pobranych danych zostanie wyświetlony dwukrotnie (raz tworzony jest za pomocą SAX-a, a raz za pomocą DOM-a). Wcześniej jednak wyświetlane są dane XML stworzone „od zera”. W przykładzie 8.6 przedstawiony jest fragment wyniku działania programu.
Przykład 8.6. Wynik działania klasy JDOMTest
$java JDOMTest contents.xml
----------------------
Testuję tworzenie dokumentu od zera...
----------------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE linux:config SYSTEM "DTD/linux.dtd">
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<linux:config xmlns:linux="http://www.linux.org" kernel="2.2.14"
dist="RedHat 6.1" />
<linux:gui>Nie zainstalowano menedżera okien</linux:gui>
<!--Konfiguracja karty dźwiękowej-->
<dzwiek>
<!--Karta Sound Blaster-->
<karta>
<nazwa>Sound Blaster Platinum</nazwa>
</karta>
</dzwiek>
</linux:config>
----------------------
Testuję odczytywanie dokumentu z DOM...
----------------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE JavaXML:Ksiazka SYSTEM "DTD/JavaXML.dtd">
<?xml-stylesheet href="XSL\JavaXML.html.xsl" type="text/xsl"?>
<?xml-stylesheet href="XSL\JavaXML.wml.xsl" type="text/xsl"
media="wap"?>
<?cocoon-process type="xslt"?>
<!-- Java i XML -->
<JavaXML:Ksiazka xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/">
<JavaXML:Tytul>Java i XML</JavaXML:Tytul>
<JavaXML:Spis>
<JavaXML:Rozdzial tematyka="XML">
<JavaXML:Naglowek>Wprowadzenie</JavaXML:Naglowek>
<JavaXML:Temat podRozdzialy="7">Co to jest?</JavaXML:Temat>
...
----------------------
Testuję odczytywanie dokumentu z SAX...
----------------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE JavaXML:Ksiazka SYSTEM "DTD/JavaXML.dtd">
<?xml-stylesheet href="XSL\JavaXML.html.xsl" type="text/xsl"?>
<?xml-stylesheet href="XSL\JavaXML.wml.xsl" type="text/xsl"
media="wap"?>
<?cocoon-process type="xslt"?>
<!-- Java i XML -->
<JavaXML:Ksiazka xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/">
<JavaXML:Tytul>Java i XML</JavaXML:Tytul>
<JavaXML:Spis>
<JavaXML:Rozdzial tematyka="XML">
<JavaXML:Naglowek>Wprowadzenie</JavaXML:Naglowek>
...
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.
|
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 org.jdom.Document;
import org.jdom.input.Builder;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
/**
* <b><code>PrettyPrinter</code></b> wyświetla dokument XML spod podanego URI
*
* @author Brett McLaughlin
* @author Jason Hunter
* @version 1.0
*/
public class PrettyPrinter {
/**
* <p>
* Ładnie drukuje XML danego URI
* </p>
*/
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();
}
}
}
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.
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.
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.).
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ć.
202 Rozdział 8. JDOM
Dokument wyjściowy 201
C:\Helion\Java i XML\jAVA I xml\08-08.doc — strona 202
C:\Helion\Java i XML\jAVA I xml\08-08.doc — strona 201
C:\Helion\Java i XML\jAVA I xml\08-08.doc — strona 177