03 08 3PCC6KQGGUBWQDLCKMB2U3UQM Nieznany (2)

background image

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 53

3

Przetwarzanie

kodu XML

Dwa rozdziały solidnego wprowadzenia za nami. Możemy rozpocząć kodowanie! Poznaliśmy już
wszystkie akronimy składające się na świat XML-a, zagłębiliśmy się w sam język XML i pozna-
liśmy jego strukturę. W tym rozdziale rozpoczniemy programowanie w Javie od przetworzenia do-
kumentu XML i uzyskania dostępu do przetworzonych danych z poziomu kodu w Javie.

Najpierw będziemy musieli pobrać i przetworzyć dokument. W czasie przetwarzania dokumentu
staje się on dostępny dla aplikacji wykorzystującej parser. To właśnie jest aplikacja „obsługująca
XML” (w angielskim używa się określenia XML-aware, czyli „wiedząca o XML-u”). To wszystko
wydaje się zbyt proste, żeby było prawdziwe — ale tak właśnie jest. W tym rozdziale poznamy
szczegóły dotyczące przetwarzania dokumentu XML. Omówimy sposoby korzystania z parsera
wewnątrz aplikacji i przekazywania parserowi danych. Potem omówimy wywołania wsteczne do-
stępne w czasie przetwarzania. Są to miejsca, w których możliwe jest wstawienie kodu specyficz-
nego dla aplikacji i w których może nastąpić obróbka danych.

Oprócz omówienia sposobu pracy parserów, poznamy również interfejs Simple API for XML (SAX).
To właśnie SAX udostępnia wywołania wsteczne. Interfejsy dostępne w pakiecie SAX staną się
istotnym elementem naszego warsztatu. Klasy SAX są niewielkie i nieliczne, ale w całym naszym
omówieniu XML-a będziemy się opierali właśnie na tych klasach. Dobre zrozumienie ich dzia-
łania jest niezbędne do sprawnego korzystania z XML-a w programach Javy.

Przygotowujemy się

Są pewne rzeczy, które należy przygotować jeszcze przed rozpoczęciem programowania. Po
pierwsze, trzeba zaopatrzyć się w parser XML. Napisanie parsera XML to ogromne zadanie; ist-
nieje kilka doskonałych projektów zmierzających do zbudowania takiego parsera. Nie będziemy
opisywali tutaj, jak pisze się parsery XML, ale w jaki sposób aplikacje korzystają z funkcji udo-
stępnianych przez te parsery. Do manipulacji danymi XML wykorzystamy istniejące narzędzia. Po
wybraniu parsera musimy zaopatrzyć się w klasy SAX. Nie jest trudno je znaleźć; klasy te są wa-
runkiem przetwarzania XML-a z poziomu Javy. Oczywiście potrzebny jest również dokument
XML do przetworzenia.

background image

54

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 54

Zaopatrujemy się w parser

Najpierw należy zaopatrzyć się w odpowiedni parser (rodzaje parserów zostały pokrótce przedsta-
wione w rozdziale 1.). Aby parser działał ze wszystkimi przykładami z książki, należy sprawdzić,
czy jest zgodny ze specyfikacją XML. Ponieważ dostępnych jest wiele parserów, a w środowisku
programistów XML następują gwałtowne zmiany, opisanie poziomu zgodności różnych parserów
ze specyfikacją wykracza poza możliwości tej książki. Powinniśmy zasięgnąć informacji na stro-
nie producenta parsera i odwiedzić wymienione wcześniej witryny.

Zgodnie z duchem społeczności wolnego oprogramowania (open source), we wszystkich przykła-
dach tej książki wykorzystany zostanie parser Apache Xerces. Jest on dostępny w postaci źró-
dłowej i binarnej pod adresem http://xml.apache.org. Parser ten został napisany w C i w Javie
i jest używany przez liczną grupę programistów. Poza tym, jeśli korzystamy z parsera objętego za-
sadą wolnego oprogramowania, mamy możliwość wysłania autorom pytań lub powiadomień
o błędach — to przyczynia się do podwyższania jakości produktu. Aby zapisać się na ogólną listę
adresową dotyczącą parsera Xerces, wystarczy wysłać pusty e-mail pod adres xerces-dev-sub-
scribe@xml.apache.org
. Tam uzyskamy odpowiedzi na pytania dotyczące samego parsera oraz
uzyskamy pomoc w przypadku wystąpienia problemów nie ujętych w tej książce. Oczywiście,
przykłady z książki będą poprawnie obsłużone przez dowolny parser wykorzystujący opisywaną
tutaj implementację SAX.

Po dokonaniu wyboru i pobraniu parsera XML należy upewnić się, że nasze środowisko programi-
styczne — czy to IDE (Integrated Development Environment), czy wiersz poleceń — ma dostęp do
parsera poprzez ścieżkę klas. To podstawowy wymóg działania wszystkich dalszych przykładów.

Zaopatrujemy się w klasy i interfejsy SAX

Po zdobyciu parsera musimy zaopatrzyć się w klasy SAX. Klasy te są niemal zawsze dołączone do
parserów i Xerces nie stanowi tutaj wyjątku. Jeśli tak właśnie jest w naszym przypadku, nie po-
winniśmy pobierać klas oddzielnie, ponieważ parser na pewno wyposażony jest w najświeższą
obsługiwaną wersję klas. W czasie pisania tej książki zkończono prace nad SAX 2.0. Właśnie z klas
w tej wersji korzystamy w niniejszej książce i powinna być w nie wyposażona najświeższa wersja
Apache Xerces.

Jeśli nie jesteśmy pewni, czy posiadamy klasy SAX, wystarczy spojrzeć na plik jar lub strukturę klas
wykorzystywaną przez parser. Klasy SAX znajdują się w pakietach struktury

org.xml.sax

.

Najświeższa wersja zawiera w katalogu głównym 17 klas oraz 9 klas

org.xml.sax.hel-

pers

i 2 klasy

org.xml.sax.ext

. Jeśli brakuje którejś z tych klas, trzeba skontaktować się

z producentem i sprawdzić, czy zostały one zawarte w dystrybucji. Niektóre mogą być pominięte,
jeśli nie są w całości obsługiwane

1

. Tyle samo klas istnieje w interfejsie SAX 2.0; mniej klas

pojawi się w tych katalogach, gdy obsługiwany jest tylko SAX 1.0.

Warto także pobrać albo zaznaczyć zakładką przeglądarki dokumenty SAX API Javadoc na stro-
nach WWW. Dokumentacja ta bardzo przydaje się podczas korzystania z klas SAX, a struktura
Javadoc zapewnia standardowy i prosty sposób znajdowania dodatkowych informacji o klasach.

1

Obsługa klas SAX w całości jest bardzo istotną cechą parsera. Oczywiście, można korzystać z dowolnego parsera, ale

jeśli nie obsługuje on w całości SAX 2.0, wiele przykładów z książki nie będzie działało. Ponadto oznacza to, że parser
nie uwzględnia nowości w technologii XML. Bez względu na powód, warto zapoznać się z parserem Xerces.

background image

Czytniki SAX

55

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 55

Dokumentacja mieści się pod adresem http://www.megginson.com/SAX/SAX2/javadoc/index.html.
Dokumentację Javadoc można także wygenerować ze źródeł SAX-a poprzez kod zawarty w par-
serze lub poprzez pobranie pełnych źródeł z adresu http://www.megginson.com/SAX/SAX2.

Przygotowujemy dokument XML

Zawsze należy mieć pod ręką dokument XML do przetworzenia. Dane uzyskiwane w przykładach
wynikają z użycia dokumentu opisanego w rozdziale 2. Dokument ten należy zachować jako plik
contents.xml na dysku lokalnym. Zalecamy pracę właśnie na tym pliku. Można wpisać go ręcznie
albo pobrać ze strony WWW tej książki, http://www.oreilly.com/catalog/javaxml. Warto jednak
poświęcić chwilę czasu i wpisać go ręcznie, gdyż jest to praktyczna nauka składni XML-a.

Oprócz pobrania lub stworzenia pliku XML, będziemy musieli poczynić w nim kilka niewielkich
zmian. Ponieważ nie zostały jeszcze podane informacje o sposobie zawężania i przekształcania do-
kumentu, w tym rozdziale nasze programy będą wyłącznie przetwarzały („parsowały”) dokument.
Aby zapobiec błędom, trzeba usunąć z dokumentu odwołania do zewnętrznej definicji DTD zawę-
żającej XML oraz do arkuszy XSL powodujących jego przekształcenie. Wystarczy opatrzyć ko-
mentarzami te dwa wiersze dokumentu XML oraz instrukcję przetwarzania wysyłaną do modułu
Cocoon i żądającą przekształcenia:

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

<!-- To na razie się nie przyda
<?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"?>
<!DOCTYPE JavaXML:Ksiazka SYSTEM "DTD\JavaXML.dtd">
-->

<!-- Java i XML -->
<JavaXML:Ksiazka xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/">

Po opatrzeniu tych wierszy komentarzami należy zanotować pełną ścieżkę dostępu do dokumentu
XML. Trzeba ją będzie przekazać programom w tym i w dalszych rozdziałach.

Na koniec należy opatrzyć komentarzami odwołanie do zewnętrznej encji

OReillyCopyright

,

ponieważ powodowałaby ona ładowanie pliku z informacjami o prawach autorskich. Nie posiada-
jąc definicji DTD opisującej sposób przetworzenia tej encji, otrzymalibyśmy błędy. W następnym
rozdziale zostanie przedstawione zastosowanie tej encji.

</JavaXML:Spis>

<!-- To odkładamy do rozdziału o DTD
<JavaXML:Copyright>&OReillyCopyright;</JavaXML:Copyright>
-->

</JavaXML:Ksiazka>

Czytniki SAX

Po informacjach wstępnych czas przejść do programowania. Nasz pierwszy program pobierze plik
jako argument wiersza poleceń i przetworzy go. Zbudujemy wywołania wsteczne do procesu prze-
twarzania, dzięki czemu zdarzenia zachodzące w czasie tego procesu będą wyświetlane i umożli-
wią lepszą obserwację całego mechanizmu.

background image

56

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 56

Najpierw należy uzyskać egzemplarz (ang. instance) klasy zgodny z interfejsem SAX

org.xml.

sax.XMLReader

. Interfejs ten definiuje sposób przetwarzania i pozwala na ustawienie funkcji

i właściwości, które zostaną omówione w rozdziale 5., Sprawdzanie poprawności składni XML-a.
Informacja dla osób znających już SAX 1.0 — interfejs ten zastępuje dotychczasowy

org.xml.

sax.Parser

.

Instalacja czytnika

Interfejs udostępniany przez SAX powinien być zaimplementowany we wszystkich parserach XML
zgodnych z SAX-em. Dzięki temu SAX wie, jakie metody dostępne są dla wywołań wstecznych
i jakie można użyć z poziomu aplikacji. Na przykład główna klasa parsera SAX w Xerces,

org.apache.xerces.parsers.SAXParser

, implementuje interfejs

org.xml.sax.XML-

Reader

. Jeśli mamy dostęp do źródeł parsera, to zobaczymy, że ten sam interfejs zaimplemento-

wano w głównej klasie parsera SAX. Każdy parser musi posiadać jedną (a czasem więcej) klas,
które implementują ten interfejs. I właśnie egzemplarz tej klasy musimy stworzyć w celu przetwo-
rzenia danych XML:

XMLReader parser =
new SAXParser();

// Tu robimy coś z parserem

parser.parse(uri);

Osoby, które po raz pierwszy stykają się z interfejsem SAX, mogą być zaskoczone brakiem zmiennej
egzemplarza o nazwie

reader

czy

XMLReader

(reader to po angielsku „czytnik”). Rzeczy-

wiście, wydawałoby się, że tak powinny nazywać się te komponenty, ale w klasach SAX 1.0 zdefi-
niowano główny interfejs przetwarzający jako

Parser

i wiele zmiennych wywodzących się

z tamtego kodu odziedziczyło właśnie nazwę

parser

. Tamta postać interfejsu została już teraz

zaniechana, ponieważ duża liczba poczynionych zmian wymagała nowych przestrzeni nazw, fun-
kcji i właściwości, ale konwencje nazewnicze wciąż obowiązują, a

parser

dobrze oddaje prze-

znaczenie tego egzemplarza.

Mając to na uwadze, przyjrzyjmy się niewielkiemu programowi uruchamiającemu i tworzącemu
egzemplarz parsera SAX (przykład 3.1). Program ten nie przetwarza faktycznie dokumentu, ale
tworzy szkielet, w ramach którego będziemy mogli wykonać pozostałe ćwiczenia rozdziału. Fa-
ktyczne przetwarzanie rozpoczniemy w następnym rozdziale.

Przykład 3.1. Przykład użycia parsera SAX

import org.xml.sax.XMLReader;

// Tutaj importujemy implementację czytnika XML (XML Reader)

