r08-01, ## Documents ##, XML Vademecum profesjonalisty


Rozdział --> 8[Author:T] .
Java i DOM XML

Cały ten rozdział poświęcimy użyciu Javy do stworzenia samodzielnych programów obsługujących XML. Tak naprawdę utworzymy tutaj kilka przeglądarek. Programy pisać będziemy w oparciu o DOM XML, skorzystamy z pakietów XML for Java (XML4J) IBM-owskich alphaWorks (www.alphaworks.ibm.com/tech/xml4j). Ten znany parser zgodny jest ze standardami W3C, zaimplementowano w nim DOM Level 1 i częściowo Level 2. Jest to najczęściej stosowany samodzielny parser współpracujący z Javą. W chwili pisania książki dostępna jest wersja 3.0.1, oparta jest ona parserze XML Apache Xerces w wersji 1.0.3.

alphaWorks z dumą informuje, że

XML Parser for Java jest parserem XML napisanym całkowicie w Javie. Pakiet (com.ibm.xml.parser) zawiera klasy i metody pozwalające parsować, generować, przetwarzać i walidować dokumenty XML. XML Parser for Java uważa się za najsolidniejszy dostępny procesor XML, jest on w pełni zgodny z rekomendacją XML 1.0.

Powyższa notatka wskazuje na ważny problem związany z dostępnymi obecnie parserami XML - ich stan właściwie zawsze jest nie do końca określony. Okazuje się, że wspomniany powyżej pakiet parsera com.ibm.xml.parser jest już przestarzały i choć obecnie jeszcze jest obsługiwany, to w przyszłości ma zostać usunięty. Zamiast niego użyjemy zatem pakietu org.apache.xerces.parsers, który jest jego następcą.

Jest to naturalne ryzyko związane z używaniem parserów firm zewnętrznych - narzędzia te swojego czasu były pisane w sposób bardzo swobodny. Kiedy XML był jeszcze bardzo młodym standardem, napisałem książkę opartą na parserze Microsoft XML Java - jedynym dostępnym wtedy parserze XML z dostępem z Javy. Zanim książka ta pojawiła się na półkach księgarń, Microsoft całkowicie zmienił swój parser, wobec czego niemalże żaden fragment kodu z mojej książki nie działał (zresztą opisywany tam pakiet nie jest już nawet dostępny jako samodzielny pakiet). Takie sytuacje wcale nie są rzadkie.

Z drugiej jednak strony warto pamiętać, że parser alphaWorks zmieniono tak, że obecnie działa w oparciu o DOM W3C (do obsługi węzłów i elementów używać będziemy pakietu org.w3c.dom alphaWorks), co oznacza, że w końcu pojawił się pewien standard. Jednak nazwy pakietów i używanych parserów, jak org.apache.xerces.parsers.DOMParser, mogą się nadal zmieniać. W chwili, kiedy książka ta dotrze do Twoich rąk, pakiety mogą zostać zmienione - jeśli tak się stanie, zajrzyj do dokumentacji XML for Java, aby sprawdzić, jakie zmiany musisz wprowadzić w swoim kodzie. Teraz, kiedy dostępny jest model DOM W3C, zmiany te powinny być zminimalizowane względem tego, co działo się dawniej.

W tym i następnym rozdziale uzyskasz niezłe podstawy używania parsera XML for Java, jednak temat jako taki wystarczyłby do wypełnienia całej książki, zresztą książki takie już zaczynają się pojawiać, ale - niespodzianka! - z powodu zmian w parserze są już nieaktualne. Pakiety XML for Java są naprawdę duże, dołączone są do nich setki stron dokumentacji, jeśli chcesz więc nauczyć się programowania XML for Java w zakresie większym niż opisano w tej książce, zajrzyj do dokumentacji.

Parser XML for Java widziałeś już w pierwszym rozdziale tej książki, gdzie przykład DOMWriter umożliwiał walidację dokumentów XML względem DTD. W pierwszym rozdziale używaliśmy dokumentu greeting.xml:

<?xml version="1.0" encoding="iso-8859-2"?>

<DOKUMENT>

<POZDROWIENIA>

Witaj w XML

</POZDROWIENIA>

<KOMUNIKAT>

Witaj w pokręconym świecie XML.

</KOMUNIKAT>

</DOKUMENT>

Dokument ten walidowaliśmy i uzyskaliśmy następującą informację o błędach:

%java dom.DOMWriter greeting.xml

greeting.xml:

[Error] greeting.xml:2:11: Element type "DOKUMENT" must be declared

[Error] greeting.xml:3:15: Element type "POZDROWIENIA" must be declared

[Error] greeting.xml:6:14: Element type "KOMUNIKAT" must be declared

<?xml version="1.0" encoding="iso-8859-2"?>

<DOKUMENT>

<POZDROWIENIA>

Witaj w XML

</POZDROWIENIA>

<KOMUNIKAT>

Witaj w pokręconym świecie XML.

</KOMUNIKAT>

</DOKUMENT>

W tym rozdziale będziemy sami tworzyć programy Java używając XML for Java bezpośrednio. Będziemy parsować i przeszukiwać dokumenty XML oraz tworzyć samodzielne przeglądarki, a nawet samodzielną specjalizowaną przeglądarkę graficzną, która na podstawie dokumentów XML będzie wyświetlała opisane w tych dokumentach kółka. Taka jest właśnie zaleta umiejętności tworzenia samodzielnych programów Javy używających parserów: można tworzyć własne specjalizowane przeglądarki.

Skąd uzyskać XML for Java

Pierwszym krokiem jest „ściągnięcie” XML for Java z Sieci, spod adresu www.alphaworks.ibm.com/tech/xml4j. Wystarczy odnaleźć przycisk Download na stronie, następnie wybrać plik i kliknąć przycisk Download Now. Jeśli na przykład używasz systemu UNIX, możesz wybrać plik XML4J-bin.3.0.1.tar.gz. Jeśli używasz Windows, możesz użyć pliku XML4J-bin.3.0.1.zip. Możesz też pobrać pliki z kodem źródłowym, co umożliwia Ci samodzielne skompilowanie całości.

Po załadowaniu pliku rozpakuj go (w przypadku systemu Windows upewnij się, że Twoje narzędzie rozpakowujące obsługuje długie nazwy!) - i to już wszystko, pakiet XML for Java jest już zainstalowany, trzeba jeszcze tylko zapewnić, że będzie on widoczny dla Javy.

Ustawianie zmiennej CLASSPATH

Z naszego punktu widzenia XML for Java to ogromny zestaw klas gotowych już do użycia. Klasy te znajdują się w plikach JAR (Archiwa Javy), musimy Javie zapewnić dostęp do klas z tych plików.

W poprzednim rozdziale powiedzieliśmy już nieco o tym, jak się to robi. Mówiliśmy o zmiennej CLASSPATH umożliwiającej wskazanie, gdzie ma wyszukiwać dodatkowych klas. Potrzebne nam pliki JAR to xerces.jar oraz xercesSamples.jar (nazwy te mogą się jednak do czasu wydania książki zmienić).

Niestety, sposób ustawiania zmiennej CLASSPATH jest różny w różnych systemach. Aby na przykład ustawić na stałe tę zmienną w Windows NT, używa się Panelu Sterowania. W okienku dialogowym Ustawienia systemowe kliknij zakładkę Środowisko, następnie kliknij zmienną CLASSPATH i wpisz nową --> wartość[Author:T] . W Windows 95 lub 98 oraz w NT możesz użyć polecenia DOS SET w pliku autoexec.bat. Pamiętaj, że możesz też użyć polecenia SET w oknie MS-DOS - wtedy zmienna CLASSPATH będzie ustawiona póki nie zostanie zamknięte to okno, tak zresztą jest zapewne najłatwiej. Jeśli na przykład pliki xerces.jar oraz xercesSamples.jar znajdują się w katalogu C:\xmlparser\XML4J_3_0_1, polecenie SET będzie miało postać następującą (pamiętaj, że wszystko ma być w jednym wierszu):

C:\SET CLASSPATH=%CLASSPATH%;C:\xmlparser\XML4J_3_0_1\xerces.jar;

C:\xmlparser\XML4J_3_0_1\xercesSamples.jar

Zajrzyj do dokumentacji Javy, znajdziesz tam informacje, jak ustawić zmienną CLASSPATH w Twoim systemie. Istnieje jeszcze jedna możliwość zastąpienia tej zmiennej: można użyć w narzędziach javacjava przełącznika -classpath. Oto na przykład sposób skompilowania programu browser.java, gdzie przełącznika używamy do wskazania ścieżki klas (oba polecenia mają być zapisane w pojedynczych wierszach):

%javac -classpath C:\xmlparser\XML4J_3_0_1\xerces.jar;

C:\xmlparser\XML4J_3_0_1\xercesSamples.jar browser.java

%java -classpath C:\xmlparser\XML4J_3_0_1\xerces.jar;

C:\xmlparser\XML4J_3_0_1\xercesSamples.jar browser

Teraz możemy zacząć programowanie. Najpierw napiszemy prosty program analizujący (parsujący) dokument XML.

Tworzenie parsera

Pierwszy przykład wykorzystania XML for Java obejmie parsowanie dokumentu XML i wyświetlanie liczby pewnych jego elementów. Używać będziemy w tym rozdziale modelu XML DOM, użyjemy klasy DOMParser tworzącej w wyniku drzewo DOM W3C.

Przetwarzać będziemy znany już dokument XML, zamówienia.xml --> [Author:T] :

<?xml version="1.0" encoding="iso-8859-2"?>

<DOKUMENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>Smith</NAZWISKO>

<IMIĘ>Sam</IMIĘ>

</IMIĘNAZWISKO>

<DATA>15 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Pomidory</PRODUKT>

<ILOŚĆ>8</ILOŚĆ>

<CENA>5zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Pomarańcze</PRODUKT>

<ILOŚĆ>24</ILOŚĆ>

<CENA>9.98zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Kredytobiorca niesolidny">

<IMIĘNAZWISKO>

<NAZWISKO>Jones</NAZWISKO>

<IMIĘ>Polly</IMIĘ>

</IMIĘNAZWISKO>

<DATA>20 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Chleb</PRODUKT>

<ILOŚĆ>12</ILOŚĆ>

<CENA>28.80zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Jabłka</PRODUKT>

<ILOŚĆ>6</ILOŚĆ>

<CENA>6.00zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>Weber</NAZWISKO>

<IMIĘ>Bill</IMIĘ>

</IMIĘNAZWISKO>

<DATA>25 października 2001</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>Asparagus</PRODUKT>

<ILOŚĆ>12</ILOŚĆ>

<CENA>11.90zł</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>Sałata</PRODUKT>

<ILOŚĆ>6</ILOŚĆ>

<CENA>31.50zł</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

</DOKUMENT>

W tym przykładzie będziemy przeglądać plik zamówienia.xml i poinformujemy użytkownika, ile wystąpiło w nim elementów KLIENT.

Na początek importujemy potrzebne nam klasy Javy: org.w3c.dom obsługujące interfejs DOM W3C, takie jak Node czy Element, oraz parser XML for Java org.apache.xerces.parsers.DOMParser:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

.

.

.

Program ten nazwiemy FirstParser.java, wobec czego klasą publiczną tego pliku będzie FirstParser:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class FirstParser

{

public static void main(String[] args)

{

.

.

.

}

}

Chcemy parsować dokument XML, więc potrzebujemy obiektu DOMParser:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class FirstParser

{

public static void main(String[] args)

{

DOMParser parser = new DOMParser();

.

.

.

}

}

Klasa DOMParser pochodzi od klasy XMLParser, która oparta jest z kolei na klasie java.lang.Object:

java.lang.Object

|

+--org.apache.xerces.framework.XMLParser

|

+--org.apache.xerces.parsers.DOMParser

Domyślnym konstruktorem klasy DOMParser jest DOMParser(). Metody tej klasy zestawiono w tabeli 8.1.

Słowo kluczowe protected to specyfikator dostępu, taki jak poznane wcześniej private i public. Specyfikator protected działa tak, jak private, poza tym że klasy pochodne mają dostęp do elementów zadeklarowanych jako protected w klasie bazowej. Metody zwrotne z tabeli 8.1 wywoływane są przez obiekty DOMParser, zajmiemy się nimi w następnym rozdziale.

Tabela 8.1.
Metody DOMParser

Metoda

Opis

void attlistDecl(int elementTypeIndex, int attrNameIndex, int attType, java.lang.String enumString, int attDefaultType, int attDefaultValue)

Metoda zwrotna deklaracji atrybutu.

void characters(int dataIndex)

Metoda zwrotna danych znakowych.

void comment(int dataIndex)

Metoda zwrotna komentarzy.

void elementDecl(int elementTypeIndex, XMLValidator.ContentSpec contentSpec)

Metoda zwrotna deklaracji elementu.

void endDATA()

Metoda zwrotna końca sekcji CDATA.

void endDocument()

Metoda zwrotna końca dokumentu.

void endDTD()

Wywoływana na koniec DTD.

void endElement(int elementTypeIndex)

Metoda zwrotna końca elementów.

void endEntityReference(int entityName, int entityType, int entityContext)

Metoda zwrotna końca odwołania do encji.

void endNameSpaceDeclScope(int prefix)

Metoda zwrotna końca zakresu deklaracji przestrzeni nazw.

void externalEntityDecl(int entityNameIndex, int publicIdIndex, int systemIdIndex)

Metoda zwrotna odwołania do encji zewnętrznej.

void externalPEDecl(int entityName, int publicId, int systemId)

Metoda zwrotna deklaracji zewnętrznej encji parametrycznej.

boolean getCreateEntityReferenceNodes()

Zwraca wartość true, jeśli odwołania do encji są do dokumentu włączane jako węzły EntityReference.

protected Element getCurrentElementNode()

Zwraca węzeł bieżącego elementu.

protected boolean getDeferNodeExpansion()

Zwraca wartość true, jeśli dokonano rozwinięcia węzła.

Document getDocument()

Zwraca sam dokument.

protected java.lang.String getDocumentClassName()

Zwraca kwalifikowaną nazwę klasy dokumentu.

boolean getFeature(java.lang.String featureId)

Pobiera bieżący stan wskazanej cechy parsera SAX2.

java.lnag.String[] getFeaturesRecognized()

Pobiera listę cech rozpoznawanych przez parser.

boolean getIncludeIgnorableWhitespace()

Zwraca wartość true, jeśli w drzewie DOM znajdują się węzły tekstowe białych znaków, które można pominąć.

java.lang.String[] getPropertiesRecognized()

Pobiera listę właściwości rozpoznawanych przez parser.

java.lang.Object getProperty(java.lang.String propertyId)

Pobiera wartość właściwości parsera SAX2.

void ignorableWhitespace(int dataIndex)

Metoda zwrotna białych znaków, które można pominąć.

protected void init()

Inicjalizuje parser na stan przed parsowaniem.

void internalEntityDecl(int entityNameIndex, int entityValueIndex)

Metoda zwrotna deklaracji encji wewnętrznej.

void internalPEDecl(int entityName, int entityValue)

Metoda zwrotna deklaracji wewnętrznej encji parametrycznej.

void internalSubset(int internalSubset)

Obsługa internalSubsets z DOM Level 2.

void notationDecl(int notationNameIndex, int publicIdIndex, int systemIdIndex)

Metoda zwrotna deklaracji notacji.

void processingInstruction(int targetIndex, int dataIndex)

Metoda zwrotna instrukcji przetwarzania.

void reset()

Zeruje parser.

void resetOrCopy()

Zeruje lub kopiuje parser.

protected void setCreateEntityReferenceNodes(boolean create)

Określa, czy odwołania do encji w dokumencie są częścią dokumentu czyli węzłami EntityReference.

protected void setDeferNodeExpansion(boolean deferNodeExpansion)

Wskazuje, czy ma być różnicowane rozwijanie węzłów.

protected void setDocumentClassName(java.lang.String documentClassName)

Umożliwia decyzję - jakie grupy dokumentów użyć.

void setFeature(java.lang.String featureId, boolean state)

Ustawia stan dowolnej cechy parsera SAX2.

void setIncludeIgnorableWhitespace(boolean include)

Określa, czy węzły tekstowe z białymi znakami, które można pominąć, włączane są do drzewa DOM.

void setProperty(java.lang.String propertyId, java.lang.Object value)

Ustawia wartość właściwości parsera SAX2.

void startCDATA()

Metoda zwrotna początku sekcji CDATA.

void startDocument(int versionIndex, int encodingIndex, int standAloneIndex)

Metoda zwrotna początku dokumentu.

void startDTD(int rootElementType, int publicId, int systemId)

Metoda zwrotna początku DTD.

void startElement(int elementTypeIndex, XMLAttrList xmlAttrList, int attrListIndex)

Metoda zwrotna początku elementu.

void startEntityReference(int entityName, int entityType, int entityContext)

Metoda zwrotna początku odwołania do encji.

void startNamespaceDeclScope(int prefix, int uri)

Metoda zwrotna początku zakresu deklaracji przestrzeni nazw.

void unparsedEntityDecl(int entityNameIndex, int publicIdIndex, int systemIdIndex, int notationNameIndex)

Metoda zwrotna deklaracji encji nie parsowanej.

Klasa DOMParser oparta jest na klasie XMLParser, a klasa XMLParser zawiera większość funkcji najczęściej używanych przy programowaniu XML for Java. Konstruktorem XMLParser jest protected XMLParser(). Metody klasy XML Parser zestawiono w tabeli 8.2.

Tabela 8.2.
Metody XMLParser

Metoda

Opis

void addRecognizer(org.apache.xerces.readers.XMLDeclRecognizer recognizer)

Dodaje moduł rozpoznający.

abstract void attlistDecl(int elementType, int attrName, int attType, java.lang.String enumString, int attDefaultType, int attDefaultValue)

Metoda zwrotna deklaracji listy atrybutów.

void callCharacters(int ch)

Wywołuje metodę zwrotną znaków.

void callComment(int comment)

Wywołuje metodę zwrotną komentarza.

void callEndDocument()

Wywołuje metodę zwrotną końca dokumentu.


boolean callEndElement(int readerId)



Wywołuje metodę zwrotną końca elementu.

void callProcessingInstruction(int target, int data)

Wywołuje metodę zwrotną instrukcji przetwarzania.

void callStartDocument(int version, int encoding, int standalone)

Wywołuje metodę zwrotną początku dokumentu.

void callStartElement(int elementType)

Wywołuje metodę zwrotną początku elementu.

org.apache.xerces.readers.XMLEntityHandler.EntityReader changeReaders()

Wywoływana jest przed podklasy po zakończeniu wczytywania się danych wejściowych.

abstract void characters(char[] ch, int start, int length)

Metoda zwrotna znaków.

abstract void characters(int data)

Metoda zwrotna znaków przy użyciu buforów znakowych.

abstract void comment(int comment)

Metoda zwrotna komentarza.

void commentInDTD(int comment)

Metoda zwrotna komentarza w DTD.

abstract void elementDecl(int elementType, XMLValidator.ContentSpec contentSpec)

Metoda zwrotna deklaracji elementu.

abstract void endCDATA()

Metoda zwrotna końca sekcji CDATA.

abstract void endDocument()

Metoda zwrotna końca dokumentu.

abstract void endDTD()

Metoda zwrotna końca DTD.

abstract void endElement(int elementType)

Metoda zwrotna końca elementu.

void endEntityDecl()

Metoda zwrotna końca deklaracji encji.

abstract void endEntityReference(int entityName, int entityType, int entityContext)

Metoda zwrotna końca odwołania do encji.

abstract void endNamespaceDeclScope(int prefix)

Metoda zwrotna końca zakresu deklaracji przestrzeni nazw.

java.lang.String expandSystemId(java.lang.String systemId)

Rozwija identyfikator systemowy i zwraca go jako adres URL.

abstract void externalEntityDecl(int entityName, int publicId, int systemId)

Metoda zwrotna deklaracji zewnętrznej encji ogólnej.

abstract void externalPEDecl(int entityName, int publicId, int systemId)

Metoda zwrotna deklaracji zewnętrznej encji parametrycznej.

protected boolean getAllowJavaEncodings()

Zwróci wartość true, jeśli w dokumencie XML dopuszczalne są kodowane nazwy Javy.

int getColumnNumber()

Zwraca numer kolumny bieżącej pozycji w dokumencie.

protected boolean getContinueAfterFatalError()

Jeśli zwróci wartość true, parser będzie kontynuować swoje działanie nawet po pojawieniu się błędu krytycznego.

org.apache.xerces.readers.XMLEntityHandler.EntityReader getEntityReader()

Pobiera czytnik Entity.

EntityResolver getEntityResolver()

Pobiera procedurę rozwijania bieżącej encji.

ErrorHandler getErrorHandler()

Pobiera procedurę obsługi bieżącego błędu.

boolean getFeature(java.lang.String featureId)

Pobiera stan wybranej cechy.

java.lang.String[] getFeaturesRecognized()

Pobiera listę cech rozumianych przez parser.

int getLineNumber()

Pobiera numer bieżącego wiersza dokumentu.

Locator getLocator()

Pobiera lokalizator używany przez parser.

protected boolean getNamespaces()

Jeśli parser wstępnie przetwarza przestrzenie nazw, zwraca wartość true.

java.lang.String[] getPropertiesRecognized()

Pobiera listę właściwości rozpoznawanych przez parser.

java.lang.Object getProperty(java.lang.String propertyId)

Pobiera wartość właściwości.

java.lang.String getPublicId()

Pobiera identyfikator publiczny InputSource.

protected org.apache.xerces.validators.schema.XSchemaValidator getSchemaValidator()

Pobiera bieżący walidator XML Schema.