import org.apache.xerces.parsers.SAXParser;

/**
* <b><code>SAXParserDemo</code></b> pobiera plik XML i przetwarza je
* za pomocą SAX, wyświetlając wywołania wsteczne.
*
* @author
* <a href="mailto:brettmclaughlin@earthlink.net">Brett McLaughlin</a>
* @version 1.0
*/
public class SAXParserDemo {
/**

background image

Czytniki SAX

57

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 57

* <p>
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi

SAX;

* wyświetlamy zdarzenia zachodzące w cyklu przetwarzania.
* </p>
*
* @param uri <code>String</code> URI pliku do przetworzenia.
*/
public void performDemo(String uri) {
System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n");

// Stwórz egzemplarz parsera
XMLReader parser =
new SAXParser();
}

/**
* <p>
* Tu obsługujemy wiersz poleceń tego programu demonstracyjnego.
* </p>
*/
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Użycie: java SAXParserDemo [XML URI]");
System.exit(0);
}

String uri = args[0];
SAXParserDemo parserDemo = new SAXParserDemo();
parserDemo.performDemo(uri);
}
}

Czytelnik powinien umieć załadować i skompilować powyższy program, o ile tylko przygotował
się w opisany wcześniej sposób i klasy SAX znajdują się w ścieżce dostępu do klas. Ten prosty
program nie potrafi jeszcze zbyt wiele; jeśli uruchomimy go, podając wymyśloną nazwę pliku lub
URI jako argument, powinien „pobrzęczeć” dyskiem i wyświetlić komunikat „Przetwarzanie pliku
XML”. Wynika to stąd, że jedynie stworzyliśmy instancję parsera, a nie zażądaliśmy jeszcze prze-
tworzenia dokumentu XML.

Jeśli wynikły kłopoty z kompilacją powyższego pliku źródłowego, najprawdopodob-
niej problem tkwi w ścieżce dostępu do klas środowiska programistycznego lub syste-
mu. Najpierw należy upewnić się, że zainstalowany został parser Apache Xerces (lub
inny). W przypadku Xerces należy po prostu pobrać odpowiedni plik jar. Po rozpako-
waniu archiwum uzyskujemy plik xerces.jar — właśnie on zawiera skompilowane klasy
dla tego programu. Po dodaniu tego archiwum do ścieżki dostępu do klas nie powinno
być problemów z kompilacją powyższego przykładu.

Przetwarzanie dokumentu

Kiedy parser jest już załadowany i gotowy do wykorzystania, można przekazać mu do prze-
tworzenia dokument. Tradycyjnie służy do tego metoda

parse()

, wchodząca w skład klasy

org.xml.sax.XMLReader

. Metoda ta przyjmuje albo wejście

org.xml.sax.Input-

Source

, albo zwykły identyfikator URI. Na razie nie będziemy zajmowali się wejściem

In-

putSource

i skorzystamy z URI. Identyfikator taki może być adresem sieciowym, ale my

background image

58

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 58

użyjemy pełnej ścieżki dostępu do dokumentu, który przygotowaliśmy wcześniej. Gdybyśmy
jednak chcieli skorzystać z dokumentów umieszczonych w sieci, powinniśmy pamiętać o tym, że
aplikacja musi umieć znaleźć dany adres URL (tzn. musi być zapewnione połączenie z siecią).

Do programu dodajemy więc metodę

parse()

oraz dwie procedury obsługi błędów. Ponieważ

dokument musi zostać załadowany, czy to lokalnie, czy przez sieć, może wystąpić wyjątek

java.io.IOException

, który musimy przechwycić. Ponadto w czasie przetwarzania mo-

że zostać zgłoszony wyjątek

org.xmlsax.SAXException

(problem przy przetwarzaniu),

a więc musimy dodać jeszcze dwie istotne instrukcje, kilka linijek kodu i już mamy gotową do
użycia aplikację przetwarzającą XML:

import java.io.IOException;

import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

// Tutaj importujemy implementację czytnika XML (XML Reader)
import org.apache.xerces.parsers.SAXParser;

/**
* <b><code>SAXParserDemo</code></b> pobiera plik XML i przetwarza je
* za pomocą SAX, wyświetlając wywołania wsteczne.
*
* @author
* <a href="mailto:brettmclaughlin@earthlink.net">Brett McLaughlin</a>
* @version 1.0
*/
public class SAXParserDemo {

/**
* <p>
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi

SAX;

* wyświetlamy zdarzenia zachodzące w cyklu przetwarzania.
* </p>
*
* @param uri <code>String</code> URI pliku do przetworzenia.
*/
public void performDemo(String uri) {
System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n");

try {
// Stwórz egzemplarz parsera

XMLReader parser =
new SAXParser();

// Przetwórz dokument
parser.parse(uri);

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

}

public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Użycie: java SAXParserDemo [XML URI]");

background image

Procedury obsługi zawartości

59

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 59

System.exit(0);
}

String uri = args[0];

SAXParserDemo parserDemo = new SAXParserDemo();
parserDemo.performDemo(uri);
}

}

Tak zmodyfikowany przykład kompilujemy i wykonujemy. Pierwszym argumentem programu po-
winna być pełna ścieżka dostępu do przetwarzanego pliku:

D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml
Przetwarzanie pliku XML: D:\prod\JavaXML\contents\contents.xml

Widząc taki nieciekawy wynik działania programu Czytelnik może zacząć wątpić, czy w ogóle co-
kolwiek się stało. Jednak dokument XML jest rzeczywiście przetwarzany, a gdybyśmy podali błędny
identyfikator URI pliku, parser zgłosi wyjątek i poinformuje, że nie mógł przetworzyć pliku. Nie
stworzyliśmy jednak jeszcze żadnych wywołań wstecznych, powodujących że SAX informuje
w czasie przetwarzania, co się dzieje w danej chwili. Bez nich dokument jest przetwarzany w spo-
sób niewidoczny dla użytkownika i bez interwencji aplikacji. Oczywiście, my mamy zamiar inter-
weniować w ten proces, a więc musimy stworzyć metody wywołań wstecznych. To właśnie ta
„interwencja” jest najważniejszym aspektem korzystania z parsera SAX. Wywołania wsteczne
parsera umożliwiają podjęcie działania w czasie trwania programu. Przetwarzanie przestaje być
takie nieciekawe jak powyżej — aplikacja zaczyna reagować na dane, elementy, atrybuty i strukturę
przetwarzanego dokumentu, a w tym czasie współdziała jeszcze z innymi programami i klientami.

Korzystanie z InputSource

Zamiast korzystania z pełnego identyfikatora URI, metodę

parse()

można także wywołać z ar-

gumentem w postaci

org.xml.sax.InputSource

. Właściwie o tej klasie niewiele można

powiedzieć — to raczej klasa osłonowa (ang. wrapper) i pomocnicza. Klasa

InputSource

po

prostu kapsułkuje informacje o pojedynczym obiekcie. W naszym przykładzie nie na wiele się to
przyda, ale w sytuacjach, gdzie identyfikator systemowy, identyfikator publiczny lub strumień mo-
gą być dowiązane do jednego identyfikatora URI, kapsułkowanie za pomocą

InputSource

może się okazać bardzo przydatne. Klasa posiada metody akcesora i mutatora do obsługi identyfi-
katorów systemowego i publicznego, kodowania znaków, strumienia bajtów (

java.io.Input-

Stream

) oraz strumienia znaków (

java.io.Reader

). Jeśli w ten sposób przekażemy argu-

ment do

parse()

, to SAX gwarantuje również, że parser nigdy nie zmodyfikuje

InputSour-

ce

. Dzięki temu wiemy na pewno, że po wykorzystaniu parsera lub aplikacji obsługującej XML

dane wejściowe pozostaną niezmienione. Wiele aplikacji opisywanych w dalszych częściach
książki będzie wykorzystywało właśnie klasy

InputSource

, a nie specyficznego URI.

Procedury obsługi zawartości

Aby nasza aplikacja mogła zrobić cokolwiek pożytecznego z przetwarzanymi właśnie danymi XML,
w parserze SAX musimy zarejestrować procedury obsługi (ang. handlers). Procedura obsługi to
po prostu grupa wywołań wstecznych zdefiniowanych w ramach interfejsu SAX i umożliwiają-
cych wywoływanie kodu aplikacji w przypadku zajścia konkretnych zdarzeń w czasie przetwarza-
nia dokumentu. Trzeba zdać sobie sprawę z tego, że wywołania te będą następowały w czasie
przetwarzania dokumentu, a nie po jego przetworzeniu. To między innymi dlatego SAX jest tak

background image

60

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 60

potężnym interfejsem — umożliwia obsługę dokumentu sekwencyjnie, bez konieczności wczyty-
wania go całego do pamięci. Ograniczenie takie posiada model Document Object Model (DOM),
który zostanie omówiony w dalszej kolejności.

W interfejsie SAX 2.0 istnieją cztery podstawowe procedury obsługi:

org.xml.sax.Content-

Handler

,

org.xml.sax.ErrorHandler

,

org.xml.sax.DTDHandler

oraz

org.

xml.sax.EntityResolver

. W tym rozdziale omówimy procedurę

ContentHandler

,

umożliwiającą obsługę standardowych zdarzeń związanych z danymi dokumentu XML. Rozpo-
częte zostanie również omawianie procedury

ErrorHandler

, za pośrednictwem której parser

zgłasza znalezione w dokumencie błędy. Procedura

DTDHandler

zostanie omówiona w rozdziale

5. Procedura

EntityResolver

omawiana jest w różnych miejscach książki; teraz wystarczy

zrozumieć, że działa ona dokładnie tak jak pozostałe i służy do tłumaczenia encji zewnętrznych
wstawionych do dokumentu XML. Każdy z tych interfejsów może zostać zaimplementowany
w klasach aplikacji wykonujących specyficzne zadania. Klasy implementacyjne rejestrowane są
w parserze metodami

setContentHandler()

,

setErrorHandler()

,

setDTDHand-

ler()

i

setEntityResolver()

. Następnie parser wykonuje wywołania wsteczne tych me-

tod w razie uruchomienia konkretnego programu obsługi.

W naszym przykładzie zaimplementujemy interfejs

ContentHandler

. W interfejsie tym zde-

finiowano szereg istotnych metod cyklu przetwarzania, na które nasza aplikacja może reagować.
Przede wszystkim musimy dodać odpowiednie instrukcje

import

do pliku źródłowego (w tym

klasę i interfejs

org.xml.sax.Locator

i

org.xml.sax.Attributes

— oraz nową klasę

implementującą te metody wywołań wstecznych. Ta nowa klasa zostanie dodana na końcu pliku
źródłowego SAXParserDemo.java:

import java.io.IOException;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

// Tutaj importujemy implementację czytnika XML (XML Reader).
import org.apache.xerces.parsers.SAXParser;

/**
* <b><code>SAXParserDemo</code></b> pobiera pliki XML i przetwarza je
* za pomocą SAX, wyświetlając wywołania wsteczne.
*
* @author Brett McLaughlin
* @version 1.0
*/
public class SAXParserDemo {

/**
* <p>
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi

SAX;

* wyświetlamy zdarzenia zachodzące w cyklu przetwarzania.
* </p>
*
* @param uri <code>String</code> URI pliku do przetworzenia.
*/
public void performDemo(String uri) {
System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n");

background image

Procedury obsługi zawartości

61

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 61

// Stwórz egzemplarze procedur obsługi.
ContentHandler contentHandler = new MyContentHandler();

try {
// Stwórz egzemplarz parsera.
XMLReader parser =
new SAXParser();

// Zarejestruj procedurę obsługi zawartości.
parser.setContentHandler(contentHandler);

// Przetwórz dokument
parser.parse(uri);

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

}

/**
* <p>
* Tu obsługujemy wiersz poleceń tego programu demonstracyjnego.
* </p>
*/
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Użycie: java SAXParserDemo [XML URI]");
System.exit(0);
}

String uri = args[0];

SAXParserDemo parserDemo = new SAXParserDemo();
parserDemo.performDemo(uri);
}

}

/**
* <b><code>MyContentHandler</code></b> implementuje interfejs SAX
* <code>ContentHandler</code> i definiuje sposób zachowania
* wywołań wstecznych SAX powiązanych z zawartością
* dokumentu XML.
*/
class MyContentHandler implements ContentHandler {

/** Zmienna locator będzie zawierała informacje o położeniu */
private Locator locator;

/**
* <p>
* Odwołania do <code>Locator</code> umożliwią uzyskanie
* informacji o miejscu, w którym wystąpiło wywołanie wsteczne.
* </p>
*
* @param locator <code>Locator</code> -- obiekt dowiązany do
* procesu wywołań wstecznych.
*/

background image

62

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 62

public void setDocumentLocator(Locator locator) {
}

/**
* <p>
* Początek przetwarzanego dokumentu -- to dzieje się przed wszystkimi
* odwołaniami wszystkich procedur obsługi SAX oprócz
* <code>{@link #setDocumentLocator}</code>.
* </p>
*
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak
*/
public void startDocument() throws SAXException {
}

/**
* <p>
* Koniec przetwarzania dokumentu -- to dzieje się po wszystkich
* wywołaniach wstecznych wszystkich procedur obsługi. SAX.</code>.
* </p>
*
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak
*/
public void endDocument() throws SAXException {
}

/**
* <p>
* To oznaczać będzie, że napotkano instrukcję przetwarzania (nie na

deklarację XML).

* </p>
*
* @param target <code>String</code> obiekt docelowy instrukcji PI
* @param data <code>String</code> zawiera wszystkie dane wysłane do PI.
* Zazwyczaj ma to postać jednej lub więcej par
* atrybut - wartość.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void processingInstruction(String target, String data)
throws SAXException {
}

/**
* <p>
* To oznacza początek odwzorowywania przedrostka przestrzeni nazw XML.
* Zazwyczaj powinno się pojawić wewnątrz elementu głównego dokumentu

XML,

* ale nie jest to reguła (może pojawić się w dowolnym miejscu).
* Odwzorowanie przedrostka danego elementu uruchamia wywołanie wsteczne
* <i>przed</i> odwołaniem odnoszącym się do samego elementu
* (<code>{@link #startElement}</code>).
* </p>
*
* @param prefix <code>String</code> przedrostek dla odnalezionej
* przestrzeni nazw.
* @param uri <code>String</code> URI dla odnalezionej
* przestrzeni nazw.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void startPrefixMapping(String prefix, String uri) {
}

background image

Procedury obsługi zawartości

63

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 63

/**
* <p>
* To oznacza koniec odwzorowania przedrostka, kiedy nie jest już dostępna
* przestrzeń nazw określona w odwołaniu
* <code>{@link #startPrefixMapping}</code>.
* </p>
*
* @param prefix <code>String</code> znalezionej przestrzeni nazw.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void endPrefixMapping(String prefix) {
}

/**
* <p>
* Komunikat o pojawieniu się faktycznego elementu. Podawane są atrybuty
* elementu, za wyjątkiem atrybutów specyficznych dla słownika XML,
* takich jak:
* <code>xmlns:[namespace prefix]</code> i
* <code>xsi:schemaLocation</code>.
* </p>
*
* @param namespaceURI <code>String</code> URI przestrzeni nazw, z którą
* skojarzony jest ten element lub pusty
* <code>String</code>.
* @param localName <code>String</code> nazwa elementu (bez
* przedrostka przestrzeni nazw, jeśli taki istnieje)
* @param rawName <code>String</code> Wersja XML 1.0 nazwy elementu:
* [namespace prefix]:[localName].
* @param atts <code>Attributes</code> -- lista atrybutów tego elementu
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void startElement(String namespaceURI, String localName,
String rawName, Attributes atts)
throws SAXException {
}

/**
* <p>
* Oznacza, że osiągnięto koniec elementu.
* (<code>&lt;/[element name]&gt;</code>). Należy zauważyć, że parser
* nie rozróżnia pomiędzy elementami pustymi i niepustymi,
* a więc to będzie się odbywało identycznie w obu przypadkach.
* </p>
*
* @param namespaceURI <code>String</code> URI przestrzeni nazw, z jaką

skojarzony jest ten element

* @param localName <code>String</code> nazwa elementu bez przedrostka
* @param rawName <code>String</code> nazwa elementu w postaci XML. 1.0
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void endElement(String namespaceURI, String localName,
String rawName)
throws SAXException {
}

/**
* <p>
* Tutaj wyświetlamy dane tekstowe (zawarte wewnątrz elementu).
* </p>
*
* @param ch <code>char[]</code> tablica znaków zawartych w elemencie.

background image

64

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 64

* @param start <code>int</code> indeks w tablicy, w którym zaczynają się

dane.

* @param end <code>int</code> indeks w tablicy, w którym kończą się dane.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void characters(char[] ch, int start, int end)
throws SAXException {
}

/**
* <p>
* Tutaj informujemy o znakach białych, ignorowanych w oryginalnym

dokumencie.

* Zazwyczaj procedura taka uruchamiana jest jedynie wtedy, gdy w

procesie

* przetwarzania odbywa się też sprawdzanie poprawności.
* </p>
*
* @param ch <code>char[]</code> tablica znaków zawartych w elemencie.
* @param start <code>int</code> indeks w tablicy, w którym zaczynają się

dane.

* @param end <code>int</code> indeks w tablicy, w którym kończą się dane.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void ignorableWhitespace(char[] ch, int start, int end)
throws SAXException {
}

/**
* <p>
* Tutaj pokazujemy encję pominiętą przez parser. Zdarzenie powinno
* pojawiać się jedynie w parserach nie sprawdzających poprawności;
* jego zachowanie zależy od konkretnej implementacji.
* </p>
*
* @param name <code>String</code> nazwa pomijanej encji.
* @throws <code>SAXException</code> gdy coś pójdzie nie tak.
*/
public void skippedEntity(String name) throws SAXException {
}

}

Dodaliśmy puste implementacje wszystkich metod zdefiniowanych w ramach interfejsu

Con-

tentHandler

i nasz plik źródłowy kompiluje się. Oczywiście, takie puste implementacje nie

umożliwiają obserwacji całego procesu, więc teraz przyjrzymy się po kolei wszystkim wymaga-
nym metodom.

Lokalizator dokumentu

Pierwsza metoda, jaką należy zdefiniować, to ta ustawiająca

org.xml.sax.Locator

dla do-

wolnego zdarzenia. W przypadku wystąpienia wywołania wstecznego klasa implementująca pro-
cedurę obsługi niejednokrotnie musi uzyskać informację o miejscu w pliku XML przetwarzanym
przez parser. Dzięki temu aplikacja będzie mogła podjąć decyzję odnośnie danego zdarzenia i miej-
sca, w którym ono wystąpiło. Klasa

Locator

udostępnia szereg przydatnych metod, takich jak

getLineNumber()

i

getColumnNumber()

, zwracających bieżące miejsce w dokumencie

XML. Ponieważ położenie to odnosi się tylko do bieżącego cyklu przetwarzania, klasy

Locator

powinno się używać wyłącznie w zakresie implementacji

ContentHandler

. Ponieważ nam

background image

Procedury obsługi zawartości

65

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 65

może ona przydać się później, zachowujemy udostępniony egzemplarz klasy

Locator

do zmien-

nej składowej i drukujemy komunikat, informujący, że nastąpiło wywołanie wsteczne. Dzięki
temu poznamy kolejność występowania zdarzeń SAX:

/** Zmienna locator będzie zawierała informacje o położeniu */
private Locator locator;

/**
* <p>
* Odwołania do <code>Locator</code> umożliwią uzyskanie
* informacji o miejscu, w którym wystąpiło wywołanie wsteczne.
* </p>
*
* @param locator <code>Locator</code> -- obiekt dowiązany do
* procesu wywołań wstecznych.
*/
public void setDocumentLocator(Locator locator) {

System.out.println(" * setDocumentLocator() została wywołana");
// Zachowujemy do ewentualnego wykorzystania w przyszłości.
this.locator = locator;
}

Później, jeśli konieczne będzie uzyskanie informacji o miejscu zdarzenia, do metody tej można
dodać bardziej szczegółowe instrukcje. Jeśli jednak użytkownik chce pokazać, gdzie w dokumen-
cie pojawiają się dane zdarzenia (np. numer wiersza, w którym wystąpił element), musi przypisać
ten

Locator

zmiennej składowej do późniejszego wykorzystania w klasie.

Początek i koniec dokumentu

Każdy proces ma początek i koniec. Oba te zdarzenia powinny wystąpić raz — pierwsze przed wszy-
stkimi innymi zdarzeniami, a drugie po. Ten oczywisty fakt ma w aplikacjach krytyczne znaczenie
— dokładnie informuje, kiedy przetwarzanie rozpoczyna się i kiedy kończy. SAX udostępnia meto-
dy wywołań wstecznych dla obu tych zdarzeń:

startDocument()

i

endDocument()

.

Pierwsza metoda,

startDocument()

, wywoływana jest przed wszelkimi innymi wywołaniami

wstecznymi, również przed tymi znajdującymi się w innych procedurach obsługi SAX, np.

DTD-

Handler

. Innymi słowy,

startDocument()

to nie tylko pierwsza metoda wywoływana we-

wnątrz

ContentHandler

, ale także pierwsza metoda w całym procesie przetwarzania (nie

licząc metody

setDocumentLocator()

,wspomnianej przed chwilą). Dzięki temu wiemy,

kiedy przetwarzanie rozpoczyna się, a nasza aplikacja może wykonać procedury, które muszą być
uruchomione jeszcze przed tym przetwarzaniem.

Druga metoda,

endDocument()

, jest zawsze wywoływana jako ostatnia, również bez względu

na procedurę obsługi. Obejmuje to także sytuacje, w których proces przetwarzania zostaje za-
trzymany w wyniku napotkania błędów. Błędy zostaną omówione w dalszej części książki; teraz
Czytelnik powinien jednak wiedzieć, że są dwa ich rodzaje: błędy naprawialne i nienaprawialne.
W przypadku wystąpienia błędu nienaprawialnego wywoływana jest metoda procedury

Error-

Handler

, po czym przetwarzanie kończy się wywołaniem metody

endDocument()

.

W naszym przykładzie, kiedy obie te metody zostaną wywołane, wyświetlimy odpowiednie ko-
munikaty na konsoli.