java.lang.String getSystemId()

Pobiera identyfikator systemowy InputSource.

protected boolean getValidation()

Jeśli włączono walidację, zwraca true.

protected boolean getValidationDynamic()

Zwraca wartość true, jeśli włączono walidację tylko wtedy, gdy dokument zawiera opis swojej gramatyki.

protected boolean getValidationWarnOnDuplicateAttdef()

Zwraca true, jeśli powstał błąd polegający na redefinicji atrybutu.

protected boolean getValidationWarnOnUndeclareElemdef()

Zwraca true, jeśli parser wygeneruje błąd polegający na odwołaniu do nie zadeklarowanego elementu.

abstract void ignorableWhitespace(char[] ch, int start, int length)

Metoda zwrotna białych znaków, które można pominąć.

abstract void ignorableWhitespace(int data)

Metoda zwrotna białych znaków, które można pominąć, korzysta się z buforów.

abstract void internalEntityDecl(int entityName, int entityValue)

Metoda zwrotna deklaracji wewnętrznej encji ogólnej.

abstract void internalPEDecl(int entityName, int entityValue)

Metoda zwrotna deklaracji wewnętrznej encji parametrycznej.

abstract void internalSubset(int internalSubset)

Obsługa internalSubset z DOM Level 2.

boolean isFeatureRecognized(java.lang.String featureId)

Ma wartość true, jeśli dana cecha jest rozumiana.

boolean isPropertyRecognized(java.lang.String propertyId)

Ma wartość true, jeśli dana właściwość jest rozumiana.

abstract void notationDecl(int notationName, int publicId, int systemId)

Metoda zwrotna deklaracji notacji.

void parse(InputSource source)

Parsuje przekazany wejściowy kod źródłowy.

void parse(java.lang.String systemId)

Parsuje źródłowy kod wejściowy przekazany jako identyfikator systemowy.

boolean parseSome()

Obsługuje parsowanie sterowane aplikacją.

boolean parseSomeSetup(InputSource source)

Przygotowuje parsowanie sterowane aplikacją.

void processCharacters(char[] chars, int offset, int length)

Przetwarza dane podane jako tablica znakowa.

void prcessCharacters(int data)

Przetwarza dane znakowe.

abstract void processingInstruction(int target, int data)

Metoda zwrotna instrukcji przetwarzania.

void processWhitespace(char[] chars, int offset, int length)

Przetwarza białe znaki.

void processWhitespace(int data)

Przetwarza białe znaki stosując bufory.

void reportError(Locator locator, java.lang.String errorDomain, int majorCode, int minorCode, java.lang.Object[] args, int errorType)

Zgłasza informacje o błędzie.

void reset()

Zeruje parser, aby można go było użyć ponownie.

protected void resetOrCopy()

Zeruje lub kopiuje parser.

int scanAttributeName(org.apache.xerces.readers.XMLEntityHandler.EntityReader entityReader, int elementType)

Przeszukuje nazwę atrybutu.

int scanAttValue(int elementType, int attrName)

Przeszukuje wartość atrybutu.

void scanDoctypeDecl(boolean standalone)

Przeszukuje deklarację doctype.

int scanElementType(org.apache.xerces.readers.XMLEntityHandler.EntityReader entityReader, char fastchar)

Przeszukuje typ elementu.

boolean scanExpectedElementType(org.apache.xerces.readers.XMLEntityHandler.EntityReader entityReader, char fastchar)

Przeszukuje oczekiwany typ elementu.

protected void setAllowJavaEncodings(boolean allow)

Obsługuje nazwy kodowań Javy.

protected void setContinueAfterFatalError(boolean continueAfterFatalError)

Umożliwia parserowi kontynuację pracy po wystąpieniu błędu krytycznego.

void setEntityResolver(EntityResolver resolver)

Wskazuje moduł rozwijający encje zewnętrzne.

void setErrorHandler(ErrorHandler handler)

Ustawia procedurę obsługi błędu.

void setFeature(java.lang.String featureId, boolean state)

Ustawia stan cechy.

void setLocale(java.util.Locale locale

Określa ustawienia lokalne.

void setLocator(Locator locator)

Określa lokalizator.

protected void setNamespaces(boolean process)

Określa, czy parser ma wstępnie przetwarzać przestrzenie nazw.

void setProperty(java.lang.String propertyId, java.lang.Object value)

Ustawia wartość właściwości.

void setReaderFactory(org.apache.xerces.readers.XMLEntityReaderFactory readerFactory)

Ustawia grupę czytnika.

protected void setSendCharDataAsCharArray)

Ustawia preferowany sposób przetwarzania danych znakowych.

void setValidating(boolean flag)

Wskazuje parserowi, że ma następować walidacja.

protected void setValidation(boolean validate)

Określa, czy parser ma przeprowadzać walidację.

protected void setValidationDynamic(boolean dynamic)

Umożliwia parserowi walidowanie dokumentów zawierających gramatykę.

protected void setValidationWarnOnDuplicateAttdef(boolean warn)

Określa, czy w przypadku redefiniowania atrybutów ma wystąpić błąd.

protected void setValidationWarnOnUndeclaredElemdef(boolean warn)

Określa, czy parser ma zgłaszać błąd, kiedy w modelu zawartości elementu występują odwołania do niezdeklarowanych elementów.

abstract void startCDATA()

Metoda zwrotna początku sekcji CDATA.

abstract void startDocument(int version, in encoding, int standAlone)

Metoda zwrotna początku dokumentu.

abstract void startDTD(int rootElementType, int publicId, int systemId)

Metoda zwrotna początku DTD.

abstract void startElement(int elementType, XMLAttrList attrList, int attrListHandle)

Metoda zwrotna początku elementu.

boolean startEntityDecl(boolean isPE, int entityName)

Metoda zwrotna początku deklaracji encji.

abstract void startEntityReference(int entityName, int entityType, int entityContext)

Metoda zwrotna początku odwołania do encji.

abstract void startNamespaceDeclScope(int prefix, int uri)

Metoda zwrotna początku deklaracji zakresu przestrzeni nazw.

boolean startReadingFromDocument(InputSource source)

Zaczyna czytanie dokumentu.

boolean startReadingFromEntity(int entityName, int readerDepth, int context)

Zaczyna wczytywanie encji zewnętrznej.

void startReadingFromExternalSubset(java.lang.String publicId, java.lang.String systemId, int readerDepth)

Zaczyna wczytywanie zewnętrznego podzbioru DTD.

void stopReadingFromExternalSubset()

Przerywa wczytywanie zewnętrznego podzbioru DTD.

abstract void unparsedEntityDecl(int entityName, int publicId, int systemId, int notationName)

Metoda zwrotna deklaracji nieparsowanej encji.

boolean validEncName(java.lang.String encoding)

Zwraca true, jeśli kodowanie jest poprawne.

boolean validVersionNum(java.lang.String version)

Zwraca true, jeśli poprawna jest podana wersja.

Do parsowania dokumentu użyjemy metody parse obiektu parser. Umożliwimy użytkownikowi określenie nazwy parsowanego dokumentu jako args[0]. Pamiętaj, że nie trzeba metodzie parse przekazywać nazwy pliku lokalnego, można podać adres URL dokumentu w Internecie i metoda parse potrzebny dokument sobie odnajdzie.

Używając metody parse musisz umieszczać kod w bloku try, aby przechwytywać możliwe błędy, na przykład tak:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class FirstParser