/**
* <p>
* Początek przetwarzanego dokumentu -- to dzieje się przed wszystkimi
* odwołaniami wszystkich procedur obsługi SAX oprócz
* <code>{@link #setDocumentLocator}</code>.

background image

66

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 66

* </p>
*
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void startDocument() throws SAXException {

System.out.println("Rozpoczyna się przetwarzanie...");
}

/**
* <p>
* Koniec przetwarzania dokumentu -- to dzieje się po wszystkich
* wywołaniach wstecznych wszystkich procedur obsługi SAX</code>.
* </p>
*
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void endDocument() throws SAXException {

System.out.println("...Przetwarzanie skończone.");
}

Oba te wywołania wsteczne mogą zgłosić wyjątek

SAXException

. Jest to jedyny typ wyjątków

zgłaszany przez SAX. Za pośrednictwem wyjątków udostępniany jest kolejny standardowy inter-
fejs do procesu przetwarzania. Jednakże wyjątki te często pośredniczą w obsłudze innych wyjąt-
ków, wyraźnie wskazujących na to, z jakim problemem mamy do czynienia. Na przykład, jeśli
plik XML jest przetwarzany za pośrednictwem sieci (poprzez adres URL), a połączenie nagle
zostało przerwane, to zgłoszony zostaje

IOException

. Ale aplikacja korzystająca z klas SAX

nie musi umieć przechwytywać tego wyjątku, ponieważ w ogóle nie musi wiedzieć, gdzie zlokali-
zowano zasób XML. Aplikacja może obsługiwać tylko jeden wyjątek —

SAXException

.

Ten pierwszy wyjątek jest przechwytywany przez parser SAX i ponownie zgłaszany, tym razem
jako

SAXException

; pierwotny wyjątek jest „kapsułkowany” w nowym. Dzięki temu

wystarczy, żeby aplikacja przechwytywała jeden wyjątek, poprzez który dostarczane są także
szczegółowe informacje o naturze problemu, jaki wystąpił w procesie przetwarzania. Klasa

SAXException

udostępnia metodę

getException()

, zwracającą właściwy wyjątek.

Instrukcje przetwarzania

Instrukcje przetwarzania (PI) w dokumencie XML są elementami dość wyjątkowymi. Nie są postrze-
gane jako elementy samego XML-a i zamiast natychmiastowej obsługi, przekazywane są aplikacji
wywołującej. Z tego względu do ich obsługi SAX udostępnia specjalne wywołanie wsteczne. Me-
toda ta otrzymuje obiekt docelowy (ang. target) instrukcji oraz dane przekazane do PI. Załóżmy,
że życzymy sobie wyświetlenia na ekranie informacji w momencie pojawienia się wywołania:

/**
* <p>
* To oznaczać będzie, że napotkano instrukcję przetwarzania (nie
* deklarację XML).
* </p>
*
* @param target <code>String</code> obiekt docelowy instrukcji PI
* @param data <code>String</code> zawiera wszystkie dane wysłane do PI.
* Zazwyczaj ma to postać jednej lub więcej par
* atrybut - wartość.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void processingInstruction(String target, String data)
throws SAXException {

background image

Procedury obsługi zawartości

67

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 67

System.out.println("PI: Obiekt docelowy:" + target + " i dane:" +

data);

}

W rzeczywistej aplikacji, korzystającej z danych XML, w tym miejscu program mógłby otrzymać
instrukcje do ustawienia wartości zmiennych lub wykonania metod związanych z tą konkretną
aplikacją. Na przykład struktura publikacji Apache Cocoon mogłaby ustawić znaczniki związane z
przekształcaniem danych po ich przetworzeniu lub wyświetlić skrót XML jako określony typ
zawartości. Metoda ta, podobnie jak inne wywołania wsteczne SAX, zgłasza

SAXException

w ra-

zie wystąpienia błędu.

Podczas omawiania instrukcji przetwarzania wspomnieliśmy także o deklaracji XML. Ta specjalna
instrukcja przetwarzania udostępnia wersję, opcjonalne informacje o kodowaniu oraz o tym, czy
dany dokument jest dokumentem samodzielnym:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

Instrukcja ta przeznaczona jest specjalnie dla parsera XML. Umożliwia zgłoszenie błędu (np. o nie-
obsługiwanej wersji) na samym początku przetwarzania. Ponieważ instrukcja ta obsługiwana jest
tylko przez parser, nie powoduje wywołania

processingInstruction()

. Należy uważać,

aby nie stworzyć kodu oczekującego tej instrukcji lub informacji o wersji, ponieważ aplikacja ni-
gdy nie otrzyma wywołania wstecznego do tej instrukcji przetwarzania. Właściwie to tylko parser
powinien „interesować się” kodowaniem i wersją dokumentu XML, ponieważ elementy związane
są z samym procesem przetwarzania. Kiedy już dane dokumentu zostaną udostępnione poprzez
interfejs API Javy, szczegóły te stają się dla aplikacji nieistotne.

Wywołania związane z przestrzenią nazw

Sądząc po tym, ile czasu poświęciliśmy przestrzeniom nazw w XML-u (oraz po stopniu skom-
plikowania tej problematyki), można przypuszczać, że jest to pojęcie dość istotne i że ma duży
wpływ na przetwarzanie i obsługę danych XML. Oprócz schematu XML, przestrzenie nazw XML
to z pewnością najważniejsza cecha dodana do języka XML od czasu opublikowania oryginalnego
zalecenia XML 1.0. W interfejsie SAX 2.0 obsługę przestrzeni nazw wprowadzono na poziomie
elementów. Dzięki temu możliwe jest rozróżnienie pomiędzy przestrzenią nazwy elementu (opi-
saną przedrostkiem i odpowiednim identyfikatorem URI) a lokalną nazwą elementu. W tym przy-
padku, pojęciem lokalna nazwa określamy nazwę elementu bez przedrostka. Na przykład lokalną
nazwą dla

JavaXML:Ksiazka

jest po prostu

Ksiazka

. Przedrostek przestrzeni nazw to

Ja-

vaXML

, a identyfikator URI przestrzeni nazw (w naszym przykładzie) to http://www.oreilly.com/

catalog/javaxml.

Istnieją dwa wywołania wsteczne do obsługi przestrzeni nazw (choć wywołania związane z ele-
mentami mogą także z nich korzystać). Wywołania te następują wtedy, gdy parser natrafi na po-
czątek i koniec odwzorowania przedrostków (ang. prefix mapping). Sam termin nie jest jeszcze
znany, ale to pojęcie nie wprowadza nic nowego. Odwzorowanie przedrostków to po prostu ele-
ment, w którym za pomocą atrybutu

xmlns

deklarowana jest przestrzeń nazw. Jest to często

element główny (w którym może występować wiele różnych odwzorowań), ale nic nie stoi na prze-
szkodzie, aby tę rolę spełniał dowolny inny element dokumentu XML, jawnie deklarujący prze-
strzeń nazw. Na przykład:

<glowny>
<element1>
<mojaPrzestrzenNazw:element2 xmlns:mojaPrzestrzenNazw="http://mojUrl.pl">
<mojaPrzestrzenNazw:element3>Tu jakieś

dane</mojaPrzestrzenNazw:element3>

background image

68

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 68

</mojaPrzestrzenNazw:element2>
</element1>
</glowny>

W tym przypadku przestrzeń nazw deklarowana jest jawnie, na głębokości kilku poziomów za-
gnieżdżenia elementów.

Wywołanie

startPrefixMapping()

otrzymuje przedrostek przestrzeni nazw oraz identyfika-

tor URI skojarzony z tym przedrostkiem. Odwzorowanie uznawane jest za „zamknięte” lub „za-
kończone”, gdy zamknięty zostaje element deklarujący to odwzorowanie. Jedyny „haczyk” tego
wywołania polega na tym, że zachowuje się ono niezupełnie sekwencyjnie — tak jak to ma za-
zwyczaj miejsce w interfejsie SAX; odwzorowanie przedrostków odbywa się bezpośrednio przed
wywołaniem związanym z elementem deklarującym przestrzeń nazw. Oto przykład wywołania:

/**
* <p>
* To oznacza początek odwzorowania przedrostka przestrzeni nazw XML.
* Zazwyczaj powinno się pojawić wewnątrz elementu głównego dokumentu

XML,

* ale nie jest to reguła (może pojawić się w dowolnym miejscu).
* Odwzorowanie przedrostka danego elementu uruchamia wywołanie wsteczne
* <i>przed</i> odwołaniem odnoszącym się do samego elementu
* (<code>{@link #startElement}</code>).
* </p>
*
* @param prefix <code>String</code> przedrostek dla odnalezionej
* przestrzeni nazw
* @param uri <code>String</code> URI dla odnalezionej
* przestrzeni nazw
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak
*/
public void startPrefixMapping(String prefix, String uri) {

System.out.println("Początek odwzorowania dla przedrostka " + prefix +
" odwzorowanego dla URI " + uri);
}

W naszym dokumencie jedynym zadeklarowanym odwzorowaniem jest atrybut elementu głównego.
Oznacza to, że powinniśmy oczekiwać tego wywołania przed wywołaniem związanym z pierw-
szym elementem (o którym za chwilę), ale już po wywołaniu

startDocument()

oraz po

wszelkich instrukcjach PI, jakie znajdują się na początku dokumentu. Drugie z tej pary wywołań
związanych z przestrzeniami nazw oznacza napotkanie końca odwzorowania i pojawia się bezpo-
średnio po znaczniku zamykającym elementu, w którym zadeklarowano odwzorowanie:

/**
* <p>
* To oznacza koniec odwzorowania przedrostka, kiedy nie jest już dostępna
* przestrzeń nazw określona w odwołaniu
* <code>{@link #startPrefixMapping}</code>.
* </p>
*
* @param prefix <code>String</code> znalezionej przestrzeni nazw
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak
*/
public void endPrefixMapping(String prefix) {

System.out.println("Koniec odwzorowania dla przedrostka " + prefix);
}

W przypadku pokazanego powyżej fragmentu dokumentu XML można oczekiwać następującego
komunikatu po napotkaniu znacznika

element2

:

background image

Procedury obsługi zawartości

69

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 69

Początek odwzorowania dla przedrostka mojaPrzestrzenNazw odwzorowanego dla URI

http://mojUrl.pl

W ten sposób można poznać odwzorowywany przedrostek oraz skojarzony z nim identyfikator URI.

Wywołania związane z elementami

Teraz najprawdopodobniej przygotowani już jesteśmy na faktyczne pobranie danych z dokumentu
XML. To prawda, że ponad połowa wywołań wstecznych SAX nie ma nic wspólnego z elementa-
mi, atrybutami i danymi XML. Wynika to stąd, że proces przetwarzania nie polega po prostu na
przekazaniu aplikacji danych XML; aplikacja otrzymuje także instrukcje PI, dzięki którym wie,
jakie działanie ma zostać podjęte; aplikacja „dowiaduje się” także, kiedy przetwarzanie rozpoczy-
na się i kończy, a nawet gdzie znajdują się białe znaki, które można zignorować. Jeśli niektóre
z tych wywołań wydają się Czytelnikowi pozbawione sensu, to należy uzbroić się w cierpliwość
— jeszcze w tym rozdziale niektóre zagadnienia zostaną wyjaśnione; więcej wiadomości na ten te-
mat znaleźć można w rozdziale 5.

Oczywiście, istnieją wywołania SAX służące do uzyskiwania dostępu do danych XML zawartych
w dokumentach. Pierwsze trzy zdarzenia, jakie zostaną omówione, to początek i koniec elementu
oraz wywołanie

characters()

. Informują one, kiedy przetwarzany jest element, jakie dane za-

wiera oraz kiedy parser napotkał znacznik zamykający elementu. Pierwsze z wywołań,

start-

Element()

, informuje aplikację o elemencie XML i ewentualnych jego atrybutach. Parametry

wywołania to nazwa elementu (w różnych postaciach) oraz egzemplarz klasy

org.xml.sax.At-

tributes

(Czytelnik powinien przypomnieć sobie opisywaną wcześniej instrukcję importującą).

Ta klasa pomocnicza zawiera referencje do wszystkich atrybutów elementu. Umożliwia proste
przetwarzanie kolejnych atrybutów elementu w postaci podobnej do

Vector

. Oprócz możliwości

odwołania się do atrybutu za pomocą indeksu (kiedy przetwarzamy wszystkie atrybuty po kolei),
możliwe jest również odwołanie się poprzez nazwę. Oczywiście, w tej chwili Czytelnik powinien
już być ostrożniejszy, słysząc słowo „nazwa” związane z elementem lub atrybutem XML — może
ono znaczyć różne rzeczy. W tym przypadku można użyć albo pełnej nazwy atrybutu (z przed-
rostkiem przestrzeni nazw, o ile taki tam jest) — to nazwiemy „surową” nazwą; albo połączenia
nazwy lokalnej i identyfikatora URI, jeśli wykorzystano przestrzeń nazw. Istnieją także metody
pomocnicze, takie jak

getURI(int index)

i

getLocalName(int index)

, dzięki któ-

rym można uzyskać dodatkowe informacje o przestrzeni nazw związanej z danym atrybutem. Cały
interfejs

Attributes

stanowi więc wszechstronne źródło danych o atrybutach elementu.

Jak wspomnieliśmy, nie tylko atrybuty, ale również nazwa samego elementu może przyjmować
różne formy. I znów chodzi tu o przestrzeń nazw w XML-u. Najpierw dostarczany jest identyfika-
tor URI przestrzeni nazw danego elementu. Dzięki temu element umieszczany jest w poprawnym
kontekście i odpowiedniej relacji do pozostałych przestrzeni nazw w dokumencie. Następnie po-
dawana jest lokalna nazwa elementu, czyli fragment bez przedrostka. Oprócz tego (na potrzeby
zgodności wstecz), podawana jest „surowa” nazwa elementu. Chodzi tu o niezmodyfikowaną naz-
wę elementu, zawierającą ewentualny przedrostek przestrzeni nazw — czyli to, co umieszczone
było w dokumencie XML; dla naszego elementu

Ksiazka

byłaby to nazwa

JavaXML:

Ksiazka

. Obecność tych trzech typów nazw umożliwia opisanie dowolnego elementu, z prze-

strzenią nazw, czy też bez niej. Skoro wiemy już, jak udostępnia się element i jego atrybuty,
spójrzmy na implementację wywołania wstecznego SAX, wyświetlającą informację na ekranie.
W tym przykładzie sprawdzamy, czy nazwa elementu posiada skojarzony z nią identyfikator URI
przestrzeni nazw; jeśli tak — drukujemy przestrzeń nazw; jeśli nie — drukujemy komunikat, in-
formujący, że z elementem nie skojarzono żadnej przestrzeni nazw:

/**

background image

70

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 70

* <p>
* Komunikat o pojawieniu się faktycznego elementu. Podawane są atrybuty
* elementu, za wyjątkiem atrybutów specyficznych dla słownika XML,
* takich jak:
* <code>xmlns:[namespace prefix]</code> i
* <code>xsi:schemaLocation</code>.
* </p>
*
* @param namespaceURI <code>String</code> URI przestrzeni nazw, z którą
* skojarzony jest ten element lub pusty.
* <code>String</code>
* @param localName <code>String</code> nazwa elementu (bez
* przedrostka przestrzeni nazw, jeśli taki istnieje)
* @param rawName <code>String</code> Wersja XML 1.0 nazwy elementu:
* [namespace prefix]:[localName]
* @param atts <code>Attributes</code> -- lista atrybutów tego elementu.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void startElement(String namespaceURI, String localName,
String rawName, Attributes atts)
throws SAXException {

System.out.print("startElement: " + localName);
if (!namespaceURI.equals("")) {
System.out.println(" w przestrzeni nazw " + namespaceURI +
" (" + rawName + ")");
} else {
System.out.println(" nie posiada skojarzonej przestrzeni nazw");
}

for (int i=0; i<atts.getLength(); i++)
System.out.println(" Atrybut: " + atts.getLocalName(i) +
"=" + atts.getValue(i));
}

Wywołanie SAX bardzo upraszcza cały ten proces. W przypadku wywołania

startElement()

trzeba jeszcze zwrócić uwagę na fakt, że nie zachowuje się kolejności atrybutów. Podczas iteracji
po implementacji klasy

Attributes

atrybuty niekoniecznie będą pojawiały się w kolejności,

w jakiej zostały przetworzone (czyli w takiej, w jakiej wpisano je do dokumentu). Nie należy więc
polegać na uzyskanej kolejności atrybutów — i język XML nie wymaga od parserów zachowy-
wania tej kolejności. W niektórych parserach kolejność jest zachowywana, ale nie należy to do
udokumentowanych funkcji takich parserów.

Zamykająca część wywołania związanego z elementem to metoda

endElement()

. To proste

wywołanie właściwie tłumaczy się samo — przekazywana mu jest tylko nazwa elementu, co umoż-
liwia dopasowanie do odpowiedniego elementu przekazanego wcześniej do wywołania

start-

Element()

. Głównym przeznaczeniem tego wywołania jest zaznaczenie zamknięcia elementu

— dzięki temu aplikacja wie, czy dalsze znaki są częścią innego zakresu, czy zamykanego właśnie
elementu. W naszym przykładzie, podczas zamykania elementu wydrukujemy po prostu jego nazwę:

/**
* <p>
* Oznacza, że osiągnięto koniec elementu
* (<code>&lt;/[element name]&gt;</code>). Należy zauważyć, że parser
* nie rozróżnia pomiędzy elementami pustymi i niepustymi,
* a więc to będzie się odbywało identycznie w obu przypadkach.
* </p>
*
* @param namespaceURI <code>String</code> URI przestrzeni nazw, z jaką

skojarzony

background image

Procedury obsługi zawartości

71

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 71

* jest ten element.
* @param localName <code>String</code> nazwa elementu bez przedrostka
* @param rawName <code>String</code> nazwa elementu w postaci XML. 1.0
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void endElement(String namespaceURI, String localName,
String rawName)
throws SAXException {

System.out.println("endElement: " + localName + "\n");
}

Dane elementu

Po określeniu początku i końca bloku elementu oraz po „wyłuskaniu” atrybutów tego elementu ko-
lejną istotną informacją do pobrania są same dane zawarte w elemencie. Dane składają się za-
zwyczaj z kolejnych elementów, danych tekstowych lub obu jednocześnie. Kiedy pojawiają się
kolejne elementy, następują dla nich odpowiednie wywołania i rozpoczyna się proces pseudoreku-
rencyjny — elementy zagnieżdżone w elementach powodują wywołania „zagnieżdżone” w wywoła-
niach. W pewnej chwili parser napotyka dane tekstowe. Te są zazwyczaj dla klienta najważniejsze
— informacje takie przeważnie są wyświetlane lub służą do zbudowania odpowiedzi.

W XML-u dane tekstowe w elementach przesyłane są do aplikacji za pomocą wywołania

cha-

racters()

. Metoda ta udostępnia aplikacji tablicę znaków oraz indeksy początkowy i końcowy,

które wskazują, gdzie należy odczytać odpowiednie dane:

/**
* <p>
* Tutaj wyświetlamy dane tekstowe (zawarte wewnątrz elementu).
* </p>
*
* @param ch <code>char[]</code> tablica znaków zawartych w elemencie.
* @param start <code>int</code> indeks w tablicy, w którym zaczynają się

dane.

* @param end <code>int</code> indeks w tablicy, w którym kończą się dane.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void characters(char[] ch, int start, int end)
throws SAXException {

String s = new String(ch, start, end);
System.out.println("znaki: " + s);
}

To, wydawałoby się, proste wywołanie niejednokrotnie powoduje spore zamieszanie, ponieważ in-
terfejs i standard SAX nie definiują ściśle, jak korzystać z tego wywołania przy przetwarzaniu
większych ilości danych tekstowych. Innymi słowy, parser może zwrócić długi ciąg danych w je-
dnym wywołaniu lub rozbić dane na wiele wywołań. Dla danego elementu metoda ta może zostać
wywołana zero razy (jeśli element nie zawiera danych tekstowych) albo jeden lub więcej razy.
W różnych parserach będzie się to odbywało inaczej, niejednokrotnie z wykorzystaniem algoryt-
mów mających na celu przyspieszenie przetwarzania. Nigdy nie należy liczyć na uzyskanie wszy-
stkich danych tekstowych po jednym wywołaniu metody; podobnie nigdy nie należy zakładać, że
wiele wywołań zwróci nam ciąg danych pochodzący z jednego tylko elementu.

Przy tworzeniu procedur obsługi SAX trzeba także pamiętać o postępowaniu hierarchicznym. In-
nymi słowy, nie wolno uważać, że dowolny element posiada swoje dane oraz elementy potomne,
należy traktować go jako element macierzysty. Pamiętać należy również, że parser „porusza się”,

background image

72

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 72

obsługując elementy, atrybuty i dane w miarę ich osiągania. Mogą z tego wynikać pewne niespo-
dzianki. Spójrzmy na taki fragment dokumentu XML:

To jestzagnieżdżony tekstdalszy tekst

Jeśli zapomnimy, że SAX przetwarza sekwencyjnie i wywołuje metody w miarę napotykania ele-
mentów i danych, oraz nie uwzględnimy faktu, że XML jest konstrukcją hierarchiczną, możemy
spodziewać się następującego rezultatu:

startElement: macierzysty nie posiada skojarzonej przestrzeni nazw
znaki: To jest dalszy tekst.
startElement: potomny nie posiada skojarzonej przestrzeni nazw
znaki: zagnieżdżony tekst.
endElement: potomny.
endElement: macierzysty.

To wydawałoby się logiczne, bo

macierzysty

całkowicie „zawiera” element

potomny

, praw-

da? Ale dzieje się inaczej — dla każdego zdarzenia SAX wywoływana jest metoda, co powoduje
następujący wynik:

startElement: macierzysty nie posiada skojarzonej przestrzeni nazw.
znaki: To jest.
startElement: potomny nie posiada skojarzonej przestrzeni nazw.
znaki: zagnieżdżony tekst.
endElement: potomny.
znaki: dalszy tekst.
endElement: macierzysty.

SAX nie wybiega z przetwarzaniem naprzód, a wiec wynik jest dokładnie taki, jaki uzyskalibyśmy
czytając dokument XML sekwencyjnie, bez „ludzkich” przyzwyczajeń. Warto o tym pamiętać.

I jeszcze jedno — metoda

characters()

często informuje o białych znakach. To powoduje

dalsze niezrozumienie, bo przecież inne wywołanie SAX,

ignorableWhitespace()

, także

komunikuje o białych znakach. W naszym przykładzie nie sprawdzamy poprawności składni do-
kumentu XML; ale możemy przecież korzystać z parsera sprawdzającego poprawność składni. To
dość ważny szczegół, bo sposób komunikowania o białych znakach zależy od tego, czy parser ma
funkcję sprawdzania poprawności składni, czy nie. Parsery sprawdzające poprawność składni będą
komunikowały o wszystkich białych znakach poprzez metodę

ignorableWhitespace()

— jest

to spowodowane pewnymi czynnikami związanymi ze sprawdzaniem poprawności składni, które
zostaną omówione w następnych dwóch rozdziałach. Parsery nie sprawdzające poprawności
składni komunikują o białych znakach albo poprzez metodę

ignorableWhitespace()

, albo

poprzez metodę

characters()

. Konieczne jest więc sprawdzenie, czy mamy do czynienia z par-

serem sprawdzającym poprawność składni.

Należy wiedzieć, że na wiele parserów faktycznie składają się dwie implementacje parsera — je-
dna sprawdzająca, a druga nie sprawdzająca poprawności składni. W czasie działania parsera odpo-
wiednia klasa ładowana jest dynamicznie. Wynika to stąd, że parser nie sprawdzający poprawności
często pracuje bardziej wydajnie niż ten sprawdzający, nawet jeśli sprawdzanie poprawności faktycz-
nie się nie odbywa (z powodu konieczności implementacji dodatkowych struktur danych w parserze
sprawdzającym). Tak właśnie jest w parserze Apache Xerces. W naszym przykładzie wykorzysta-
ny zostanie egzemplarz parsera nie sprawdzającego składni, ale gdyby określono definicję DTD
lub schemat i gdyby zażądano sprawdzania poprawności, użyta zostałaby klasa parsera sprawdza-
jącego poprawność.

Aby uniknąć tego całego zamieszania, najlepiej w ogóle nie czynić żadnych założeń dotyczących
białych znaków. Rzadko konieczne jest wykorzystanie białych znaków jako danych w dokumencie

background image

Procedury obsługi zawartości

73

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 73

XML. Jeśli zachodzi taka potrzeba (np. w przypadku konstrukcji typu „kilka spacji + dane + kilka
spacji”, gdzie liczba spacji ma znaczenie w danej aplikacji), to należy korzystać z konstrukcji

CDATA

. To zapewni, że dane, w których liczy się liczba spacji, nie będą w ogóle przetwarzane

— obsłużone zostaną przez aplikację nadrzędną jako duży „wycinek” danych tekstowych. W po-
zostałych przypadkach należy unikać stosowania spacji w reprezentowaniu danych i czynienia ja-
kichkolwiek założeń odnośnie sposobu zwracania spacji przez wywołania.

Spokojnie, to tylko białe znaki

Zagadnienia związane z białymi znakami zostały już omówione. Teraz wystarczy dodać to ostatnie
wywołanie SAX do naszej klasy

MyContentHandler

. Metoda

ignorableWhitespace()

pobiera parametry w dokładnie takim samym formacie jak metoda

characters()

i powinna

wykorzystywać indeksy początkowy i końcowy, udostępniane w celu czytania znaków z dostar-
czonej tablicy:

/**
* <p>
* Tutaj informujemy o znakach białych, ignorowanych w oryginalnym

dokumencie.

* Zazwyczaj procedura taka uruchamiana jest jedynie wtedy, gdy w

procesie

* przetwarzania odbywa się też sprawdzanie poprawności.
* </p>
*
* @param ch <code>char[]</code> tablica znaków zawartych w elemencie.
* @param start <code>int</code> indeks w tablicy, w którym zaczynają się

dane.

* @param end <code>int</code> indeks w tablicy, w którym kończą się dane.
* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void ignorableWhitespace(char[] ch, int start, int end)
throws SAXException {

String s = new String(ch, start, end);
System.out.println("ignorableWhitespace: [" + s + "]");
}

Oczywiście, nasz program nie wydrukuje nic widzialnego, ponieważ utworzony

String

będzie

się składał wyłącznie z białych znaków — dlatego dane wyjściowe ujmujemy w nawiasy klamro-
we. Białe znaki wyświetlane są w taki sam sposób jak zwykłe dane tekstowe; mogą zostać ob-
służone przez jedno wywołanie, proces ten może również zostać rozbity w parserze SAX na kilka
wywołań. W każdym razie w celu uniknięcia błędów w aplikacji należy przestrzegać tych samych
zasad, które obowiązują w przypadku danych tekstowych.

Pominięte encje

Jak już wspomnieliśmy, w naszym dokumencie pojawiła się jedna encja —

OreillyCopy-

right

. Przetworzenie i przetłumaczenie takiej encji oznacza załadowanie innego pliku, albo

z lokalnego systemu plików, albo z identyfikatora URI. Jednakże w tym dokumencie nie wyma-
gamy takiego przetworzenia. Z parserami nie sprawdzającymi poprawności składni wiąże się
jedna, często pomijana cecha — nie muszą one także tłumaczyć encji — po prostu pomijają je.
W interfejsie SAX 2.0 sprawę rozwiązano bardzo elegancko — kiedy encja jest pomijana przez
parser nie sprawdzający poprawności, wywoływana jest specjalna metoda. W wywołaniu podawa-
na jest nazwa encji — my wykorzystamy tę właściwość do wyświetlenia odpowiedniej informacji

background image

74

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 74

na ekranie (parser Apache Xerces nie działa w ten sposób, ale nie znaczy to, że będzie tak w przy-
padku parsera zastosowanego przez Czytelnika):

/**
* <p>
* Tutaj pokazujemy encję pominiętą przez parser. Zdarzenie powinno
* pojawiać się jedynie w parserach nie sprawdzających poprawności;
* jego zachowanie zależy od konkretnej implementacji.
* </p>
*
* @param name <code>String</code> nazwa pomijanej encji.
* @throws <code>SAXException</code> gdy coś pójdzie nie tak.
*/
public void skippedEntity(String name) throws SAXException {

System.out.println("Pomijam encję " + name);
}

Zanim spróbujemy wykorzystać powyższą metodę, musimy pamiętać, że większość istniejących
parserów nie pomija encji, nawet jeśli nie są to parsery sprawdzające poprawność. Na przykład
parser Apache Xerces nigdy nie skorzysta z tego wywołania. Parser ten tłumaczy encję, a otrzyma-
ny wynik dołącza do danych otrzymywanych po przetworzeniu. Innymi słowy, wywołanie to cze-
ka na wykorzystanie przez parser, ale niełatwo będzie nam znaleźć parser, który z niego skorzysta!
Jeśli jednak posiadamy parser, który korzysta z powyższego wywołania, należy pamiętać, że prze-
kazany parametr nie zawiera otwierającego znaku „ampersand” ani końcowego średnika. W przy-
padku

&OReillyCopyright;

do metody

skippedEntity()

przekazywany jest tylko łańcuch

OReillyCopyright

.

Wyniki

Teraz należy zarejestrować procedurę obsługi w klasie

XMLReader

, której egzemplarz stworzy-

liśmy. Służy do tego metoda

setContentHandler()

, której jedynym argumentem jest implemen-

tacja

ContentHandler

. Do metody

demo()

w naszym przykładowym programie dodajemy na-

stępujące wiersze:

/**
* <p>
* Tutaj przetwarzamy plik za pomocą zarejestrowanych procedur obsługi

SAX;

* wyświetlamy zdarzenia zachodzące w cyklu przetwarzania.
* </p>
*
* @param uri <code>String</code> URI pliku do przetworzenia.
*/
public void performDemo(String uri) {
System.out.println("Przetwarzanie pliku XML: " + uri + "\n\n");

// Stwórz egzemplarze procedur obsługi
ContentHandler contentHandler = new MyContentHandler();

try {
// Stwórz egzemplarz parsera
XMLReader parser =
new SAXParser();

// Zarejestruj procedurę obsługi zawartości
parser.setContentHandler(contentHandler);

// Przetwórz dokument
parser.parse(uri);

background image

Procedury obsługi zawartości

75

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 75

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

}

Jeśli Czytelnik dołączył wszystkie omawiane wyżej wywołania do pliku, powinien skompilować
klasę

MyContentHandler

oraz zawierający ją plik

SAXParserDemo

. Po udanej kompilacji

można uruchomić program demonstrujący działanie parsera na stworzonym wcześniej przykłado-
wym pliku XML. Pełne polecenie wywołujące Javę powinno mieć postać:

D:\prod\JavaXML> java SAXParserDemo D:\prod\JavaXML\contents\contents.xml

Polecenie to powinno zwrócić dużo danych wyjściowych. Jeśli Czytelnik korzysta z systemu Win-
dows, może się okazać konieczne zwiększenie bufora dla okna DOS-owego w takim stopniu, aby
możliwe było przewinięcie ekranu i obejrzenie wszystkich danych wyjściowych. Dane te powinny
mieć postać podobną do tych przedstawionych w przykładzie 3.2

2

.

Przykład 3.2. Dane wyjściowe zwracane przez program SAXParserDemo

Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana.
Rozpoczyna się przetwarzanie...
Początek odwzorowania dla przedrostka JavaXML odwzorowanego dla URI.
http://www.oreilly.com/catalog/javaxml/
startElement: Ksiazka w przestrzeni nazw.
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Ksiazka)
znaki:

startElement: Tytul w przestrzeni nazw.
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Tytul)
znaki: Java i XML
endElement: Tytul

znaki:

startElement: Spis w przestrzeni nazw.
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Spis)
znaki:

startElement: Rozdzial w przestrzeni nazw.
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Rozdzial)
Atrybut: tematyka=XML
znaki:

startElement: Naglowek w przestrzeni nazw
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Naglowek)
znaki: Wprowadzenie
endElement: Naglowek

2

W tym oraz innych przykładach danych zwracanych przez programy możliwe jest, że zostały dodatkowo przełamane

wiersze w celu poprawnego umieszczenia wydruku w książce. O ile jednak same dane tekstowe się zgadzają, to na pew-
no wszystko jest w porządku!

background image

76

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 76

znaki:

startElement: Temat w przestrzeni nazw
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat)
Atrybut: podRozdzialy=7
znaki: Co to jest?
endElement: Temat

znaki:

startElement: Temat w przestrzeni nazw
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat)
Atrybut: podRozdzialy=3
znaki: Jak z tego korzystać?
endElement: Temat

znaki:

startElement: Temat w przestrzeni nazw
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat)
Atrybut: podRozdzialy=4
znaki: Dlaczego z tego korzysta?
endElement: Temat

znaki:

startElement: Temat w przestrzeni nazw
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Temat)
Atrybut: podrozdzialy=0
znaki: Co dalej?
endElement: Temat

...

Takich danych pojawi się na wyjściu sporo, ponieważ przetwarzany dokument XML ma wiele ele-
mentów. Wyraźnie widać, że parser sekwencyjnie przetwarza każdy element, atrybuty elementu,
dane w nim zawarte, elementy zagnieżdżone oraz znacznik końcowy elementu. Proces powtarza
się dla każdego elementu. W naszym przykładzie został zastosowany parser Apache Xerces w wersji
nie sprawdzającej poprawności dokumentu, a więc białe znaki są zgłaszane za pomocą wywołań

characters()

. W następnych dwóch rozdziałach zostaną omówione zagadnienia związane ze

sprawdzaniem poprawności i wówczas będzie można porównać wynik działania.

Teraz wiemy już, w jaki sposób parser zgodny z interfejsem SAX przetwarza dokument XML. Po-
winniśmy również rozumieć wywołania pojawiające się w czasie procesu przetwarzania oraz to,
w jaki sposób można z nich uzyskać informacje potrzebne aplikacji. W następnych dwóch rozdzia-
łach zostaną omówione zagadnienia związane ze sprawdzaniem poprawności dokumentu XML za
pomocą dodatkowych klas SAX służących do obsługi definicji DTD. Teraz sprawdzimy, jakie
błędy mogą się pojawić, gdy dokument nie będzie poprawny, co dzieje się, gdy dokument XML
nie jest poprawny i jakie błędy mogą z tego wynikać.

Procedury obsługi błędów

Oprócz interfejsu

ContentHandler

, SAX udostępnia również interfejs

ErrorHandler

, słu-

żący do obsługi sytuacji awaryjnych zaistniałych w czasie przetwarzania. Klasa ta działa podobnie
jak omówiona wcześniej procedura obsługi dokumentu, ale zdefiniowano w niej jedynie trzy

background image

Procedury obsługi błędów

77

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 77

wywołania wsteczne. Przy pomocy tych trzech metod SAX obsługuje i powiadamia o wszystkich
możliwych błędach.

Każda metoda otrzymuje informacje o błędzie lub ostrzeżeniu poprzez klasę

SAXParseExcep-

tion

. Obiekt ten zawiera numer wiersza, w którym wystąpił błąd, identyfikator URI przetwarza-

nego dokumentu (dokument przetwarzany lub zewnętrzne odwołanie wewnątrz tego dokumentu)
oraz zwykłe informacje o błędzie, takie jak komunikat i dane ze śledzenia stosu. Ponadto każda
metoda może zgłosić

SAXException

. Na początku może się to wydawać nieco dziwne — pro-

gram obsługi wyjątków zgłaszający wyjątek? Należy jednak pamiętać, że każda z procedur obsługi
błędów otrzymuje wyjątki związane z przetwarzaniem. Może to być ostrzeżenie, które nie powin-
no przerwać procesu przetwarzania, lub błąd, który należy rozwiązać, aby to przetwarzanie mogło
być kontynuowane. Jednak wywołanie takie może także wykonywać operacje wejścia-wyjścia lub
inne, które mogą spowodować zgłoszenie wyjątku — i wyjątek ten należy przekazać aż „na samą
górę”, do aplikacji. Służy do tego właśnie wyjątek

SAXException

.

Załóżmy, że procedura obsługi błędów otrzymuje powiadomienia o błędzie i zapisuje je do pliku
dziennika błędów. Metoda taka musi być w stanie albo dopisać informacje do pliku istniejącego,
albo stworzyć nowy plik. Gdyby w czasie przetwarzania dokumentu XML wystąpiło ostrzeżenie,
także „dowiadywałaby” się o nim ta metoda. Ostrzeżenie miałoby na celu wywołanie odpowie-
dniej procedury i nie przerywałoby przetwarzania. Jednakże, jeśli procedura obsługi błędów nie
byłaby w stanie zapisywać do pliku dziennika, musiałaby zakomunikować parserowi i aplikacji, że
przetwarzanie ma zostać przerwane. Efekt taki można uzyskać poprzez przechwycenie wszystkich
wyjątków wejścia-wyjścia i ponowne zgłoszenie ich aplikacji wywołującej, powodując przerwanie
przetwarzania. Powyższy przykład wyjaśnia, dlaczego procedury obsługi błędów muszą mieć
możliwość zgłaszania wyjątków (patrz przykład 3.3).

Przykład 3.3. Procedura obsługi, która może zgłosić SAXException

public void warning(SAXParseException exception)
throws SAXException {

try {
FileWriter fw = new FileWriter("error.log");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("Ostrzeżenie: " + exception.getMessage() + "\n");
bw.flush();
bw.close();
fw.close();
} catch (Exception e) {
throws new SAXException("Brak możliwości zapisu do pliku dziennika",

e);

}

}

Teraz można już zdefiniować szkielet procedury obsługi błędów i zarejestrować ją w parserze
w taki sam sposób, jak w przypadku procedury obsługi zawartości. Najpierw trzeba zaimportować
klasę

SAXParseException

i

ErrorHandler

:

import java.io.IOException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;

import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;

background image

78

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 78

Teraz w tym samym pliku z programem w Javie (znów na dole, po klasie

MyContentHandler

)

należy zaimplementować interfejs

ErrorHandler

, zdefiniowany w ramach SAX-a. Podobnie jak

przy omawianiu klasy

ContentHandler

, poniżej są przedstawione puste implementacje, które

wkrótce zostaną wypełnione treścią:

/**
* <b><code>MyErrorHandler</code></b> implementuje interfejs SAX
* <code>ErrorHandler</code> i definiuje zachowanie wywołań
* wstecznych powiązanych z błędami w XML-u.
*/
class MyErrorHandler implements ErrorHandler {

/**
* <p>
* Powiadomienie o ostrzeżeniu; żadne zasady XML nie zostały "złamane",
* ale wydaje się, że czegoś brakuje lub coś jest wpisane niepoprawnie.
* </p>
*
* @param exception <code>SAXParseException</code> -- wyjątek, jaki

nastąpił.

* @throws <code>SAXException</code> gdy coś pójdzie nie tak.
*/
public void warning(SAXParseException exception)
throws SAXException {
}

/**
* <p>
* Tutaj komunikujemy o błędzie, jeśli taki się pojawi; błąd oznacza, że
* złamano regułę i zazwyczaj pojawia się w czasie sprawdzania składni;
* przetwarzanie może jeszcze być kontynuowane.
* </p>
*
* @param exception <code>SAXParseException</code> -- wyjątek, który

nastąpił.

* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void error(SAXParseException exception)
throws SAXException {
}

/**
* <p>
* Tutaj komunikujemy, że nastąpił błąd krytyczny; oznacza to, że
* złamano regułę w ten sposób, że dalsze przetwarzanie
* nie jest możliwe albo jest bezcelowe.
* </p>
*
* @param exception <code>SAXParseException</code> -- wyjątek, który

nastąpił.

* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void fatalError(SAXParseException exception)
throws SAXException {
}

}

Aby procedura obsługi błędów mogła zostać użyta, należy ją zarejestrować w parserze SAX. Służy
do tego metoda

setErrorHandler()

interfejsu

XMLReader

w naszej przykładowej metodzie

background image

Procedury obsługi błędów

79

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 79

demo()

. Metoda

setErrorHandler()

pobiera jako jedyny parametr interfejs

ErrorHand-

ler

lub jego implementację:

// Stwórz egzemplarze procedur obsługi
ContentHandler contentHandler = new MyContentHandler();

ErrorHandler errorHandler = new MyErrorHandler();

try {
// Stwórz egzemplarz parsera
XMLReader parser =
new SAXParser();

// Zarejestruj procedurę obsługi zawartości
parser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów
parser.setErrorHandler(errorHandler);

// Przetwórz dokument
parser.parse(uri);

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

...

A teraz spróbujmy sprawić, żeby na ekranie pojawiały się komunikaty w przypadku wywołania
tych metod.

Ostrzeżenia

Za każdym razem, gdy pojawi się ostrzeżenie (wynikające ze specyfikacji XML 1.0), metoda ta wy-
woływana jest w zarejestrowanej procedurze obsługi błędów. Są różne sytuacje powodujące wygene-
rowanie ostrzeżenia; wszystkie związane są z definicjami DTD i poprawnością składniową dokumentu.
Zostaną one omówione w następnych dwóch rozdziałach. Tymczasem zdefiniujemy prostą metodę wy-
świetlającą numer wiersza, identyfikator URI i komunikat w przypadku wystąpienia ostrzeżenia.
Ponieważ chcemy, aby w razie ostrzeżenia przetwarzanie zostało przerwane, zgłaszamy wyjątek
SAXException i pozwalamy aplikacji nadrzędnej elegancko zakończyć działanie i zwolnić zasoby:

/**
* <p>
* Powiadomienie o ostrzeżeniu; żadne zasady języka XML nie zostały złamane,
* ale wydaje się, że czegoś brakuje lub coś jest wpisane niepoprawnie.
* </p>
*
* @param exception <code>SAXParseException</code> -- wyjątek, jaki nastąpił.
* @throws <code>SAXException</code> gdy coś pójdzie nie tak.
*/
public void warning(SAXParseException exception)
throws SAXException {

System.out.println("**Przetwarzanie ostrzeżenia**\n" +
" Wiersz: " +
exception.getLineNumber() + "\n" +
" URI: " +
exception.getSystemId() + "\n" +

background image

80

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 80

" Komunikat: " +
exception.getMessage());
throw new SAXException("Napotkano ostrzeżenie");
}

Błędy niekrytyczne

Błędy występujące w czasie przetwarzania, które można naprawić, ale które stanowią pogwałcenie
fragmentu specyfikacji XML, postrzegane są jako błędy niekrytyczne. Procedura obsługi błędów
powinna je przynajmniej odnotować w pliku dziennika, bo zazwyczaj są na tyle poważne, że za-
sługują na zwrócenie uwagi użytkownika lub administratora aplikacji; jednak nie są aż tak istotne,
by przerwać jej działanie. Tak jak w przypadku ostrzeżeń, większość błędów niekrytycznych
związanych jest z poprawnością dokumentu, która zostanie omówiona w odpowiednich rozdzia-
łach. Podobnie jak w przypadku ostrzeżeń, nasza prosta procedura obsługi błędów wyświetli infor-
macje przesłane do wywołania wstecznego i zakończy proces przetwarzania:

/**
* <p>
* Tutaj komunikujemy o błędzie, jeśli taki się pojawi; błąd oznacza, że
* złamano regułę i zazwyczaj pojawia się w czasie sprawdzania składni;
* przetwarzanie może jeszcze być kontynuowane.
* </p>
*
* @param exception <code>SAXParseException</code> -- wyjątek, który

nastąpił.

* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void error(SAXParseException exception)
throws SAXException {

System.out.println("**Przetwarzanie błędu**\n" +
" Wiersz: " +
exception.getLineNumber() + "\n" +
" URI: " +
exception.getSystemId() + "\n" +
" Komunikat: " +
exception.getMessage());
throw new SAXException("Napotkano błąd");
}

Błędy krytyczne

Błędy krytyczne to te, które wymuszają zatrzymanie działania parsera. Zazwyczaj wynikają z niepo-
prawnego sformatowania dokumentu i ich pojawienie się oznacza, że albo dalsze przetwarzanie nie ma
sensu, albo jest technicznie niemożliwe. Procedura obsługi błędu powinna niemal zawsze poinformo-
wać użytkownika lub administratora o wystąpieniu takiego błędu; pozostawienie aplikacji samej sobie
może grozić całkowitym jej zawieszeniem. W naszym przykładzie będziemy emulowali zachowanie
dwóch pozostałych metod i zatrzymamy przetwarzanie, wyświetlając komunikat o błędzie na ekranie:

/**
* <p>
* Tutaj komunikujemy, że nastąpił błąd krytyczny; oznacza to, że
* złamano regułę w ten sposób, że dalsze przetwarzanie
* nie jest możliwe albo jest bezcelowe.
* </p>
*

background image

Procedury obsługi błędów

81

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 81

* @param exception <code>SAXParseException</code> -- wyjątek, który

nastąpił.

* @throws <code>SAXException</code> jeśli coś pójdzie nie tak.
*/
public void fatalError(SAXParseException exception)
throws SAXException {

System.out.println("**Przetwarzanie błędu krytycznego**\n" +
" Wiersz: " +
exception.getLineNumber() + "\n" +
" URI: " +
exception.getSystemId() + "\n" +
" Komunikat: " +
exception.getMessage());
throw new SAXException("Napotkano błąd krytyczny");
}

Po zakodowaniu procedury obsługi tego trzeciego typu błędu powinno być możliwe przekompilo-
wanie pliku źródłowego i powtórne uruchomienie go na naszym dokumencie XML. Uzyskany
wynik nie powinien różnić się od poprzedniego, bo dokument XML nie zawiera błędów, o których
aplikacja mogłaby poinformować. W kolejnych podrozdziałach zostaną omówione błędy w doku-
mentach XML, w których nie jest wykonywane sprawdzanie poprawności.

Rozbijanie danych

Skoro posiadamy już procedury obsługi błędów, to warto zobaczyć, jak one działają. Jak już to by-
ło wspomniane, większość ostrzeżeń i błędów niekrytycznych związanych jest z poprawnością
składni dokumentu — zagadnienia te zostaną omówione w kilku następnych rozdziałach. Jest je-
dnak jeden błąd niekrytyczny, który pojawia się w dokumentach XML nie sprawdzonych pod
względem poprawności. Chodzi tutaj o wersję języka XML zgłaszaną przez dokument. Aby zoba-
czyć, jak ten błąd jest zgłaszany, zmieńmy nasz przykładowy dokument w następujący sposób:

<?xml version="1.2"?>

<!-- To na razie nie jest potrzebne.
<?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"?>
<!DOCTYPE JavaXML:Ksiazka SYSTEM "DTD\JavaXML.dtd">
-->

Teraz należy uruchomić przykładowy program na takim zmienionym pliku XML. Wynik powi-
nien być zbliżony do przedstawionego w przykładzie 3.4.

Przykład 3.4. Program SAXParserDemo wyświetlający błąd

D:\prod\JavaXML>java SAXParserDemo D:\prod\JavaXML\contents.xml
Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana
Rozpoczyna się przetwarzanie...
**Przetwarzanie błędu**
Wiersz: 1
URI: file:/D:/prod/JavaXML/contents/contents.xml
Komunikat: XML version "1.2" is not supported.

Błąd w przetwarzaniu: Napotkano błąd

background image

82

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 82

Kiedy parser XML operuje na dokumencie, którego wersja języka XML została określona jako
nowsza niż te obsługiwane przez parser, zgłaszany jest błąd niekrytyczny, zgodnie ze specyfikacją
XML 1.0. W ten sposób aplikacja zostaje poinformowana o tym, że nowe funkcje, które mogły
zostać użyte w dokumencie, nie muszą być rozpoznawane przez parser w tej wersji. Przetwarzanie
może być kontynuowane, a więc taki błąd nie jest postrzegany jako krytyczny. Jednakże może on
mieć istotny wpływ na dokument (np. nowa składnia może powodować dalsze błędy), a więc ma
wyższą rangę niż ostrzeżenie. To dlatego wywoływana jest metoda

error()

, powodująca wysła-

nie komunikatu informującego o błędzie i zatrzymanie przetwarzania w naszym przykładowym
programie.

Wszystkie inne istotne ostrzeżenia będą omawiane w następnych dwóch rozdziałach; wciąż istnie-
ją jednak błędy krytyczne, które mogą się pojawiać w dokumencie nie sprawdzonym pod kątem
poprawności. Są one związane z niepoprawnym formatowaniem dokumentu. Parsery XML nie po-
trafią naprawiać takich dokumentów, a więc błąd w składni powoduje zatrzymanie procesu prze-
twarzania. Najprostszym sposobem demonstracji tego zagadnienia jest wprowadzenie błędów do
dokumentu XML. Ponownie zmieńmy deklarację wersji na 1.0 i wprowadźmy następujące zmiany
w naszym dokumencie:

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

<!-- Tego jeszcze nie potrzebujemy.
<?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"?>
<!DOCTYPE JavaXML:Ksiazka SYSTEM "DTD\JavaXML.dtd">
-->

<!-- Java i XML -->
<JavaXML:Ksiazka xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/">

</JavaXML:Tytul>Java i XML</JavaXML:Tytul>
<!-- Zwróćmy uwagę na niepoprawnie wstawiony ukośnik przed elementem

JavaXML:Tytul -->

Taki dokument nie jest poprawnie sformatowany. Uruchamiamy program

SAXParserDemo

na

tak zmodyfikowanym pliku. Wynik przestawiony jest w przykładzie 3.5.

Przykład 3.5. Program SAXParserDemo wyświetlający błąd krytyczny

D:\prod\JavaXML>java SAXParserDemo D:\prod\JavaXML\contents.xml
Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana
Rozpoczyna się przetwarzanie...
startElement: Ksiazka w przestrzeni nazw
http://www.oreilly.com/catalog/javaxml/ (JavaXML:Ksiazka)
znaki:

**Przetwarzanie błędu krytycznego**
Wiersz: 13
URI: file:/D:/prod/JavaXML/contents/contents.xml
Komunikat: The element type "JavaXML:Ksiazka" must be terminated by the

matching end-tag "</JavaXML:Ksiazka>".

Parser zgłasza niepoprawne zakończenie elementu

JavaXML:Ksiazka

. Aby zrozumieć komu-

nikat o błędzie, należy zdać sobie sprawę z faktu, że parser „widzi” znak ukośnika przed ele-
mentem

JavaXML:Tytul

i zakłada, że element, który musi zostać zamknięty, to

JavaXML:

background image

Lepszy sposób ładowania parsera

83

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 83

Ksiazka

— czyli ten, który jest obecnie „otwarty”. Kiedy znajduje znacznik zamykający ele-

mentu

JavaXML:Tytul

, zgłasza niepoprawność tego znacznika jako zamknięcia otwartego ele-

mentu

JavaXML:Ksiazka

.

Uważna analiza omawianej procedury obsługi błędów pozwala rozumieć, na jakie problemy moż-
na natknąć się w czasie przetwarzania i jak można ich unikać. W rozdziale 5. ponownie zajmiemy
się procedurami obsługi błędów i przyjrzymy się problemom zgłaszanym przez parser wykonujący
sprawdzanie poprawności składni.

Lepszy sposób ładowania parsera

Choć potrafimy już przetwarzać dokumenty XML z wykorzystaniem interfejsu SAX, w naszym
kodzie wciąż znajduje się pewien ewidentny błąd. Przeanalizujmy powtórnie proces tworzenia
egzemplarza klasy

XMLReader

:

try {
// Stwórz egzemplarz parsera
XMLReader parser =
new SAXParser();

// Zarejestruj procedurę obsługi zawartości
parser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów
parser.setErrorHandler(errorHandler);

// Przetwórz dokument
parser.parse(uri);

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

Czy powyższy fragment nie budzi żadnych wątpliwości? Wskazówką może być jeszcze inny
wiersz naszego kodu:

// Tutaj importujemy implementację czytnika XML (XML Reader)
import org.apache.xerces.parsers.SAXParser;

Jawnie importujemy implementację klasy

XMLReader

danego producenta, a następnie bezpośre-

dnio tworzymy egzemplarz tej implementacji. Problem nie wynika z trudności tego zadania, ale
z faktu, że gubimy jedną z najważniejszych cech Javy — przenośność. Takiego kodu nie da się
uruchomić, a nawet skompilować na platformie nie wykorzystującej parsera Apache Xerces. A prze-
cież możliwe jest nawet, że w zaktualizowanej wersji Xerces nazwa używanej klasy może ulec
zmianie! A zatem nasz kod nie jest przenośny.

W związku z tym zaleca się stworzenie egzemplarza klasy o nazwie klasy pobranej z implemen-
tacji. W ten sposób w kodzie źródłowym możliwe jest zastosowanie rozwiązania polegającego na
zmianie prostego parametru

String

. Wszystko to jest możliwe w interfejsie SAX 2.0, a klasa,

która udostępni potrzebną nam metodę, nosi nazwę

org.xml.sax.helpers.XMLReader-

Factory

:

/**
* Próba utworzenia egzemplarza czytnika XML z nazwy klasy.

background image

84

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 84

*
* <p>Po otrzymaniu nazwy klasy metoda ta usiłuje załadować
* i stworzyć egzemplarz klasy jako czytnika XML.</p>
*
* @return Nowy czytnik XML.
* @exception org.xml.sax.SAXException Jeśli klasa nie może zostać
* załadowana; nie można stworzyć egzemplarza lub
* wykonać rzutowania na XMLReader.
* @see #createXMLReader()
*/
public static XMLReader createXMLReader (String className)
throws SAXException {
// Implementacja
}

Metody tej w naszym kodzie użyjemy następująco:

try {
// Stwórz egzemplarz parsera

XMLReader parser =
XMLReaderFactory.createXMLReader(
"org.apache.xerces.parsers.SAXParser");

// Zarejestruj procedurę obsługi zawartości
parser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów
parser.setErrorHandler(errorHandler);

// Przetwórz dokument
parser.parse(uri);

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

Ta statyczna metoda pobiera nazwę klasy parsera do załadowania, a zwraca wersję tej klasy w postaci
egzemplarza rzutowanego na interfejs

XMLReader

(zakładając, że faktycznie implementowany

jest

XMLReader

). Ewentualne problemy są zgłaszane do programu wywołującego za pośredni-

ctwem

SAXException

. Teraz wystarczy dodać jedną instrukcję importującą, usunąć odwołanie

do parsera konkretnego producenta, wprowadzić powyższe zmiany i już można przekompilować
nasz kod źródłowy:

import java.io.IOException;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;

import org.xml.sax.helpers.XMLReaderFactory;

// To usuwamy
// import org.apache.xerces.parsers.SAXParser;

No i znów mamy przenośny kod! Aby jeszcze bardziej rozszerzyć przenośność, warto przechowy-
wać nazwę klasy parsera w pliku właściwości. Umożliwia to ładowanie klasy „na gorąco”, a kod

background image

Uwaga! Pułapka!

85

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 85

można przenosić pomiędzy platformami bez konieczności rekompilacji — zmianie ulega jedynie
plik właściwości. Tak przebiega proces odczytywania plików właściwości w programie:

try {
// Stwórz egzemplarz parsera

XMLReader parser =
XMLReaderFactory.createXMLReader(
PropertiesReader().getInstance()
.getProperty("parserClass"));

// Zarejestruj procedurę obsługi zawartości
parser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów
parser.setErrorHandler(errorHandler);

// Przetwórz dokument
parser.parse(uri);

} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

Klasa narzędziowa

PropertiesReader

posłużyła tutaj do odczytania pliku właściwości i zwró-

cenia wartości odpowiadającej kluczowi

parserClass

(wartość ta będzie zawierała nazwę klasy

parsera, którą należy wykorzystać dla danej platformy. W naszych przykładach byłby to łańcuch

org.apache.xerces.SAXParser

. Oczywiście, można byłoby także skorzystać z systemo-

wych właściwości Javy, ale nie są one odpowiednie dla rozproszonych aplikacji WWW przedsta-
wionych w tej książce (obsługiwane są z poziomu wiersza poleceń). Często aplikacje rozproszone
uruchamiane są jako całość, a nie indywidualnie, a więc określenie właściwości systemowych dla
jednego tylko komponentu byłoby trudne.

Uwaga! Pułapka!

Programista w trakcie pracy może napotkać wiele pułapek. Czytelnik powinien wiedzieć, jak uni-
kać typowych błędów programistycznych związanych z używaniem interfejsu SAX; w dalszych
rozdziałach zostaną także omówione pułapki związane z innymi interfejsami API.

Mój parser nie obsługuje interfejsu SAX 2.0. Co robić?

Nie załamywać się. Przede wszystkim zawsze można zmienić parser na inny — utrzymanie zgod-
ności z najnowszą wersją SAX należy do obowiązku producenta parsera. Jeśli producent nie na-
dąża za standardami, to znaczy, że w naszym parserze mogą też istnieć inne błędy. Istnieją jednak
przypadki, kiedy zmuszeni jesteśmy korzystać z danego parsera (np. z powodu konieczności ob-
sługi zastanego kodu lub aplikacji). Jednak nawet w takich sytuacjach nie wszystko jest stracone.

SAX 2.0 wyposażony jest w klasę pomocniczą

org.xml.sax.helpers.ParserAdapter

,

która powoduje, że implementacja klasy

Parser

w SAX 1.0 zachowuje się jak implementacja

XMLReader

w interfejsie SAX 2.0. To poręczne narzędzie pobiera implementację

Parser

z wersji 1.0 jako parametr wejściowy i dalej może być używane zamiast tej implementacji. Umoż-
liwia ustawienie

ContentHandler

i poprawnie obsługuje wszystkie wywołania związane z prze-

strzenią nazw. Jedyna funkcja, do jakiej nie będziemy mieli dostępu, to obsługa pomijania encji

background image

86

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 86

(nie była ona po prostu dostępna w implementacji 1.0 w jakiejkolwiek formie, więc nie może być
emulowana). Sposób użycia tej klasy przedstawiony jest w przykładzie 3.6.

Przykład 3.6. Korzystanie z klasy Parser z SAX 1.0 jako klasy XMLReader z 2.0.

try {
// Zarejestruj parser w SAX
Parser parser =
ParserFactory.makeParser(
"org.apache.xerces.parsers.SAXParser");

ParserAdapter myParser = new ParserAdapter(parser);

// Zarejestruj procedurę obsługi zawartości
myParser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów
myParser.setErrorHandler(errorHandler);

// Przetwórz dokument
myParser.parse(uri);

} catch (ClassNotFoundException e) {
System.out.println(
"Nie znaleziono klasy parsera.");
} catch (IllegalAccessException e) {
System.out.println(
"Niewystarczające przywileje do załadowania klasy parsera.");
} catch (InstantiationException e) {
System.out.println(
"Niemożliwe utworzenie egzemplarza klasy parsera.");
} catch (ClassCastException e) {
System.out.println(
"Parser nie ma zaimplementowanego org.xml.sax.Parser");
} catch (IOException e) {
System.out.println("Błąd przy wczytywaniu URI: " +

e.getMessage());

} catch (SAXException e) {
System.out.println("Błąd w przetwarzaniu: " + e.getMessage());
}

Jeśli Czytelnik dopiero zaczyna poznawać interfejs SAX i ma kłopoty ze zrozumieniem tego przy-
kładu, nie powinien się martwić — w przypadku korzystania z najnowszej i najlepszej wersji SAX
(2.0) prawdopodobnie nigdy nie będzie trzeba używać kodu podobnego do powyższego. Przydatny
jest on tylko tam, gdzie konieczne jest korzystanie z parsera 1.0.

SAX XMLReader — wielokrotne użycie a współbieżność

Jedną z najciekawszych cech Javy jest fakt, że w niezwykle prosty sposób można wielokrotnie
używać tych samych obiektów. Cechę tę posiadają również parsery SAX. Po utworzeniu egzem-
plarza

XMLReader

możliwe jest ciągłe jego używanie i przekazywanie mu wielu (nawet setek)

dokumentów. Kolejne dokumenty lub źródła

InputSources

przekazywane są parserowi, zatem

może być on wykorzystany do różnych zadań. Parsery nie są jednak współbieżne. Kiedy już roz-
począł się proces przetwarzania, parsera nie będziemy w stanie wykorzystać ponownie dopóty,
dopóki to pierwsze przetwarzanie nie zostanie zakończone. Dla tych z Czytelników, którzy lubią
korzystać z algorytmów rekurencyjnych, to właśnie może stanowić pułapkę. Kiedy spróbujemy
użyć parsera wtedy, gdy ten jest akurat w trakcie przetwarzania innego dokumentu, zgłoszony zo-
stanie raczej niemiły

SAXException

i całe przetwarzanie zostanie zatrzymane. Jaki z tego wy-

background image

Uwaga! Pułapka!

87

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 87

nika wniosek? Dokumenty należy przetwarzać jeden po drugim, albo — godząc się na wszelkie
tego skutki — tworzyć więcej egzemplarzy parsera.

Locator w złym miejscu

Kolejną niebezpieczną (a z pozoru niewinną) cechą zdarzeń SAX jest fakt udostępniania egzem-
plarza

Locator

poprzez wywołanie metody

setDocumentLocator()

. W ten sposób aplika-

cja poznaje źródło zdarzenia SAX, co umożliwia podjęcie decyzji o dalszym przetwarzaniu oraz
sposobie reagowania na inne zdarzenia. Jednakże to miejsce jest określone poprawnie jedynie na
czas istnienia egzemplarza

ContentHandler

. Po ukończeniu przetwarzania

Locator

nie jest

już poprawny (szczególnie wtedy, gdy rozpoczyna się następne przetwarzanie). Błędem popełnia-
nym przez nowicjuszy jest przechowywanie referencji do obiektu

Locator

wewnątrz zmiennej

należącej do klasy spoza wywołania wstecznego:

public void setDocumentLocator(Locator locator) {
// Zachowanie Locator w klasie poza ContentHandler
mojaInnaKlasa.setLocator(locator);
}

...

public metodaInnejKlasy() {
// Próba użycia poza ContentHandler
System.out.println(locator.getLineNumber());
}

To bardzo zły pomysł —

Locator

traci znaczenie, bo jesteśmy już poza implementacją

Con-

tentHandler

. Często korzystanie z takiej zmiennej powoduje nie tylko otrzymywanie przez

aplikację nieprawidłowych informacji, ale również uszkodzenie dokumentu XML. Innymi słowy,
obiektu tego należy używać lokalnie, a nie globalnie. W naszej implementacji

Content-

Handler

otrzymany

Locator

zachowaliśmy do zmiennej. Następnie moglibyśmy jej użyć w po-

prawny sposób (np. do podania numerów wierszy, w których napotkaliśmy poszczególne elementy):

public void startElement(String namespaceURI, String localName,
String rawName, Attributes atts)
throws SAXException {

System.out.print("startElement: " + localName +
" w wierszu " + locator.getLineNumber());

if (!namespaceURI.equals("")) {
System.out.println(" w przestrzeni nazw " + namespaceURI +
" (" + rawName + ")");
} else {
System.out.println(" nie posiada skojarzonej przestrzeni nazw");
}

for (int i=0; i<atts.getLength(); i++)
System.out.println(" Atrybut: " + atts.getLocalName(i) +
"=" + atts.getValue(i));
}

Wyprzedzanie danych

Wywołanie

characters()

przyjmuje tablicę znaków oraz parametry

start

i

end

, które

wskazują, gdzie należy rozpocząć, a gdzie skończyć odczytywanie z tej tablicy. Może to prowa-
dzić do pomyłek — typowym błędem jest odczytywanie znaków z tablicy w następujący sposób:

background image

88

Rozdział 3. Przetwarzanie kodu XML

C:\WINDOWS\Pulpit\Szymon\Java i XML\03-08.doc — strona 88

public void characters(char[] ch, int start, int end)
throws SAXException {

for (int i=0; i<ch.length; i++)
System.out.println(i);

}

Błąd polega na odczytaniu tablicy od początku do końca. To naturalne zachowanie programisty,
wynikające z przyzwyczajeń programowania w Javie, C czy innym języku, gdzie iteracje po pęt-
lach są często spotykane. Jednak w przypadku zdarzenia SAX takie postępowanie może powo-
dować poważny błąd. Parsery SAX przekazują początek i koniec tablicy, z której należy korzystać
we wszystkich pętlach czytających z tej tablicy. W ten sposób dane tekstowe przetwarzane są na
niższym poziomie, co umożliwia optymalizację pracy parsera (np. odczytywanie danych z wyprze-
dzeniem oraz wielokrotne wykorzystanie tej samej tablicy). Wszystko to jest poprawnym działa-
niem w interfejsie SAX, ponieważ zakłada się, że aplikacja nie będzie próbowała „wybiegać
przed” parametr

end

przekazany do wywołania.

Tego typu błąd powoduje wyświetlenie na ekranie (lub przetworzenie przez aplikację) przypadko-
wych danych. Pętla wygląda zupełnie zwyczajnie i kompiluje się „bez zająknięcia”, a więc jest to
prawdziwa pułapka, na którą musi już uważać sam programista.

Co dalej?

Teraz Czytelnik powinien już dobrze rozumieć zasady działania interfejsów SAX oraz sposób,
w jaki współdziałają z parserem XML i procesem przetwarzania — wszystko w kontekście parsera
nie sprawdzającego poprawności dokumentu XML. Interfejsy te mają kluczowe znaczenie dla reszty
zagadnień opisywanych w tej książce — w następnych rozdziałach zostaną poszerzone wiadomości
o interfejsie SAX, a w programie zastosowane będą dodatkowe klasy tego interfejsu. W następ-
nym rozdziale omówione zostaną zagadnienia związane z zawężaniem XML-a, procesem spraw-
dzania poprawności dokumentu, a także przedstawione zostaną definicje DTD i schematy.


Wyszukiwarka

Podobne podstrony:
08 2HBZ25RJBN6D4ITZUMZK4SZDSTJD Nieznany
Harmonogram ćwiczeń s5 2014 TABL 03 (08 10 14 )
Konserwacja 2014 03 id 245321 Nieznany
11 03 08 sem IVid 12319
2011 03 08
2013 03 08
egzamin 2006 03 08
03 Kinematykaid 4394 Nieznany
713[05] Z1 03 Wykonywanie izola Nieznany (2)
03 5id 4121 Nieznany
ais 03 id 53431 Nieznany (2)
712[06] S1 03 Montowanie system Nieznany (2)
03 4id 4118 Nieznany (2)
2001 03 08
Chemia 03 id 557778 Nieznany
03 08 wymagania dlaprocesu termicznego przekształcania o
2014 Matura 01 03 2014id 28469 Nieznany (2)
Biul Moni Przyr 1(4)03 Aves id Nieznany

więcej podobnych podstron