{

public static void main(String[] args)

{

try {

DOMParser parser = new DOMParser();

parser.parse(args[0]);

.

.

.

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

Jeśli parsowanie dokumentu się powiedzie, używając metody getDocument parsera otrzymuje się obiekt Document oparty na DOM W3C odpowiadający dokumentowi parsowanemu:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class FirstParser

{

public static void main(String[] args)

{

try {

DOMParser parser = new DOMParser();

parser.parse(args[0]);

Document doc = parser.getDocument();

.

.

.

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

}

Interfejs obiektu Document należy do modelu DOM W3C, jego metody zestawiono w tabeli 8.3.

Tabela 8.3.
Metody interfejsu obiektu Document

Metoda

Opis

Attr createAttribute(java.lang.String name)

Tworzy atrybut o danej nazwie.

Attr createAttributeNS(java.lang.String namespaceURI, java.lang.String qualifiedName)

Tworzy atrybut o danej nazwie kwalifikowanej przestrzenią nazw.

CDATASection create(CDATASection(java.lang.String data)

Tworzy węzeł CDATASection.

Comment createComment(java.lang.String data)

Tworzy węzeł Comment.

DocumentFragment createDocumentFragment()

Tworzy pusty obiekt DocumentFragment.

Element createElement(java.lang.String tagName)

Tworzy element danego typu.

Element createElementNS(java.lang.String namespaceURI, java.lang.String qualifiedName)

Tworzy element danego typu z nazwą kwalifikowaną przestrzenią nazw.

EntityReference createEntityReference(java.lang.String name)

Tworzy obiekt EntityReference.

ProcessingInstruction createProcessingInstruction(java.lang.String target, java.lang.String data)

Tworzy węzeł ProcessingInstruction o podanej nazwie i ze wskazanymi danymi.

Text createTextNode(java.lang.String data)

Tworzy węzeł Text.

DocumentType getDoctype()

Pobiera deklarację typu dokumentu.

Element getDocumentElement()

Pobiera element główny dokumentu.

Element getElementById(java.lang.String elementId)

Pobiera element o danym identyfikatorze ID.

NodeList getElementsByTagName(java.lang.String tagname)

Zwraca listę NodeList wszystkich elementów o podanej nazwie.

NodeList getElementsByTagNameNS(java.lang.String namespaceURI, java.lang.String localName)

Zwraca listę NodeList wszystkich elementów o podanej nazwie należących do przestrzeni nazw określonej przez podany adres URI.

DOMImplementation getImplementation()

Pobiera obiekt DOMImplementation.

Node importNode(Node importedNode, boolean deep)

Importuje węzeł z innego dokumentu.

Interfejs obiektu Document oparty jest na interfejsie obiektu Node, który obsługuje obiekt Node W3C. Obiekty Node odpowiadają pojedynczym węzłom drzewa dokumentu (jak pamiętasz, wszystko w dokumencie, włącznie z tekstem i z komentarzami, traktowane jest jako węzły). Interfejs obiektu Node ma wiele metod, których można użyć do pracy z węzłami; można na przykład użyć metod takich jak getNodeName i getNodeValue do pobrania informacji o węźle, tego typu metod będziemy w tym rozdziale intensywnie używać. Interfejs ma także fragmenty danych nazywane polami, które zawierają wartości stałe odpowiadające różnym typom węzłów, też będziemy je w tym rozdziale omawiać. Pola interfejsu Node wyliczono poniżej, metody tego interfejsu zestawiono w tabeli 8.4. Jak wynika z tej tabeli, interfejs Node zawiera wszystkie standardowe metody DOM W3C umożliwiające nawigację po dokumencie, które już poznaliśmy w rozdziale 5, kiedy omawialiśmy użycie JavaScriptu. Przykładami tych metod są getNextSibling, getPreviousSibling, getFirstChild, getLastChild i getParent.

Tabela 8.4.
Metody interfejsu obiektu Node

Metoda

Opis

Node appendChild(Node newChild)

Dodaje węzeł newChild jako ostatni węzeł dziecko węzła bieżącego.

Node cloneNode(boolean deep)

Tworzy duplikat danego węzła.

NamedNodeMap getAttributes()

Pobiera obiekt NamedNodeMap zawierający atrybuty węzła bieżącego.

NodeList getChildNodes()

Pobiera listę NodeList zawierającą wszystkie dzieci węzła bieżącego.

Node getFirstChild()

Pobiera pierwsze dziecko danego węzła.

Node getLastChild()

Pobiera ostatnie dziecko danego węzła.

java.lang.String getLocalName()

Pobiera lokalną (bez przestrzeni nazw) nazwę węzła.

java.lang.String getNamespaceURI()

Pobiera adres URI przestrzeni nazw bieżącego węzła.

Nde getNextSibling()

Pobiera węzeł znajdujący się bezpośrednio za bieżącym.

java.lang.String getNodeName()

Pobiera nazwę bieżącego węzła.

short getNodeType()

Pobiera kod odpowiadający typowi węzła.

java.lang.String getNodeValue()

Pobiera wartość bieżącego węzła.

Document getOwnerDocument()

Pobiera obiekt Document zawierający bieżący węzeł.

Node getParentNode()

Pobiera rodzica węzła bieżącego.

java.lang.String getPrefix()

Pobiera przedrostek bieżącego węzła.

Node getPreviousSibling()

Pobiera węzeł znajdujący się bezpośrednio przed danym węzłem.

boolean hasChildNodes()

Zwraca wartość true, jeśli węzeł ma jakieś dzieci.

Node insertBefore(Node newChild, Node refChild)

Wstawia węzeł newChild przed węzłem dzieckiem refChild.

void normalize()

Normalizuje węzły tekstowe zapewniając, że żadne dwa węzły tekstowe nie będą do siebie bezpośrednio przylegały ani że nie wystąpią puste węzły tekstowe.

Node removeChild(Node oldChild)

Usuwa węzeł dziecko oldChild.

Node replaceChild(Node newChild, Node oldChild)

Zamienia węzeł dziecko oldChild na newChild.

void setNodeValue(java.lang.String nodeValue)

Ustawia wartość węzła.

void setPrefix(java.lang.String prefix)

Ustawia przedrostek.

boolean supports(java.lang.String feature, java.lang.String version)

Zwraca wartość true, jeśli w DOM jest zaimplementowana dana cecha obsługiwana przez podany węzeł.

Teraz mamy już dostęp do elementu głównego dokumentu. Naszym celem jest sprawdzenie, ile elementów KLIENT ma dokument, użyjemy zatem metody getElementsByTagName, aby uzyskać obiekt NodeList, zawierający listę wszystkich elementów KLIENT:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class FirstParser

{

public static void main(String[] args)

{

try {

DOMParser parser = new DOMParser();

parser.parse(args[0]);

Document doc = parser.getDocument();

NodeList nodelist = doc.getElementsByTagName("KLIENT");

.

.

.

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

}

Interfejs NodeList obsługuje uporządkowany zestaw węzłów. Do węzłów z takiego zestawu można sięgać przez indeks, tak też będziemy w tym rozdziale postępować. Metody interfejsu NodeList zestawiono w tabeli 8.5.

Tabela 8.5.
Metody interfejsu obiektu NodeList

Metoda

Opis

int getLength()

Pobiera liczbę węzłów na liście.

Node item(int index)

Pobiera węzeł o wskazanym indeksie.

Z tabeli 8.5 wynika, że interfejs NodeList obsługuje metodę getLength zwracającą liczbę węzłów na liście. Oznacza to, że liczbę elementów KLIENT w dokumencie możemy znaleźć następująco:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class FirstParser

{

public static void main(String[] args)

{

try {

DOMParser parser = new DOMParser();

parser.parse(args[0]);

Document doc = parser.getDocument();

NodeList nodelist = doc.getElementsByTagName("KLIENT");

System.out.println(args[0] + " ma " +

nodelist.getLength() + " element. KLIENT.");

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

}

Wyniki pokazano poniżej - plik zamówienia.xml zawiera trzy elementy KLIENT:

%java FirstParser zamówienia.xml

zamówienia.xml ma 3 element. KLIENT.

Jeśli nie chcesz ustawiać ścieżki klas, możesz użyć przełącznika -classpath - zakładamy, że pliki .jar znajdują się w katalogu bieżącym:

javac -classpath xerces.jar;xercesSamples.jar FirstParser.java

Następnie kod uruchamiamy tak:

java -classpath xerces.jar;xercesSamples.jar FirstParser zamówienia.xml

I tak oto zawarliśmy znajomość z parserami XML w Javie.

Wyświetlanie całego dokumentu

W następnym przykładzie napiszemy program, który będzie parsował i wyświetli cały dokument stosując wcięcia poszczególnych elementów, instrukcji przetwarzania i tak dalej, wyświetlone zostaną też wszystkie atrybuty i ich wartości. Jeśli na przykład przekażemy temu programowi (nazwiemy go IndentingParser.java) plik zamówienia.xml, wyświetlony zostanie cały dokument wraz z odpowiednimi wcięciami.

Zaczniemy od umożliwienia użytkownikowi określenia, jaki dokument ma być parsowany, następnie go będziemy parsować jak poprzednio. W celu uruchomienia parsowania w metodzie main użyjemy nowej metody displayDocument:

public static void main(String[] args)

{

displayDocument(args[0]);

.

.

.

}

W metodzie displayDocument będziemy parsować dokumentu, uzyskamy obiekt temu dokumentowi odpowiadający:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class IndentingParser

{

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

.

.

.

} catch (Exception e) {

e.printStackTrace(System.err);

}

.

.

.

Metoda parsująca faktycznie dokument, display, będzie metodą rekursywną, tak jak rekursji używaliśmy już pracując z JavaScriptem. Metodzie tej przekażemy parsowany dokument oraz aktualny ciąg wcięcia (który będzie na każdy kolejnym poziomie rósł o cztery spacje):

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class IndentingParser

{

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

display(document, "");

} catch (Exception e) {

e.printStackTrace(System.err);

}

.

.

.

W metodzie display sprawdzimy, czy przekazany nam węzeł naprawdę jest węzłem, a jeśli nie, zakończymy działanie metody. Następnie węzeł wyświetlimy, sposób wyświetlenia zależeć będzie od typu węzła. W celu pobrania typu węzła użyć można metody getNodeType obiektu Node - zastosujemy długą instrukcję switch, która umożliwi obsługę poszczególnych typów węzłów:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class IndentingParser

{

public static void displayDocument(String uri)

{

.

.

.

}

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

.

.

.

W celu obsługi wyników naszego programu utworzymy tablicę napisów displayString, w każdym jej elemencie umieścimy jeden wiersz wyniku. Bieżącą pozycję tej tablicy przechowywać będziemy w zmiennej całkowitoliczbowej numberDisplayLines:

public class IndentingParser

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

.

.

.

Teraz zajmiemy się obsługą poszczególnych typów węzłów w instrukcji switch.

Obsługa węzłów dokumentu

Na początku dokumentu znajduje się deklaracja XML, typ tego węzła odpowiada stałej Node.DOCUMENT_NODE zdefiniowanej w interfejsie Node (zajrzyj do tabeli 8.4). Deklaracja ta w wyniku zajmie jeden wiersz, pierwszy wiersz wyniku zacznie się bieżącym wcięciem, dalej będzie domyślna deklaracja XML.

Następnym pobranym elementem będzie element główny - do jego pobrania użyjemy metody getDocumentElement. Element główny zawiera wszystkie inne elementy, więc przekażemy ten element metodzie display, która owe elementy wewnętrzne wyświetli:

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

case Node.DOCUMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

display(((Document)node).getDocumentElement(), "");

break;

}

.

.

.

Obsługa węzłów elementów

Aby obsłużyć węzły elementów, należy wyświetlić nazwę elementu oraz jego atrybuty. Zaczynamy od sprawdzenia, czy bieżący typ węzła to ELEMENT_NODE. Jeśli tak, do napisu wynikowego wstawiamy bieżące wcięcie, dalej znak < i nazwę elementu, którą możemy określić stosując metodę getNodeName:

switch (type) {

.

.

.

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

.

.

.

Obsługa atrybutów

Teraz musimy obsłużyć ewentualne atrybuty elementu. Z uwagi na to, że węzeł bieżący jest węzłem elementu, możemy użyć metody getAttributes - uzyskamy obiekt NodeList zawierający wszystkie atrybuty w postaci obiektów Attr. Listę węzłów przekształcimy na tablicę obiektów Attr o nazwie attributes - najpierw jednak określamy liczbę atrybutów stosując metodę getLength obiektu NodeList:

switch (type) {

.

.

.

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

.

.

.

Metody interfejsu obiektu Attr zestawiono w tabeli 8.6.

Tabela 8.6.
Metody interfejsu obiektu Attr

Metoda

Opis

java.lang.String getName()

Pobiera nazwę atrybutu.

Element getOwnerElement()

Pobiera węzeł Element, do którego atrybut należy.

boolean getSpecified()

Zwraca wartość true, jeśli atrybut został w dokumencie oryginalnym jawnie określony.

java.lang.String getValue()

Zwraca wartość atrybutu w postaci napisu.

Interfejs Attr stworzony został na podstawie interfejsu Node, oprócz metod getName i getValue do pobrania nazwy i wartości atrybutu można używać też metod getNodeName oraz getNodeValue (w tym przykładzie skorzystamy z tych ostatnich dwóch metod). Stworzymy pętlę działającą na wszystkich atrybutach z tablicy, do bieżącego wiersza wyniku dodawać będziemy za każdym razem napis NazwaAtrybutu = "WartośćAtrybutu" (zwróć uwagę na cytowanie cudzysłowów jako \", aby Java nie potraktowała ich jako końca napisu).

switch (type) {

.

.

.

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

for (int loopIndex = 0; loopIndex < attributes.length; loopIndex++) {

Attr attribute = attributes[loopIndex];

displayStrings[numberDisplayLines] += " ";

displayStrings[numberDisplayLines] += attribute.getNodeName();

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] += attribute.getNodeValue();

displayStrings[numberDisplayLines] += "\"";

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

.

.

.

Oczywiście przetwarzany element może mieć elementy potomne i trzeba je także obsłużyć. Wszystkie węzły dzieci umieścimy w obiekcie NodeList przez zastosowanie metody getChildNodes. Jeśli jakieś dzieci istnieją, do wcięcia dodajemy cztery spacje i wykonujemy pętlę działającą na tych wszystkich węzłach dla każdego z nich wywołując metodę display:

switch (type) {

.

.

.

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

for (int loopIndex = 0; loopIndex < attributes.length; loopIndex++) {

Attr attribute = attributes[loopIndex];

displayStrings[numberDisplayLines] += " ";

displayStrings[numberDisplayLines] += attribute.getNodeName();

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] += attribute.getNodeValue();

displayStrings[numberDisplayLines] += "\"";

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

NodeList childNodes = node.getChildNodes();

if (childNodes != null) {

length = childNodes.getLength();

indent += " ";

for (int loopIndex = 0; loopIndex < length; loopIndex++ ) {

display(childNodes.item(loopIndex), indent);

}

}

break;

}

.

.

.

To tyle, jeśli chodzi o obsługę elementów, teraz zajmiemy się sekcjami CDATA.

Obsługa węzłów sekcji CDATA

Obsługa sekcji CDATA jest wyjątkowo prosta. Wystarczy tylko umieścić wartość sekcji CDATA między napisami <!CDATA[ a  --> ]]>[Author:T] :

case Node.CDATA_SECTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<![CDATA[";

displayStrings[numberDisplayLines] += node.getNodeValue();

displayStrings[numberDisplayLines] += "]]>";

numberDisplayLines++;

break;

}

.

.

.

Obsługa węzłów tekstowych

DOM W3C wymaga, aby treść elementów była umieszczana w węzłach tekstowych, a typem tych węzłów była wartość TEXT_NODE. W tym wypadku dodamy bieżące wcięcie i następnie zastosujemy metodę trim obiektu String Javy do odrzucenia początkowych i końcowych spacji z wartości węzła:

case Node.TEXT_NODE: {

displayStrings[numberDisplayLines] = indent;

String newText = node.getNodeValue().trim();

.

.

.

Parser XML for Java traktuje wszystkie napisy jako węzły tekstowe, dotyczy to także spacji użytych jako wcięć elementów w pliku zamówienia.xml. Odrzucimy wszystkie węzły tekstowe tym wcięciom odpowiadające; jeśli węzeł tekstowy zawiera jedynie tekst widoczny na ekranie, dodamy go do treści napisu wynikowego:

case Node.TEXT_NODE: {

displayStrings[numberDisplayLines] = indent;

String newText = node.getNodeValue().trim();

if(newText.indexOf("\n") < 0 && newText.length() > 0) {

displayStrings[numberDisplayLines] += newText;

numberDisplayLines++;

}

break;

}

.

.

.

Obsługa węzłów instrukcji przetwarzania

DOM W3C umożliwia także obsłużenie instrukcji przetwarzania. W tym wypadku typ węzła to PROCESSING_INSTRUCTION_NODE, a wartość takiego węzła to po prostu instrukcja przetwarzania. Załóżmy na przykład, że mamy do czynienia z następującą instrukcją przetwarzania:

<?xml-stylesheet type="text/css" href="style.css"?>

Wartością odpowiadającego jej węzła będzie:

xml-stylesheet type="text/css" href="style.css"

Oznacza to, że wystarczy wartość instrukcji przetwarzania otoczyć napisami <? i ?>. Odpowiedni kod wyglądać będzie tak:

case Node.PROCESSING_INSTRUCTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

String text = node.getNodeValue();

if(text != null && text.length() > 0) {

displayStrings[numberDisplayLines] += text;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

break;

}

.

.

.

I tak oto zakończyliśmy tworzenie instrukcji switch obsługującej poszczególne typy węzłów. Została jeszcze tylko jedna rzecz.

Domykanie znaczników

Wyświetlanie węzłów elementów wymaga nieco więcej zachodu niż wyświetlanie pozostałych typów węzłów. Na początku już wyświetliliśmy znak <, nazwę elementu oraz znak >, na końcu pojawić się muszą znaki </, nazwa elementu i znak >.

Z tego właśnie powodu po instrukcji switch dodamy kod, który spowoduje dodanie do elementów znaczników końcowych już po wyświetleniu ich dzieci. Zwróć uwagę, że za pomocą metody substr obiektu String usuwamy po cztery spacje z wcięcia, aby znacznik końcowy był w tej samej kolumnie, co znacznik początkowy.

if (type == Node.ELEMENT_NODE) {

displayStrings[numberDisplayLines] = indent.substring(0,

indent.length() - 4);

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += node.getNodeName();

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

indent += " ";

}

I to już wszystko. Po skompilowaniu programu IndentingParser.java możemy parsować i wyświetlić nasz plik zamówienia.xml. Wyniki przekażemy w potoku do filtru more, aby uzyskać efekt stopniowego przewijania danych na ekranie (filtr more jest dostępny w systemie DOS oraz w większości implementacji systemu UNIX, powoduje on wyświetlenie jednego ekranu danych i następnie oczekuje na wciśnięcie jakiegoś klawisza, dopiero wtedy pokazuje następny ekran danych).

%java IndentingParser zamówienia.xml | more

Wyniki działania programu pokazano na rysunku 8.1. Jak widać, program działa zgodnie z oczekiwaniami - bez zmian wyświetlone są wszystkie elementy i ich treść, dodane zostały wymagane wcięcia. Możemy sobie zatem pogratulować, teraz za pomocą pakietów XML for Java jesteśmy w stanie obsłużyć typowe dokumenty XML. Kompletny kod programu IndentingParser.java pokazano na wydruku 8.1. Programu tego można użyć jako przeglądarki trybu tekstowego - wystarczy podać nazwę i położenie dowolnego dokumentu XML (nie musi to być dokument lokalny), a zostanie on pobrany i sparsowany.

Rysunek 8.1.

Parsowanie dokumentu XML.

0x01 graphic

Wydruk 8.1.
IndentingParser.java

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class IndentingParser

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

display(document, "");

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

case Node.DOCUMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

display(((Document)node).getDocumentElement(), "");

break;

}

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

for (int loopIndex = 0; loopIndex < attributes.length; loopIndex++) {

Attr attribute = attributes[loopIndex];

displayStrings[numberDisplayLines] += " ";

displayStrings[numberDisplayLines] += attribute.getNodeName();

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] += attribute.getNodeValue();

displayStrings[numberDisplayLines] += "\"";

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

NodeList childNodes = node.getChildNodes();

if (childNodes != null) {

length = childNodes.getLength();

indent += " ";

for (int loopIndex = 0; loopIndex < length; loopIndex++ ) {

display(childNodes.item(loopIndex), indent);

}

}

break;

}

case Node.CDATA_SECTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<![CDATA[";

displayStrings[numberDisplayLines] += node.getNodeValue();

displayStrings[numberDisplayLines] += "]]>";

numberDisplayLines++;

break;

}

case Node.TEXT_NODE: {

displayStrings[numberDisplayLines] = indent;

String newText = node.getNodeValue().trim();

if(newText.indexOf("\n") < 0 && newText.length() > 0) {

displayStrings[numberDisplayLines] += newText;

numberDisplayLines++;

}

break;

}

case Node.PROCESSING_INSTRUCTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

String text = node.getNodeValue();

if(text != null && text.length() > 0) {

displayStrings[numberDisplayLines] += text;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

break;

}

}

if (type == Node.ELEMENT_NODE) {

displayStrings[numberDisplayLines] = indent.substring(0,

indent.length() - 4);

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += node.getNodeName();

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

indent += " ";

}

}

public static void main(String[] args)

{

displayDocument(args[0]);

for(int loopIndex = 0; loopIndex < numberDisplayLines; loopIndex++){

System.out.println(displayStrings[loopIndex]);

}

}

}

Filtrowanie dokumentów XML

W poprzednim przykładzie wyświetlaliśmy cały dokument, jednak w procesie filtrowania można uzyskać tylko wybraną część dokumentu. Filtrowanie pozwala wybrać z dokumentu tylko te elementy, które są w danej chwili istotne.

Oto przykład nazwany searcher.java. Umożliwiamy użytkownikowi wskazanie przeszukiwanego dokumentu i wskazanie elementów, które mają być wybrane - oto jak wybrać elementy POZYCJA z dokumentu zamówienia.xml:

%java searcher zamówienia.xml POZYCJA

Zaczniemy tworzenie programu od utworzenia nowej klasy FindElements, co nam ułatwi dalsze programowanie. Konstruktorowi nowej klasy wystarczy przekazać nazwę przeszukiwanego dokumentu oraz nazwę elementu, który ma zostać odnaleziony:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class searcher

{

public static void main(String args[])

{

FindElements findElements = new FindElements(args[0], args[1]);

}

}

W konstruktorze klasy FindElements zapisujemy nazwę wyszukiwanego elementu w zmiennej tekstowej searchFor, następnie wywołujemy metodę displayDocument, która będzie wyświetlała dokument tak, jak poprzednio. Metoda ta wypełni tablicę displayStrings gotowymi do wyświetlenia tekstami:

class FindElements

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static String searchFor;

public FindElements (String uri, String searchString)

{

searchFor = searchString;

displayDocument(uri);

for(int loopIndex = 0; loopIndex < numberDisplayLines; loopIndex++){

System.out.println(displayStrings[loopIndex]);

}

}

W metodzie displayDocument chcemy wyświetlić jedynie te elementy, których nazwa pasuje do napisu ze zmiennej searchFor. Do znalezienia takich elementów używamy metody getElementsByTagName, która zwraca listę węzłów pasujących elementów. Następnie tworzymy pętlę po wszystkich węzłach z danej listy, wywołujemy metodę display wyświetlającą kolejne elementy i ich dzieci:

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

NodeList nodeList = document.getElementsByTagName(searchFor);

if (nodeList != null) {

for (int loopIndex = 0; loopIndex < nodeList.getLength();

loopIndex++) {

display(nodeList.item(loopIndex), "");

}

}

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

Metoda display pozostanie taka sama, jak w poprzednim przykładzie.

I tak oto mamy już wszystko gotowe. Poniższy przykład pozwoli wybrać wszystkie elementy POZYCJA z pliku zamówienia.xml:

% java searcher zamówienia.xml POZYCJA | more

Wyniki pokazano na rysunku 8.2, zaś cały kod programu searcher.java na wydruku 8.2.

Rysunek 8.2.

Filtrowanie dokumentu XML

0x01 graphic

Wydruk 8.2.
searcher.java

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class searcher

{

public static void main(String args[])

{

FindElements findElements = new FindElements(args[0], args[1]);

}

}

class FindElements

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static String searchFor;

public FindElements (String uri, String searchString)

{

searchFor = searchString;

displayDocument(uri);

for(int loopIndex = 0; loopIndex < numberDisplayLines; loopIndex++){

System.out.println(displayStrings[loopIndex]);

}

}

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

NodeList nodeList = document.getElementsByTagName(searchFor);

if (nodeList != null) {

for (int loopIndex = 0; loopIndex < nodeList.getLength();

loopIndex++) {

display(nodeList.item(loopIndex), "");

}

}

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

case Node.DOCUMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

display(((Document)node).getDocumentElement(), "");

break;

}

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

for (int loopIndex = 0; loopIndex < attributes.length; loopIndex++) {

Attr attribute = attributes[loopIndex];

displayStrings[numberDisplayLines] += " ";

displayStrings[numberDisplayLines] += attribute.getNodeName();

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] += attribute.getNodeValue();

displayStrings[numberDisplayLines] += "\"";

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

NodeList childNodes = node.getChildNodes();

if (childNodes != null) {

length = childNodes.getLength();

indent += " ";

for (int loopIndex = 0; loopIndex < length; loopIndex++ ) {

display(childNodes.item(loopIndex), indent);

}

}

break;

}

case Node.CDATA_SECTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<![CDATA[";

displayStrings[numberDisplayLines] += node.getNodeValue();

displayStrings[numberDisplayLines] += "]]>";

numberDisplayLines++;

break;

}

case Node.TEXT_NODE: {

displayStrings[numberDisplayLines] = indent;

String newText = node.getNodeValue().trim();

if(newText.indexOf("\n") < 0 && newText.length() > 0) {

displayStrings[numberDisplayLines] += newText;

numberDisplayLines++;

}

break;

}

case Node.PROCESSING_INSTRUCTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

String text = node.getNodeValue();

if(text != null && text.length() > 0) {

displayStrings[numberDisplayLines] += text;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

break;

}

}

if (type == Node.ELEMENT_NODE) {

displayStrings[numberDisplayLines] = indent.substring(0,

indent.length() - 4);

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += node.getNodeName();

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

indent += " ";

}

}

}

Tworzone dotąd przykłady dawały wyniki w postaci tekstu wyprowadzanego metodą System.out.println. Jednak dzisiaj już niewiele przeglądarek działa w ten sposób - teraz zajmiemy się tworzeniem przeglądarek posiadających własne okienka.

Tworzenie przeglądarki okienkowej

Przekształcenie zapisanego tutaj kodu tak, aby mógł wyświetlać dokument w osobnym okienku, nie jest trudne, gdyż kod ten został specjalnie tak zapisany, aby cały wynik był zapisany w tablicy tekstów, które można następnie wyświetlić w osobnym oknie.

Zaczynamy od parsowania dokumentu w metodzie main:

public static void main(String[] args) {

displayDocument(args[0]);

.

.

.

Następnie stosując techniki znane z poprzedniego rozdziału tworzymy okienko. Utworzymy nową klasę AppFrame, następnie utworzymy obiekt tej klasy i go wyświetlimy:

public static void main(String[] args) {

displayDocument(args[0]);

AppFrame f = new AppFrame(displayStrings, numberDisplayLines);

f.setSize(300, 500);

f.addWindowListener(new WindowAdapter() {public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

Klasa AppFrame powstała po to, aby umożliwić wyświetlanie napisów wynikowych z tablicy displayStrings w okienku Javy. Tablicę i liczbę wypełnionych komórek przekazujemy konstruktorowi AppFrame, wszystko zamykamy w definicji nowej klasy:

class AppFrame extends Frame

{

String displayStrings[];

int numberDisplayLines;

public AppFrame(String[] strings, int number)

{

displayStrings = strings;

numberDisplayLines = number;

}

.

.

.

Teraz zostało nam już tylko wyświetlić napisy z tablicy displayStrings. Kiedy wyświetlamy tekst w oknie Javy, odpowiedzialni jesteśmy za jego pozycjonowanie. Jeśli wyświetlamy wiele wierszy tekstu, musimy znać wysokość wiersza tekstu w oknie - można ją określić stosując metodę getHeight klasy Javy FontMetrics.

Przejdźmy teraz do wyświetlania tekstu wynikowego w oknie AppFrame. Tworzymy nowy obiekt klasy Font stosując czcionkę Courier, wiążemy go z obiektem Graphics przekazywanym metodzie paint. Następnie określamy wysokość poszczególnych wierszy:

public void paint(Graphics g)

{

Font font = new Font("Courier", Font.PLAIN, 12);

g.setFont(font);

FontMetrics fontmetrics = getFontMetrics(getFont());

int y = fontmetrics.getHeight();

for(int index = 0; index < numberDisplayLines; index++) {

y += fontmetrics.getHeight();

g.drawString(displayStrings[index], 5, y);

}

}

Wyniki pokazano na rysunku 8.3. Jak widać, plik zamówienia.xml wyświetlony został w oknie naszej nowej przeglądarki. Kod tego przykładu zawarty w pliku browser.java pokazano na wydruku 8.3.

Rysunek 8.3.

Przeglądarka graficzna

0x01 graphic

Wydruk 11.3.
browser.java

import java.awt.*;

import java.awt.event.*;

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class browser

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

display(document, "");

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

case Node.DOCUMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

display(((Document)node).getDocumentElement(), "");

break;

}

case Node.ELEMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

for (int loopIndex = 0; loopIndex < attributes.length; loopIndex++) {

Attr attribute = attributes[loopIndex];

displayStrings[numberDisplayLines] += " ";

displayStrings[numberDisplayLines] += attribute.getNodeName();

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] += attribute.getNodeValue();

displayStrings[numberDisplayLines] += "\"";

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

NodeList childNodes = node.getChildNodes();

if (childNodes != null) {

length = childNodes.getLength();

indent += " ";

for (int loopIndex = 0; loopIndex < length; loopIndex++ ) {

display(childNodes.item(loopIndex), indent);

}

}

break;

}

case Node.CDATA_SECTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<![CDATA[";

displayStrings[numberDisplayLines] += node.getNodeValue();

displayStrings[numberDisplayLines] += "]]>";

numberDisplayLines++;

break;

}

case Node.TEXT_NODE: {

displayStrings[numberDisplayLines] = indent;

String newText = node.getNodeValue().trim();

if(newText.indexOf("\n") < 0 && newText.length() > 0) {

displayStrings[numberDisplayLines] += newText;

numberDisplayLines++;

}

break;

}

case Node.PROCESSING_INSTRUCTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

String text = node.getNodeValue();

if(text != null && text.length() > 0) {

displayStrings[numberDisplayLines] += text;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

break;

}

}

if (type == Node.ELEMENT_NODE) {

displayStrings[numberDisplayLines] = indent.substring(0,

indent.length() - 4);

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += node.getNodeName();

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

indent += " ";

}

}

public static void main(String[] args) {

displayDocument(args[0]);

AppFrame f = new AppFrame(displayStrings, numberDisplayLines);

f.setSize(300, 500);

f.addWindowListener(new WindowAdapter() {public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

}

class AppFrame extends Frame

{

String displayStrings[];

int numberDisplayLines;

public AppFrame(String[] strings, int number)

{

displayStrings = strings;

numberDisplayLines = number;

}

public void paint(Graphics g)

{

Font font = new Font("Courier", Font.PLAIN, 12);

g.setFont(font);

FontMetrics fontmetrics = getFontMetrics(getFont());

int y = fontmetrics.getHeight();

for(int index = 0; index < numberDisplayLines; index++) {

y += fontmetrics.getHeight();

g.drawString(displayStrings[index], 5, y);

}

}

}

Teraz, kiedy potrafimy już dokumenty XML parsować i wyświetlać w odrębnym oknie, równie dobrze możemy zacząć tworzyć zwykłą grafikę. W następnym przykładzie utworzymy przeglądarkę graficzną, która nie będzie w ogóle pokazywała danych tekstowych, ale na podstawie dokumentu XML utworzy rysunek.

Tworzenie przeglądarki graficznej

W Javie tekst to po prostu specyficzny rodzaj grafiki, więc de facto z grafiką już mieliśmy do czynienia. W następnym przykładzie utworzymy przeglądarkę nie korzystającą z tekstu, ale wyświetlającą rysunek składający się z kółek na podstawie zawartości dokumentu XML. Poniżej przedstawiono używany dokument, kółka.xml - podajemy współrzędne xy środków okręgów oraz ich promień jako atrybuty elementu OKRĄG:

<?xml version = "1.0" encoding="iso-8859-2"?>

<!DOCTYPE DOKUMENT [

<!ELEMENT DOKUMENT (OKRĄG|ELIPSA)*>

<!ELEMENT OKRĄG EMPTY>

<!ELEMENT ELIPSA EMPTY>

<!ATTLIST OKRĄG

X CDATA #IMPLIED

Y CDATA #IMPLIED

PROMIEŃ CDATA #IMPLIED>

<!ATTLIST ELIPSA

X CDATA #IMPLIED

Y CDATA #IMPLIED

SZEROKOŚĆ CDATA #IMPLIED

WYSOKOŚĆ CDATA #IMPLIED>

]>

<DOKUMENT>

<OKRĄG X='200' Y='160' PROMIEŃ='50' />

<OKRĄG X='170' Y='100' PROMIEŃ='15' />

<OKRĄG X='80' Y='200' PROMIEŃ='45' />

<OKRĄG X='200' Y='140' PROMIEŃ='35' />

<OKRĄG X='130' Y='240' PROMIEŃ='25' />

<OKRĄG X='270' Y='300' PROMIEŃ='45' />

<OKRĄG X='210' Y='240' PROMIEŃ='25' />

<OKRĄG X='60' Y='160' PROMIEŃ='35' />

<OKRĄG X='160' Y='260' PROMIEŃ='55' />

</DOKUMENT>

Nasz nowy przykład nazwiemy circles.java. Konieczne będzie zdekodowanie dokumentu XML i zapisanie danych o poszczególnych okręgach. Do zapisania tych danych użyjemy tablic x do współrzędnych poziomych środków okręgów, y do współrzędnych pionowych środków okręgów i radius do zapisu promieni okręgów. Liczbę pozycji zapisanych w tych tablicach przechowywać będziemy w zmiennej numberFigures:

public class circles

{

static int numberFigures = 0;

static int x[] = new int[100];

static int y[] = new int[100];

static int radius[] = new int[100];

.

.

.

W trakcie parsowania dokumentu będziemy wybierać wszystkie elementy OKRĄG. Po znalezieniu takiego elementu zapiszemy w odpowiednich tablicach współrzędne środka oraz promień. Aby sprawdzić, czy bieżący węzeł odpowiada elementowi OKRĄG, porównywać będziemy nazwę węzła uzyskaną metodą getNodeName z napisem „OKRĄG” za pomocą metody Javy equals - metoda ta musi być używa do obiektów klasy String zamiast operatora ==:

if (node.getNodeType() == Node.ELEMENT_NODE) {

if (node.getNodeName().equals("OKRĄG")) {

NamedNodeMap attrs = node.getAttributes();

x[numberFigures] =

Integer.parseInt((String)attrs.getNamedItem("X").getNodeValue());

y[numberFigures] =

Integer.parseInt((String)attrs.getNamedItem("Y").getNodeValue());

radius[numberFigures] =

Integer.parseInt((String)attrs.getNamedItem("PROMIEŃ").getNodeValue());

numberFigures++;

}

.

.

.

Metody interfejsu NamedNodeMap zestawiono w tabeli 8.7.

Tabela 8.7.
Metody interfejsu NamedNodeMap

Metoda

Opis

int getLength()

Zwraca liczbę węzłów należących do mapy.

Node getNamedItem(java.lang.String name)

Pobiera węzeł o wskazanej nazwie.

Node getNamedItemNS(java.lang.String namespaceURI, java.lang.String localName)

Pobiera węzeł określony przez nazwę lokalną i URI przestrzeni nazw.

Node item(int index)

Pobiera pozycję z mapy według indeksu.

Node removeNamedItem(java.lang.String name)

Usuwa węzeł o podanej nazwie.

Node removeNamedItemNS(java.lang.String namespaceURI, java.lang.String localName)

Usuwa węzeł o podanej nazwie lokalnej i URI przestrzeni nazw.

Node setNamedItem(Node arg)

Dodaje węzeł określony atrybutem nodeName.

Node setNamedItemNS(Node arg)

Dodaje węzeł określony nazwą lokalną oraz adresem URI.

Po zakończeniu parsowania dokumentu w tablicach x, y i radius mamy współrzędne środków i promienie okręgów. Pozostaje jedynie wyświetlić same okręgi, do czego użyjemy metody drawOval obiektu Graphics. Metoda ta wykreśla elipsy o zadanym środku i osiach. Jeśli chcemy wykreślić okręgi, jako długość obu osi podawać musimy promień okręgu. Całość jest podobna do klasy AppFrame, gdzie tworzyliśmy własne okno przeglądarki:

class AppFrame extends Frame

{

int numberFigures;

int[] xValues;

int[] yValues;

int[] radiusValues;

public AppFrame(int number, int[] x, int[] y, int[] radius)

{

numberFigures = number;

vValues = x;

yValues = y;

radiusValues = radius;

}

public void paint(Graphics g)

{

for(int loopIndex = 0; loopIndex < numberFigures; loopIndex++){

g.drawOval(xValues[loopIndex], yValues[loopIndex],

radiusValues[loopIndex], radiusValues[loopIndex]);

}

}

}

To już właściwie wszystko; wyniki pokazano na rysunku 8.4 - wyświetlony został plik kółka.xml. Cały kod programu pokazano na wydruku 8.4.

Rysunek 8.4.

Graficzna przeglądarka XML

0x01 graphic

Wydruk 8.4.
circles.java

import java.awt.*;

import java.awt.event.*;

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class circles

{

static int numberFigures = 0;

static int x[] = new int[100];

static int y[] = new int[100];

static int radius[] = new int[100];

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

display(document);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node)

{

if(node == null) {

return;

}

int type = node.getNodeType();

if (node.getNodeType() == Node.DOCUMENT_NODE) {

display(((Document)node).getDocumentElement());

}

if (node.getNodeType() == Node.ELEMENT_NODE) {

if (node.getNodeName().equals("OKRAG")) {

NamedNodeMap attrs = node.getAttributes();

x[numberFigures] =

Integer.parseInt((String)attrs.getNamedItem("X").getNodeValue());

y[numberFigures] =

Integer.parseInt((String)attrs.getNamedItem("Y").getNodeValue());

radius[numberFigures] =

Integer.parseInt((String)attrs.getNamedItem("PROMIEN").getNodeValue());

numberFigures++;

}

NodeList childNodes = node.getChildNodes();

if (childNodes != null) {

int length = childNodes.getLength();

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

display(childNodes.item(loopIndex));

}

}

}

}

public static void main(String[] args)

{

displayDocument(args[0]);

AppFrame f = new AppFrame(numberFigures, x, y, radius);

f.setSize(400, 400);

f.addWindowListener(new WindowAdapter() {public void

windowClosing(WindowEvent e) {System.exit(0);}});

f.show();

}

}

class AppFrame extends Frame

{

int numberFigures;

int[] xValues;

int[] yValues;

int[] radiusValues;

public AppFrame(int number, int[] x, int[] y, int[] radius)

{

numberFigures = number;

xValues = x;

yValues = y;

radiusValues = radius;

}

public void paint(Graphics g)

{

for(int loopIndex = 0; loopIndex < numberFigures; loopIndex++){

g.drawOval(xValues[loopIndex], yValues[loopIndex],

radiusValues[loopIndex], radiusValues[loopIndex]);

}

}

}

Nawigacja po dokumentach XML

Jak to wynika z tabeli 8.4, interfejs Node zawiera standardowe metody DOM W3C pozwalające poruszać się po dokumencie XML - używaliśmy ich już w rozdziale 5: getNextSibling, getPreviousSibling, getFirstChild, getLastChild i getParent. Zastosowanie tych metod w Javie jest równie proste, jak ich użycie pokazane w rozdziale 5. Poniżej przypominamy dokument spotkania.xml, którego używaliśmy już poprzednio:

<?xml version="1.0" encoding="iso-8859-2"?>

<SPOTKANIA>

<SPOTKANIE TYP="nieformalne">

<PRZEWODNICZĄCY>Ted Bond</PRZEWODNICZĄCY>

<TYTUŁ>XML w praktycznych zastosowaniach</TYTUŁ>

<NUMER>2079</NUMER>

<TEMAT>XML</TEMAT>

<DATA>6/1/2002</DATA>

<OSOBY>

<OSOBA STATUS="obecny">

<IMIĘ>Edward</IMIĘ>

<NAZWISKO>Samson</NAZWISKO>

</OSOBA>

<OSOBA STATUS="nieobecny">

<IMIĘ>Ernestyna</IMIĘ>

<NAZWISKO>Johnson</NAZWISKO>

</OSOBA>

<OSOBA STATUS="obecny">

<IMIĘ>Betty</IMIĘ>

<NAZWISKO>Richardson</NAZWISKO>

</OSOBA>

</OSOBY>

</SPOTKANIE>

</SPOTKANIA>

W piątym rozdziale poruszając się po tym dokumencie odnajdowaliśmy nazwisko trzeciej osoby, teraz zrobimy to samo. Podstawowa różnica między implementacjami XML for Java i JavaScriptem polega na tym, że w XML for Java cały tekst traktowany jest jako zbiór węzłów tekstowych, dotyczy to także spacji użytych do stworzenia wcięć. Oznacza to, że do poruszania się po dokumencie możemy użyć w zasadzie tego samego kodu, co w rozdziale 5, tyle tylko że konieczne będzie pomijanie węzłów tekstowych zawierających jedynie spacje wcięć. Oto jak wyglądać będzie program nav.java:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class nav

{

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

display(document);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node)

{

Node textNode;

Node meetingsNode = ((Document)node).getDocumentElement();

textNode = meetingsNode.getFirstChild();

Node meetingNode = textNode.getNextSibling();

textNode = meetingNode.getLastChild();

Node peopleNode = textNode.getPreviousSibling();

textNode = peopleNode.getLastChild();

Node personNode = textNode.getPreviousSibling();

Node first_nameNode = textNode.getNextSibling();

textNode = first_nameNode.getNextSibling();

Node last_nameNode = textNode.getNextSibling();

System.out.println("Trzecie nazwisko: " +

first_nameNode.getFirstChild().getNodeValue() + ' '

+ last_nameNode.getFirstChild().getNodeValue());

}

public static void main(String args[])

{

displayDocument("spotkania.xml");

}

}

Oto wyniki działania tego programu:

%java nav

Trzecie nazwisko: Betty Richardson

Pomijanie białych znaków

Wszystkie spacje związane z wcięciami, nazywane „pomijalnymi” białymi znakami, można odrzucić. W takim wypadku trzeba jakoś wskazać parserowi XML for Java gramatykę dokumentu XML, aby wiedział, które białe znaki może pominąć - najprościej jest użyć do tego DTD:

<?xml version="1.0" encoding="iso-8859-2"?>

<!DOCTYPE SPOTKANIA [

<!ELEMENT SPOTKANIA (SPOTKANIE*)>

<!ELEMENT SPOTKANIE (TYTUŁ,NUMER,TEMAT,DATA,OSOBY*)>

<!ELEMENT TYTUŁ (#PCDATA)>

<!ELEMENT NUMER (#PCDATA)>

<!ELEMENT TEMAT (#PCDATA)>

<!ELEMENT DATA (#PCDATA)>

<!ELEMENT IMIĘ (#PCDATA)>

<!ELEMENT NAZWISKO (#PCDATA)>

<!ELEMENT OSOBY (OSOBA*)>

<!ELEMENT OSOBA (IMIĘ,NAZWISKO)>

<!ATTLIST SPOTKANIE

TYP CDATA #IMPLIED>

<!ATTLIST OSOBA

STATUS CDATA #IMPLIED>

]>

<SPOTKANIA>

<SPOTKANIE TYP="nieformalne">

<PRZEWODNICZĄCY>Ted Bond</PRZEWODNICZĄCY>

<TYTUŁ>XML w praktycznych zastosowaniach</TYTUŁ>

<NUMER>2079</NUMER>

<TEMAT>XML</TEMAT>

<DATA>6/1/2002</DATA>

<OSOBY>

<OSOBA STATUS="obecny">

<IMIĘ>Edward</IMIĘ>

<NAZWISKO>Samson</NAZWISKO>

</OSOBA>

<OSOBA STATUS="nieobecny">

<IMIĘ>Ernestyna</IMIĘ>

<NAZWISKO>Johnson</NAZWISKO>

</OSOBA>

<OSOBA STATUS="obecny">

<IMIĘ>Betty</IMIĘ>

<NAZWISKO>Richardson</NAZWISKO>

</OSOBA>

</OSOBY>

</SPOTKANIE>

</SPOTKANIA>

Teraz wywołujemy metodę parsera setIncludeIgnorableWhitespace z wartością false, co spowoduje wyłączenie pomijalnych białych znaków i nie musimy się już przejmować węzłami tekstowymi ze spacjami wykorzystanymi do zrobienia w dokumencie wcięć, dzięki czemu nasz kod może być krótszy:

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

public class nav

{

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.setIncludeIgnorableWhitespace(false);

parser.parse(uri);

Document document = parser.getDocument();

display(document);

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node)

{

Node meetingsNode = ((Document)node).getDocumentElement();

Node meetingNode = textNode.getNextSibling();

Node peopleNode = textNode.getPreviousSibling();

Node personNode = textNode.getPreviousSibling();

Node first_nameNode = textNode.getNextSibling();

Node last_nameNode = textNode.getNextSibling();

System.out.println("Trzecie nazwisko: " +

first_nameNode.getFirstChild().getNodeValue() + ' '

+ last_nameNode.getFirstChild().getNodeValue());

}

public static void main(String args[])

{

displayDocument("spotkania.xml");

}

}

Modyfikowanie dokumentów XML

Z tabeli 8.4 wynika, że interfejs Node zawiera wiele metod pozwalających modyfikować dokumenty przez dodawanie bądź usuwanie węzłów. Do metod tych należą appendChild, insertBefore, removeChild, replaceChild i inne, wszystkie one pozwalają „w locie” modyfikować dokumenty XML.

Jeśli jednak dokument modyfikujesz, konieczne będzie późniejsze jego zapisanie (nie mogliśmy zrobić tego w rozdziale 5 za pomocą JavaScriptu, zatem cały dokument wysyłaliśmy do skryptu ASP, który zwracał do przeglądarki nową postać dokumentu, który miał być wyświetlony). Pakiety XML for Java obsługują interfejs Serializer, który umożliwia zapisywanie dokumentów. Interfejs ten jednak nie jest dołączany do standardowych archiwów JAR, którymi się do tej pory posługiwaliśmy. Tak naprawdę łatwo można dokumenty normalnie zapisywać podczas ich drukowania. Zamiast używać metody System.out.println wypisującej dokument na konsoli, wystarczy zastosować obiekt FileWriter, który pozwala dokument zapisać na dysku.

W tym przykładzie zakładamy, że wszystkie osoby z pliku zamówienia.xml (dokument ten znajdziesz na początku tego rozdziału) są doświadczonymi programistami. Wszystkim dodamy drugie imię XML w postaci elementu DRUGIE_IMIĘ. Nowy element będzie dzieckiem elementu IMIĘNAZWISKO, tak jak dotychczas stosowane IMIĘ i NAZWISKO:

<IMIĘNAZWISKO>

<NAZWISKO>

Jones

</NAZWISKO>

<IMIĘ>

Polly

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

Dodanie elementu DRUGIE_IMIĘ do poszczególnych elementów IMIĘNAZWISKO jest proste - wystarczy w czasie parsowania elementu IMIĘNAZWISKO wywołać metodę createElement:

case Node.ELEMENT_NODE: {

if(node.getNodeName().equals("IMIĘNAZWISKO")) {

Element middleNameElement = document.createElement("DRUGIE_IMIĘ");

.

.

.

Cały tekst dokumentu przechowywany jest w węzłach tekstowych, stosując metodę createTextNode tworzymy zatem nowy węzeł tekstowy, który będzie zawierał napis XML:

case Node.ELEMENT_NODE: {

if(node.getNodeName().equals("IMIĘNAZWISKO")) {

Element middleNameElement = document.createElement("DRUGIE_IMIĘ");

Text textNode = document.createTextNode("XML");

.

.

.

Następnie metodą appendChild możemy węzeł tekstowy dołączyć do nowego elementu:

case Node.ELEMENT_NODE: {

if(node.getNodeName().equals("IMIĘNAZWISKO")) {

Element middleNameElement = document.createElement("DRUGIE_IMIĘ");

Text textNode = document.createTextNode("XML");

middleNameElement.appendChild(textNode);

.

.

.

Powyższy kod umożliwia nam modyfikację dokumentu wczytanego do pamięci komputera. Poszczególne wiersze dokumentu jak poprzednio znajdują się w tablicy displayStrings, tablicę tę możemy zapisać w pliku zamówienia2.xml. Użyjemy do tego klasy Javy FileWriter, która wypisuje tekst zawarty w tablicach znakowych do plików. Aby takie tablice znakowe utworzyć, użyjemy metody toCharArray obiektu String:

public static void main(String[] args)

{

displayDocument(args[0]);

try {

FileWriter filewriter = new FileWriter("zamówienia2.xml");

for(int loopIndex = 0; loopIndex < numberDisplayLines; loopIndex++){

filewriter.write(displayStrings[loopIndex].toCharArray());

filewriter.write('\n');

}

filewriter.close();

}

catch (Exception e) {

}

}

I to już wszystko. Kiedy nasz program uruchomimy, uzyskamy plik zamówienia2.xml z dodanymi elementami DRUGIE_IMIĘ:

<?xml version="1.0" encoding="iso-8859-2"?>

<DOKUMENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>

Smith

</NAZWISKO>

<IMIĘ>

Sam

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

<DATA>

15 października 2001

</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>

Pomidory

</PRODUKT>

<ILOŚĆ>

8

</ILOŚĆ>

<CENA>

5zł

</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>

Pomarańcze

</PRODUKT>

<ILOŚĆ>

24

</ILOŚĆ>

<CENA>

9.98zł

</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Kredytobiorca niesolidny">

<IMIĘNAZWISKO>

<NAZWISKO>

Jones

</NAZWISKO>

<IMIĘ>

Polly

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

<DATA>

20 października 2001

</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>

Chleb

</PRODUKT>

<ILOŚĆ>

12

</ILOŚĆ>

<CENA>

28.80zł

</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>

Jabłka

</PRODUKT>

<ILOŚĆ>

6

</ILOŚĆ>

<CENA>

6.00zł

</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

<KLIENT STATUS="Rzetelny kredytobiorca">

<IMIĘNAZWISKO>

<NAZWISKO>

Weber

</NAZWISKO>

<IMIĘ>

Bill

</IMIĘ>

<DRUGIE_IMIĘ>

XML

</DRUGIE_IMIĘ>

</IMIĘNAZWISKO>

<DATA>

25 października 2001

</DATA>

<ZAMÓWIENIA>

<POZYCJA>

<PRODUKT>

Asparagus

</PRODUKT>

<ILOŚĆ>

12

</ILOŚĆ>

<CENA>

11.90zł

</CENA>

</POZYCJA>

<POZYCJA>

<PRODUKT>

Sałata

</PRODUKT>

<ILOŚĆ>

6

</ILOŚĆ>

<CENA>

31.50zł

</CENA>

</POZYCJA>

</ZAMÓWIENIA>

</KLIENT>

</DOKUMENT>

Gotowy kod programu - nazwiemy go XMLWriter.java - pokazano na wydruku 8.5.

Wydruk 11.5.
XMLWriter.java

import java.awt.*;

import java.io.*;

import java.awt.event.*;

import org.w3c.dom.*;

import org.apache.xerces.parsers.DOMParser;

import org.apache.xerces.*;

public class XMLWriter

{

static String displayStrings[] = new String[1000];

static int numberDisplayLines = 0;

static Document document;

static Node c;

public static void displayDocument(String uri)

{

try {

DOMParser parser = new DOMParser();

parser.parse(uri);

Document document = parser.getDocument();

display(document, "");

} catch (Exception e) {

e.printStackTrace(System.err);

}

}

public static void display(Node node, String indent)

{

if(node == null) {

return;

}

int type = node.getNodeType();

switch (type) {

case Node.DOCUMENT_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] +=

"<?xml version=\"1.0\" encoding=\"iso-8859-2\"?>";

numberDisplayLines++;

display(((Document)node).getDocumentElement(), "");

break;

}

case Node.ELEMENT_NODE: {

if(node.getNodeName().equals("IMIĘNAZWISKO")) {

Element middleNameElement = document.createElement("DRUGIE_IMIĘ");

Text textNode = document.createTextNode("XML");

middleNameElement.appendChild(textNode);

node.appendChild(middleNameElement);

}

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<";

displayStrings[numberDisplayLines] += node.getNodeName();

int length = (node.getAttributes() != null) ?

node.getAttributes().getLength() : 0;

Attr attributes[] = new Attr[length];

for (int loopIndex = 0; loopIndex < length; loopIndex++) {

attributes[loopIndex] =

(Attr)node.getAttributes().item(loopIndex);

}

for (int loopIndex = 0; loopIndex < attributes.length; loopIndex++) {

Attr attribute = attributes[loopIndex];

displayStrings[numberDisplayLines] += " ";

displayStrings[numberDisplayLines] += attribute.getNodeName();

displayStrings[numberDisplayLines] += "=\"";

displayStrings[numberDisplayLines] += attribute.getNodeValue();

displayStrings[numberDisplayLines] += "\"";

}

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

NodeList childNodes = node.getChildNodes();

if (childNodes != null) {

length = childNodes.getLength();

indent += " ";

for (int loopIndex = 0; loopIndex < length; loopIndex++ ) {

display(childNodes.item(loopIndex), indent);

}

}

break;

}

case Node.CDATA_SECTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<![CDATA[";

displayStrings[numberDisplayLines] += node.getNodeValue();

displayStrings[numberDisplayLines] += "]]>";

numberDisplayLines++;

break;

}

case Node.TEXT_NODE: {

displayStrings[numberDisplayLines] = indent;

String newText = node.getNodeValue().trim();

if(newText.indexOf("\n") < 0 && newText.length() > 0) {

displayStrings[numberDisplayLines] += newText;

numberDisplayLines++;

}

break;

}

case Node.PROCESSING_INSTRUCTION_NODE: {

displayStrings[numberDisplayLines] = indent;

displayStrings[numberDisplayLines] += "<?";

String text = node.getNodeValue();

if(text != null && text.length() > 0) {

displayStrings[numberDisplayLines] += text;

}

displayStrings[numberDisplayLines] += "?>";

numberDisplayLines++;

break;

}

}

if (type == Node.ELEMENT_NODE) {

displayStrings[numberDisplayLines] = indent.substring(0,

indent.length() - 4);

displayStrings[numberDisplayLines] += "</";

displayStrings[numberDisplayLines] += node.getNodeName();

displayStrings[numberDisplayLines] += ">";

numberDisplayLines++;

indent += " ";

}

}

public static void main(String[] args)

{

displayDocument(args[0]);

try {

FileWriter filewriter = new FileWriter("zamówienia2.xml");

for(int loopIndex = 0; loopIndex < numberDisplayLines; loopIndex++){

filewriter.write(displayStrings[loopIndex].toCharArray());

filewriter.write('\n');

}

filewriter.close();

}

catch (Exception e) {

}

}

}

Powyższe przykłady powinny jasno wykazać, jak duże możliwości ma XML for Java. Jednak XML w Javie można używać inaczej niż tylko stosując model DOM - można też użyć interfejsu SAX, który omawiać będziemy w następnym rozdziale.

--> W chwili przygotowywania polskiego wydania książki dostępna była już wersja 3.1.0 oparta na parserze XML Apache Xerces w wersji 1.2.0. W dalszych przykładach używać będziemy jednak wersji 3.0.1, gdyż nowa wersja jest określana przez autorów jako „eksperymentalna”. (przyp. tłum.)

W wyniku zadeklarowane będzie zawsze kodowanie ISO 8859-2, niezależnie od tego, jakie było w oryginale. Brak polskich liter związany jest z ułomnościami okienka MS-DOS, a nie wynika z działanie samej Javy - problem zniknie, kiedy otworzymy własne okienko dokumentu. (przyp. tłum.)

Zgodnie z zapowiedzią w oddzielnym oknie prawidłowo są już wyświetlane polskie litery. (przyp. tłum.)

4 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

Rozdział 1 Pierwsze kroki (Nagłówek strony)

4 C:\Moje dokumenty\Wojtek Romowicz\Książki\XML Vademecum profesjonalisty\r08-01.doc

C:\Moje dokumenty\Wojtek Romowicz\Książki\XML Vademecum profesjonalisty\r08-01.doc 1

[Author:T]

Wyszukiwarka