05-08, Programowanie, ! Java, Java i XML


5

Sprawdzanie poprawności składni XML-a

Po lekturze poprzednich rozdziałów Czytelnik potrafi już stworzyć dokument XML, przetworzyć go za pomocą klas SAX oraz zawęzić. W tym rozdziale zostanie omówione kolejne zagadnienie — spraw­dzanie poprawności dokumentu XML za pomocą Javy. Bez takiej możliwości tworzenie aplikacji firma-firma oraz komunikacji międzyaplikacyjnej staje się o wiele trudniejsze. Zawężenie zwię­ksza przenośność danych; natomiast sprawdzanie poprawności — spójność. Innymi słowy, moż­liwość zawężenia dokumentu nie zda się na wiele, jeśli stworzonych zawężeń nie przeforsujemy w aplikacji XML.

W tym rozdziale przedstawione zostaną klasy i interfejsy SAX służące do sprawdzania po­praw­ności dokumentów XML względem ich zawężeń. Czytelnik dowie się, jak ustawić cechy i właś­ci­wości parsera zgodnego z SAX, aby możliwe było proste sprawdzanie poprawności, obsługa prze­strzeni nazw i wykonywanie innych czynności. Ponadto szczegółowo omówione zostaną błędy i ostrze­że­nia zgłaszane przez parsery sprawdzające poprawność.

Konfiguracja parsera

W obliczu bogactwa specyfikacji i technologii autorstwa konsorcjum W3C związanych z językiem XML, dodanie obsługi nowej cechy lub właściwości nie jest proste. W wielu implementacjach par­serów dodano własne rozszerzenia lub metody kosztem przenośności kodu. W pakietach tych mógł zostać zaimplementowany interfejs XMLReader, ale metody do ustawiania sprawdzania po­praw­ności, obsługi przestrzeni nazw i innych kluczowych cech są odmienne w różnych imple­men­ta­cjach parserów. W związku z tym w interfejsie SAX 2.0 zdefiniowano standardowy mechanizm do ustawiania istotnych właściwości i cech parsera. Umożliwia on dodawanie nowych właściwości i cech w miarę przyjmowania ich przez W3C, bez konieczności stosowania własnych metod lub rozszerzeń.

Ustawianie właściwości i cech

Na szczęście dla nas, interfejs SAX 2.0 wyposażono w metody służące do ustawiania właściwości i cech interfejsu XMLReader. Oznacza to, że w celu zażądania sprawdzania poprawności, usta­wienia separatora przestrzeni nazw czy obsługi innych cech nie trzeba zmieniać zbyt wiele w istnie­jącym kodzie. Odpowiednie metody przedstawione są w tabeli 5.1.

Tabela 5.1. Metody do obsługi właściwości i cech parsera

Metoda

Zwraca

Parametry

Składnia

setProperty()

void

String IDwlasciwosci, Object wartosc

parser.setProperty(„[URI wlasciwosci]”, „[Parametr obiektu]”);

setFeature()

void

String IDcechy, boolean stan

parser.setFeature(„[URI cechy]”, true);

getProperty()

Object

String IDwlasciwosci

String separator = (Stringparser.getProperty(„[URI wlasciwosci]”);

getFeature()

boolean

String IDcechy

if (parser.getFeature(„[URI cechy]”)) {robiCos();}

W każdej z powyższych metod identyfikatorem ID określonej właściwości lub cechy jest iden­ty­fi­ka­tor URI. Lista najważniejszych właściwości i cech jest zamieszczona w dodatku B. Producent par­sera XML powinien udostępnić dodatkową dokumentację informujacą o obsługiwanych ce­chach i właś­ciwościach. Pamiętajmy jednak, że identyfikatory te, podobnie jak identyfikatory URI prze­strzeni nazw, służą wyłącznie do kojarzenia odpowiednich cech. Dobry parser umożliwi ko­rzys­ta­nie z nich bez posiadania połączenia z siecią. W tym sensie identyfikatory URI można postrzegać jako proste stałe, które akurat mają format URI. Po skorzystaniu z takiej metody następuje lokalne prze­­two­rze­nie identyfikatora, często jako stałej reprezentującej odpowiednią czynność, którą na­leży podjąć.

W kontekście konfiguracji parsera właściwość wymaga obecności obiektu, z którego można sko­rzystać. Na przykład w celu obsługi leksykalnej jako wartość właściwości można dostarczyć klasę LexicalHandler. Cecha natomiast ma postać znacznika wykorzystywanego przez parser w ce­lu określenia, czy ma nastąpić przetwarzanie określonego typu. Typowe cechy to sprawdzanie poprawności, obsługa przestrzeni nazw i dołączanie encji zewnętrznych.

Najwygodniejsza własność powyższych metod polega na tym, że umożliwiają one łatwe dodawa­nie i zmia­nę różnych cech. Nowe lub zaktualizowane właściwości wymagają obsługi ze strony par­se­ra, ale dostęp do nich uzyskuje się wciąż za pomocą tych samych metod — konieczne jest tylko zde­­fi­nio­wanie nowego identyfikatora URI. Bez względu na złożoność nowych koncepcji związanych z XML, ich implementacja w parserach przebiega bezproblemowo, właśnie dzięki tym metodom.

Włączanie sprawdzania poprawności

Czytelnik dowiedział się już, jak ustawiać cechy i właściwości, ale nie poznał jeszcze samych cech i właściwości. W tym rozdziale interesujemy się przede wszystkim sprawdzaniem poprawności w czasie przetwarzania. Aby zilustrować, jak ważne są wspomniane wyżej metody, trzeba od­wo­łać się do historii. W interfejsie SAX 1.0 implementacje parserów musiały udostępniać własne roz­wiązania do obsługi przetwarzania ze sprawdzaniem poprawności lub bez. Nie było możliwości włączenia lub wyłączenia sprawdzania poprawności w sposób standardowy, więc najprostszy spo­sób polegał na dostarczeniu dwóch niezależnych klas przetwarzających. Na przykład, aby wyko­nać przetwarzanie bez sprawdzania poprawności we wczesnych wersjach parsera Project X firmy Sun, trzeba było skorzystać z przedstawionego poniżej fragmentu kodu.

Przykład 5.1. Uruchamianie parsera nie sprawdzającego poprawności w interfejsie SAX 1.0

try {

// Rejestrujemy parser w SAX

Parser parser =

ParserFactory.makeParser(

"com.sun.xml.parser.Parser");

// Przetwarzamy dokument

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace();

}

Ponieważ nie istniał standardowy mechanizm włączania sprawdzania poprawności, trzeba było załadować inną klasę; ta nowa klasa jest niemal identyczną implementacją interfejsu Parser w SAX 1.0, ale wykonującą sprawdzanie poprawności. Kod pozwalający na użycie parsera jest niemal identyczny (przykład 5.2), różnica jest tylko w klasie załadowanej w celu przetwarzania.

Przykład 5.2. Uruchamianie parsera sprawdzającego poprawność w interfejsie SAX 1.0

try {

// Rejestrujemy parser w SAX

Parser parser =

ParserFactory.makeParser(

"com.sun.xml.parser.ValidatingParser");

// Przetwarzamy dokument

parser.parse(uri);

} catch (Exception e) {

e.printStackTrace();

}

Włączając lub wyłączając sprawdzanie poprawności trzeba więc było zmieniać i kompilować kod. Ponadto wynikał z tego dodatkowy problem z przetwarzaniem. Standardowe środowisko pro­gramistyczne wykorzystuje kod, który sprawdza poprawność danych XML wytwarzanych przez aplikację. To sprawdzanie poprawności, choć obniża wydajność, zapewnia, że aplikacja tworzy za­wsze poprawny kod XML albo że otrzymuje poprawne dokumenty XML na wejściu. Często takie za­­wę­żenia po intensywnym testowaniu można usunąć, dzięki czemu odzyskuje się wysoką wy­daj­ność działania aplikacji. Pozbawienie parsera możliwości sprawdzania poprawności jest uzasad­nio­ne, ponieważ dogłębne testy potwierdziły poprawność tworzonego dokumentu XML; ale zmia­na ta wy­­maga modyfikacji i rekompilacji kodu. Wydaje się to być sprawą banalną, ale wiele firm nie poz­wala na wdrożenie do produkcji kodu, który był modyfikowany później niż przed jakimś okre­ś­lo­nym czasem — często kilka dni, a nawet tygodni. Taka niewielka zmiana może więc spo­wo­do­wać dodatkowy cykl testowy — niejednokrotnie niepotrzebny, a dodatkowo wydłużający czas wdro­żenie aplikacji.

Ale przecież nazwę parsera można pobrać z pliku właściwości (zostało to już powiedziane w roz­dziale 2. przy okazji opisywania aspektów przenośności aplikacji XML). Tak, ale zmiana całej im­ple­mentacji parsera tuż przed wdrażaniem aplikacji do produkcji to zmiana poważna, która po­winna być należycie przetestowana. Jeśli porównamy to ze zmianą wartości zestawu cech (za­kła­da­jąc, że wartość do ustawiania tej cechy także pobierana jest z pliku właściwości), to łatwo zga­dnąć, które rozwiązanie jest lepsze.

Z tych wszystkich powodów w interfejsie SAX 2.0 do XMLReader dodano omawiane metody. Dzięki nim włączenie sprawdzania poprawności polega na wykorzystaniu odpowiedniego identy­fi­ka­tora URI: http://xml.org/sax/features/validation. Moglibyśmy zażądać również przetwarzania en­cji zewnętrznych i przestrzeni nazw, ale tymczasem „włączymy” tylko sprawdzanie popraw­ności (przykład 5.3).

Przykład 5.3. Włączanie sprawdzania poprawności

// Stwórz egzemplarze procedur obsługi

ContentHandler contentHandler = new MyContentHandler();

ErrorHandler errorHandler = new MyErrorHandler();

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);

parser.setFeature("http://xml.org/sax/features/validation",

true);

// Przetwórz dokument

parser.parse(uri);

} catch (IOException e) {

System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage());

} catch (SAXException e) {

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

}

Po tych prostych zmianach możemy już zmodyfikować nasz przykładowy plik XML tak, by znów zawierał odwołanie do definicji DTD i zewnętrzną encję (zostały one opatrzone komentarzami w po­przednim rozdziale).

<?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>

<JavaXML:Spis>

...

<!-- z encji także usuwamy komentarz -->

<JavaXML:Copyright>&OReillyCopyright;</JavaXML:Copyright>

Upewniamy się, czy w podanym tutaj katalogu mamy stworzoną wcześniej definicję DTD. Zanim uruchomimy przykład, musimy połączyć się z Internetem — pamiętajmy, że encje muszą zostać „przetłumaczone”. W naszym przykładowym pliku mamy taką encję — OReillyCopyright. W definicji DTD odwołujemy się do identyfikatora URI http://www.oreilly.com/catalog/javaxml/ docs/copyright.xml. Jeśli w czasie sprawdzania poprawności identyfikator URI nie jest dostępny, pojawią się błędy przetwarzania. Jeśli nie mamy dostępu do Internetu albo nie chcemy z niego ko­rzystać, możemy odwołanie sieciowe zastąpić odwołaniem do pliku lokalnego. Na przykład two­rzymy jednowierszowy plik jak w przykładzie 5.4.

Przykład 5.4. Lokalny plik z opisem praw autorskich

Przykładowy współdzielony plik z opisem praw autorskich.

Plik ten zachowujemy w katalogu dostępnym z poziomu programu parsera i zamieniamy dekla­ra­cję encji w DTD na ścieżkę do tego pliku:

<!ENTITY OReillyCopyright SYSTEM

"encje/copyright.txt">

W tym przykładzie plik tekstowy zachowany jest pod nazwą copyright.txt w katalogu encje/. Po takiej zmianie możemy już uruchomić nasz program na przykładowym pliku XML.

Wynik sprawdzania poprawności

Sprawdzamy, czy pliki XML, DTD, plik z prawami autorskimi (o ile taki stworzyliśmy) oraz od­po­wiednie klasy Javy znajdują się w jednym miejscu, i uruchamiamy przykładowy program. Wy­nik może być zaskakujący (przykład 5.5).

Przykład 5.5. Wynik działania programu SAXParserDemo

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

Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana

Rozpoczyna się przetwarzanie...

**Przetwarzanie błędu**

Wiersz: 11

URI: file:/D:/prod/JavaXML/contents/contents.xml

Komunikat: Document root element "JavaXML:Ksiazka", must match DOCTYPE

root "JavaXML:Ksiazka".

Ten dość enigmatyczny komunikat wynika z istnienia poważnego problemu związanego z używa­niem definicji DTD i przestrzeni nazw. Z informacji wynikałoby, że element główny określony w deklaracji DOCTYPE (JavaXML:Ksiazka) jest różny od elementu głównego samego doku­men­tu. Ale przecież elementy te są identyczne, prawda? Otóż nie! Domyślnie SAX 2.0 wymaga od parserów włączenia przetwarzania przestrzeni nazw, chyba że jawnie wyłączymy tę ce­chę. My ustawienia domyślnego nie zmienialiśmy, a więc nasza implementacja interfejsu XMLReader jest „świadoma” przestrzeni nazw. Nieoczekiwany rezultat wynika stąd, że nasz ele­ment główny jest postrzegany (przez parser) jako Ksiazka z przedrostkiem przestrzeni nazw JavaXML. Ale pamiętajmy, że XML 1.0 oraz definicje DTD nie rozróżniały pomiędzy przed­rost­kiem a nazwą elementu, a więc element główny DTD oczekuje nazwy JavaXML:Ksiazka. Kie­dy znajduje Ksiazka, zgłasza błąd.

Jedynym sposobem obejścia tej nieco uciążliwej cechy SAX-a jest wyłączenie „świadomości” przestrzeni nazw w dokumentach, w których sprawdzanie poprawności odbywa się za pomocą DTD. Dodajemy następujący fragment kodu:

try {

// Stwórz egzemplarz parsera

XMLReader parser =

XMLReaderFactory.createXMLReader(

"org.apache.xerces.parsers.SAXParser");

// Zarejestruj procedurę obsługi zawartości

parser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów

parser.setErrorHandler(errorHandler);

// Włącz sprawdzanie poprawności

parser.setFeature("http://xml.org/sax/features/validation",

true);

// Wyłącz "świadomość" przestrzeni nazw

parser.setFeature("http://xml.org/sax/features/namespaces",

false);

// Przetwórz dokument

parser.parse(uri);

} catch (IOException e) {

System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage());

} catch (SAXException e) {

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

}

Po tej zmianie wszystkie nazwy traktowane są jako zawierające zarówno przedrostek przestrzeni nazw, jak i nazwę lokalną elementu. Do korzystania z przestrzeni nazw powrócimy w dalszej częś­ci rozdziału. Tymczasem spróbujmy ponownie uruchomić program z zaimplementowanymi zmia­na­mi. Powinniśmy uzyskać wynik widoczny w przykładzie 5.6.

Przykład 5.6. Wynik działania programu SAXParserDemo po wyłączeniu obsługi przestrzeni nazw

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

Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana

Rozpoczyna się przetwarzanie...

startElement: nie posiada skojarzonej przestrzeni nazw

Atrybut: =http://www.oreilly.com/catalog/javaxml/

ignorableWhitespace: [

]

startElement: nie posiada skojarzonej przestrzeni nazw

znaki: Java i XML

endElement:

...

Wynik może nas nieco rozczarować. Nie różni się niczym od wyniku uzyskanego w przypadku par­sera nie sprawdzającego poprawności (zob. rozdział 3.)! To dlatego, że nasz dokument jest po­prawny i poprawnie sformatowany — parser sprawdzający poprawność nie ma o czym komuni­ko­wać. To ważne — zachowanie parsera w przypadku sprawdzania poprawnego dokumentu niemal nie różni się od zachowania parsera nie sprawdzającego poprawności. Jeśli to wydaje się dziwne, to należy przypomnieć, że sprawdzanie poprawności ma na celu zagwarantowanie, że dokument nie łamie zdefiniowanych wcześniej zasad. Jeśli wszystkie reguły są przestrzegane, aplikacja użyje dokumentu XML zgodnie z jego przeznaczeniem. Tylko kiedy nastąpi złamanie zasad, parser spraw­dzający poprawność wkracza do akcji — poniżej zostaną omówione takie przypadki.

Wcześniej jednak trzeba zauważyć, że mimo wszystko powyższy wynik nieco różni się od po­przednich. Wcześniej wszystkie znaki białe pomiędzy elementami w dokumencie XML zgłaszane były poprzez wywołanie metody characters() w implementacji ContentHandler. W do­kumencie XML, którego poprawność nie jest sprawdzana, parser ma możliwość zgłoszenia bia­łych znaków albo przez tę metodę, albo przez ignorableWhitespace().Wynika to z fa­ktu, że parser nie może zakładać określonego przeznaczenia obecności białych znaków pomiędzy ele­mentami bez definicji DTD zawężającej XML. Kiedy w programie włączamy sprawdzanie po­praw­ności, widzimy, że wszystkie znaki białe zgłaszane są za pomocą ignorableWhitespace(). Przy sprawdzaniu poprawności wszystkie białe znaki są ignorowane i traktowane tak, jakby ich nie było — chyba że zostanie to inaczej ujęte w definicji DTD. Dzięki temu parser potrafi określić, czy zawartość elementu XML zgodna jest z definicją DTD bez analizowania białych znaków ota­czających tę zawartość. Innymi słowy, parser potraktuje taki fragment XML:

<dokument>

<element1>

<element2>Cześć!</element2>

</element1>

</dokument>

dokładnie tak samo jak:

<dokument><element1><element2>Cześć!</element2></element1></dokument>

Drugi fragment jest co prawda mniej miły dla ludzkiego oka, ale zgodny jest z tymi samymi za­wężeniami co fragment pierwszy i powinien być identycznie traktowany w czasie sprawdzania poprawności. Brak wcięć nie może wpłynąć na działanie aplikacji korzystających z danych XML. Gdyby białe znaki tworzące wcięcia zgłaszane były poprzez wywołanie characters(), apli­ka­cja monitorująca to wywołanie uznałaby, że dokumenty nie są identyczne.

Ostrzeżenia

W wyniku żądania sprawdzania poprawności nie mogą się pojawić niemal żadne ostrzeżenia. Ja­kiekolwiek dane XML niezgodne z odpowiednią definicją DTD są postrzegane jako błąd. Nie­po­prawność dokumentu została uznana przez W3C za rzecz na tyle ważną, że zawsze zgłaszany jest wtedy błąd. Dlatego trudno jest, szczególnie za pomocą parsera SAX 2.0, wywołać ostrzeżenie. Je­dnak istnieją parsery SAX 1.0, w których jest to możliwe. Na przykład parser Project X firmy Sun zawiera obecnie klasę służącą do sprawdzania poprawności XML-a w czasie przetwarzania i klasę nie wykonującą takiego sprawdzania. Jest to rozwiązanie podobne do omawianego wcześ­niej. Jeśli parser sprawdzający poprawność przetwarza dokument nie deklarujący definicji DTD w sposób jawny, generowane jest ostrzeżenie. Dotyczy to tylko niektórych implementacji par­se­rów SAX 1.0, dlatego w niniejszej książce nie zostanie przedstawiony kod powodujący zgłoszenie ostrzeżenia. Czytelnik może natomiast obejrzeć wynik działania takiego ostrzeżenia (przykład 5.7) — podobny do tych opisywanych w rozdziale 3.

Przykład 5.7. Program SAXParserDemo zgłaszający ostrzeżenie

D:\prod\JavaXML&gt; java SAXParserDemo D:\prod\JavaXML\contents\contents.xml

Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana

Rozpoczyna się przetwarzanie...

**Przetwarzanie ostrzeżenia**

Wiersz: 6

URI: file:/D:/prod/JavaXML/contents/contents.xml

Komunikat: Valid documents must have a <!DOCTYPE declaration.

Taki komunikat pojawiłby się, gdyby konstrukcja <!DOCTYPE> była opatrzona komentarzami lub gdyby została pominięta. Ponieważ niemal wszystkie parsery XML (w tym Project X firmy Sun) ewoluują w kierunku zgodności z SAX 2.0, takiego ostrzeżenia najprawdopodobniej nigdy nie zobaczymy.

Błędy niekrytyczne

Najbardziej typowym problemem z interfejsem SAX, jaki będzie zgłaszany w czasie sprawdzania poprawności XML, jest błąd o statusie „niekrytyczny”. Błąd taki generowany jest za każdym ra­zem, gdy pogwałcone zostaną zawężenia XML. Aby to zademonstrować, wykonajmy następującą zmianę w naszym przykładowym dokumencie XML (czyniąc go niepoprawnym):

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

<!-- to jeszcze 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">

<!-- Java i XML -->

<JavaXML:Ksiazka xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"

dataPublikacji="czerwiec 2000">

<JavaXML:Tytul>Java i XML</JavaXML:Tytul>

<JavaXML:Spis>

Taka zmiana, choć nie wpływa na poprawność danych XML, nie jest zgodna z narzuconymi regu­ła­mi — atrybut dataPublikacji nie został zadeklarowany w definicji DTD i w czasie prze­twa­rzania zgłoszony zostanie błąd (przykład 5.8).

Przykład 5.8. Błąd zgłaszany przez program SAXParserDemo

D:\prod\JavaXML&gt; java SAXParserDemo D:\prod\JavaXML\contents\contents.xml

Przetwarzanie pliku XML: contents.xml

* setDocumentLocator() została wywołana

Rozpoczyna się przetwarzanie...

**Przetwarzanie błędu**

Wiersz: 14

URI: file:///mnt/teksty_zdalny/biura/helion/javaxml/cwiczenia/contents.xml

Komunikat: Attribute "dataPublikacji" must be declared for element type

"JavaXML:Ksiazka".

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

Analizator przesyła błąd do implementacji ErrorHandler, która zgłasza błąd — w tym przy­pad­ku mówiący, że atrybut nie został zadeklarowany dla zamykającego go elementu. Oczywiście, poprawność dokumentu można zburzyć na wiele sposobów — to tylko jeden z nich; zawsze je­dnak zgłoszony zostanie ten sam błąd, warto więc poeksperymentować. Należy po prostu pamię­tać, że jakiekolwiek pogwałcenie DTD spowoduje zgłoszenie błędu. Dotyczy to przypadków, w których zawartość dokumentu nie jest poprawna, elementy nie są prawidłowo zagnieżdżone, atry­buty znajdują się nie tam, gdzie trzeba, lub są niepoprawne itd.

Błędy krytyczne

Co ciekawe, dokument niezgodny z zawężeniami DTD nigdy nie spowoduje wygenerowania błę­du krytycznego. W czasie przetwarzania dokumentu nie mogą zajść takie warunki, które spo­wo­du­ją przerwanie tego procesu. Może nam się wydawać, że kontynuacja przetwarzania niepoprawnego dokumentu kłóci się z celowością sprawdzania poprawności. Należy jednak pamiętać o tym, że w większości przypadków dokumenty XML są generowane przez aplikacje. Innymi słowy, apli­ka­cja otrzymuje dane wejściowe XML z innego programu lub podprogramu. Jeśli dane te są nie­po­praw­ne, aplikacja usiłująca z nich skorzystać powinna zgłosić aplikacji klienta błąd, a nie prze­ry­wać przetwarzanie. Co więcej, przetwarzanie to niejednokrotnie powinno być konty­nuowane, aby moż­li­we było zakończenie procesu w sposób elegancki — w ten sposób aplikacja jest w stanie pre­cy­zyj­nie zakomunikować, jakie błędy wystąpiły. Dokumenty błędnie sforma­to­wa­ne spowodują zatrzy­manie prze­twarzania, a dokumenty niepoprawne wskażą, że albo nastąpił błąd, który można poprawić, albo zaszła sytuacja, o której klient powinien wiedzieć, np. wprowadzono niepoprawne dane. Zasta­nów­my się, jak trudno byłoby obsługiwać edytor XML lub środowisko programistyczne IDE, jeśli za każdym razem, gdy nie do końca przestrzegaliśmy zawężeń DTD, edytor kończyłby działanie z błę­­dem krytycznym, lub też odmawiałby w ogóle przetwarzania do­kumentu; a przecież niektóre edy­­tory mogłyby takie błędy poprawiać za nas! Dlatego właśnie nie­poprawne dokumenty po­wo­du­ją zgłaszanie ostrzeżeń i błędów, ale nigdy błędów krytycznych.

Jedyny błąd krytyczny, na jaki możemy się natknąć przy używaniu definicji DTD w dokumentach, których poprawność nie jest sprawdzana, to błąd składniowy w wykorzystywanej definicji DTD. To nie powinno Czytelnika dziwić, jako że błędy składniowe w dokumencie XML także powodują błędy krytyczne. Ma tutaj miejsce to samo rozumowanie — nie jest możliwe dalsze przetwarzanie lub sprawdzanie poprawności, jeśli nie można określić zawężeń (a tak jest, gdy składnia DTD nie jest poprawna). Powinniśmy zdawać sobie sprawę, że to nie to samo co błąd generowany, gdy ma­my do czynienia z niepoprawnie sformatowanym dokumentem XML — główna różnica polega na

tym, że definicja DTD nigdy nie jest postrzegana jako „poprawnie sformatowana”, bo przecież nie jest to tak naprawdę dokument XML. Ale rezultat wystąpienia błędów składniowych w DTD jest taki sam, jak rezultat przetwarzania niepoprawnie sformatowanych danych XML.

Interfejs DTDHandler

Ostatnia ważna procedura obsługi udostępniania przez SAX rejestruje metody wywołań wstecz­nych w czasie procesu czytania i przetwarzania definicji DTD dokumentu. W interfejsie tym nie są zdefiniowane zdarzenia, jakie występują w czasie sprawdzania poprawności, a jedynie te, które wy­stępują w czasie czytania DTD. W podrozdziale dotyczącym pułapek zostaną omówione pro­blemy wywoływane przez to zróżnicowanie. Ta procedura obsługi zachowuje się w taki sam spo­sób jak interfejsy ContentHandler i ErrorHandler, omawiane w rozdziale 3. — definiowane są dwie metody wywoływane w czasie przetwarzania.

Sprawdzanie poprawności dokumentu XML jest bardzo ważne, ale zdarzenia związane z od­czy­ty­wa­niem dokumentu DTD — już mniej. Są tylko dwie metody, obie nie są wykorzystywane tak często, a więc prawdopodobnie rzadko Czytelnik będzie musiał korzystać z interfejsu DTDHand­ler (chyba że pisząc edytor lub IDE dla XML-a będzie chciał tworzyć lub przetwarzać do­ku­men­ty DTD w celu sprawdzenia poprawności zapisu i składni). Dlatego pokrótce omówimy tutaj te dwie metody udostępniane przez SAX, ale nie będziemy poświęcali im zbyt dużo czasu. O opcjo­nalnej procedurze obsługi SAX, służącej do pobierania innych informacji z DTD, można prze­cz­y­tać w części DeclHandler, w dodatku A (pakiet org.xml.sax.ext).

Nieprzetwarzane deklaracje encji

Pierwsze wywołanie wsteczne, unparsedEntityDecl(), wywoływane jest, kiedy deklaracja encji w DTD mówi, iż encja ta ma nie być przetwarzana przez parser XML. Choć nie omawia­liś­my jeszcze przykładu takiego zachowania, nieprzetwarzane encje są dość typowe w dokumentach XML, w których istnieją odwołania do plików graficznych lub innych danych binarnych (często pli­ków multimedialnych). Metoda ta pobiera nazwę encji, identyfikatory, publiczny i systemowy, oraz nazwę notacyjną encji. Nazwy notacyjne w XML-u nie były jeszcze omawiane. Spójrzmy na przykład dokumentu XML odwołującego się do pliku graficznego (np. do logo firmy) — przykład 5.9.

Przykład 5.9. Dokument XML z nieprzetwarzaną encją

<dokument>

<mojeLogo>&LogoFirmy;</mojeLogo>

</dokument>

Podczas przetwarzania parser stara się przetłumaczyć wszystkie encje i wstawić w ich miejsce przetworzoną wartość. Jednakże parser nie potrafi przetwarzać plików graficznych i powinien pozostawić dane binarne w postaci nieprzetworzonej. Można go o tym „poinformować” poprzez następującą definicję typu dokumentu:

<!ENTITY LogoFirmy SYSTEM "obrazki/logo.gif" NDATA gif>

Słowo kluczowe NDATA powoduje, że parser XML nie przetworzy tej encji. Gdyby ta definicja DTD była przetwarzana za pośrednictwem zarejestrowanej implementacji DTDHandler, infor­ma­cja w deklaracji encji zostałaby przekazana do wywołania. Inną ważną sprawą jest tutaj fakt, że wywołanie następuje w miejscu deklaracji w DTD, a nie w chwili, gdy deklaracja ta jest prze­-

twarzana w dokumencie XML. Oznacza to, że nawet jeśli encja nie znajduje się w dokumencie XML, to i tak wywołanie nastąpi. To ma sens — przecież wywołanie to stanowi część interfejsu DTDHandler, a nie ContentHandler.

Deklaracje notacji

Deklaracje notacji zawsze skojarzone są z nieprzetwarzanymi deklaracjami i stanowią przedmiot działania drugiej metody wchodzącej w skład DTDHandler. Ostatnia część powyższej deklaracji nieprzetwarzanej encji to słowo „gif”. Słowo to określa typ nieprzetwarzanej encji i musi stanowić odwołanie do typu zdefiniowanego w innym miejscu DTD, za pośrednictwem konstrukcji NOTA­TION. W ten sposób parser poznaje identyfikator URI dla danego typu — często jest to odwołanie publiczne dla typów binarnych. Przypomina to odwoływanie się do DTD z dokumentu XML, ponieważ specyficzne dane (w tym przypadku plik GIF) są kojarzone z publicznym identy­fi­ka­to­rem lub URI. Przetwarzanie definicji NOTATION i deklaracji nieprzetwarzanej encji powoduje w rezultacie, że parser XML nie przetwarza danych binarnych. A o to właśnie chodziło.

<!NOTATION gif SYSTEM "http://www.gif.com">

Wystąpienia takich deklaracji zgłaszane są zarejestrowanej procedurze obsługi poprzez wywołanie notationDecl(). Po wywołaniu metoda ta otrzymuje deklarację notacji, identyfikator syste­mo­wy i dostępny identyfikator publiczny. Podobnie jak w przypadku nieprzetwarzanych encji, me­toda ta wywoływana jest podczas odczytywania definicji DTD, a nie samego dokumentu XML.

Rejestrowanie procedury obsługi

Rejestrowanie implementacji DTDHandler w naszym parserze XML nie różni się niczym od re­je­strowania procedur obsługi błędów i zawartości. Egzemplarz klasy przekazywany jest metodzie setDTDHandler(), a parser rejestruje odpowiednie zdarzenia związane z metodami SAX w tej klasie:

import java.io.IOException;

import org.xml.sax.Attributes;

import org.xml.sax.ContentHandler;

import org.xml.sax.DTDHandler;

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;

...

// Stwórz egzemplarze procedur obsługi

ContentHandler contentHandler = new MyContentHandler();

ErrorHandler errorHandler = new MyErrorHandler();

DTDHandler dtdHandler = new MyDTDHandler();

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);

// Zarejestruj procedurę obsługi DTD

parser.setDTDHandler(dtdHandler);

// Włącz sprawdzanie poprawności

parser.setFeature("http://xml.org/sax/features/validation",

true);

// Wyłącz "świadomość" przestrzeni nazw

parser.setFeature("http://xml.org/sax/features/namespaces",

false);

// Przetwórz dokument

parser.parse(uri);

} catch (IOException e) {

System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage());

} catch (SAXException e) {

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

}

I cała reszta...

Czytelnik może odnieść wrażenie, że interfejs procedury obsługi DTD został omówiony dość po­bieżnie, szczególnie w porównaniu z innymi procedurami. Jednakże aplikacje wykorzystujące XML rzadko muszą rejestrować tego typu procedury. W systemach XML-owych często sprawdza się poprawność dokumentu, ale szczegółowe informacje odnośnie nieprzetworzonych encji rzadko przydają się na wyższym poziomie wykorzystania XML-a. Dlatego właśnie przejdziemy do in­nych szczegółowych informacji, bardziej przydatnych przy tworzeniu aplikacji korzystających z XML-a.

Czytelnik mógłby oczekiwać, że teraz zostanie omówione sprawdzanie poprawności za pomocą XML Schema; schematy są jednakże w coraz większym stopniu wykorzystywane nie tylko do spraw­dzania poprawności, ale także do zwykłej reprezentacji danych. Dlatego właśnie omawianie schematów z poziomu Javy zostało przeniesione do rozdziału 14. — wtedy Czytelnik będzie już znał XSL i różnego rodzaju praktyczne sposoby użycia XML-a w aplikacjach. Interfejsy SAX do obsługi schematów są o wiele potężniejsze niż DTDHandler i będą wymagały od Czytelnika wię­kszej wiedzy z zakresu XML-a. W niniejszym rozdziale omawianie sprawdzania poprawności danych XML zostanie zakończone przedstawieniem typowych problemów, na jakie można się nat­knąć w czasie tego procesu.

Uwaga! Pułapka!

Podczas sprawdzania poprawności dokumentu XML na programistę czyha wiele pułapek. Poniżej zostaną omówione problemy, z którymi najczęściej mają do czynienia początkujący programiści XML, a także te, których rozwiązanie wcale nie jest łatwe. Warto dobrze się zapoznać z poniż­szym materiałem, bo opisywane trudności spędziły sen z oczu już niejednemu programiście.

Obsługa sprawdzania poprawności a obsługa definicji DTD

Jednym z typowych nieporozumień odnośnie SAX-a i sprawdzania poprawności jest błędne za­ło­że­nie, że sprawdzanie poprawności dokumentu XML następowało automatycznie po zarejestro­wa­niu implementacji DTDHandler w parserze XML. Często zdarza się, że programista implementuje ten interfejs i rejestruje go w parserze, ale nie włącza funkcji sprawdzania poprawności parsera. Ten błąd wynika z zagubienia różnicy pomiędzy obsługą samego DTD a wykorzystaniem DTD do spraw­dzania poprawności. W tym przypadku definicja DTD zostanie przetworzona, wy­stą­pią też wszyst­kie wywołania związane z DTD (o ile są konieczne), jednakże nie nastąpi spraw­dzenie poprawności dokumentu XML — zostanie on wyłącznie przetworzony. Należy pamiętać, że wynik sprawdzania poprawności poprawnego dokumentu XML wygląda niemal tak samo jak wynik przetwarzania dokumentu bez sprawdzania jego poprawności; zawsze należy śledzić, kiedy występuje sprawdzanie poprawności — w ten sposób można uniknąć błędów w aplikacji:

try {

// Stwórz egzemplarz parsera

XMLReader parser =

XMLReaderFactory.createXMLReader(

"org.apache.xerces.parsers.SAXParser");

// Zarejestruj procedurę obsługi zawartości

parser.setContentHandler(contentHandler);

// Zarejestruj procedurę obsługi błędów

parser.setErrorHandler(errorHandler);

// Z tego nie wynika włączenie sprawdzania poprawności

parser.setDTDHandler(dtdHandler);

// Włącz sprawdzanie poprawności

parser.setFeature("http://xml.org/sax/features/validation",

true);

// Wyłącz "świadomość" przestrzeni nazw

parser.setFeature("http://xml.org/sax/features/namespaces",

false);

// Przetwórz dokument

parser.parse(uri);

} catch (IOException e) {

System.out.println("Błąd przy wczytywaniu URI: " + e.getMessage());

} catch (SAXException e) {

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

}

Trzeba koniecznie pamiętać o tym, że zarejestrowanie DTD nie ma nic wspólnego z procesem spraw­dzania poprawności; parser z zarejestrowaną implementacją DTDHandler nie zawsze spra­w­dza poprawność XML-a; zaś parser bez zarejestrowanej tej procedury może sprawdzić popraw­ność. To od cech (w implementacjach SAX 2.0) lub klasy (w implementacjach sprzed SAX 2.0) parsera XML zależy, czy sprawdzanie poprawności będzie się odbywało — a nie od zarejestro­wa­nej procedury obsługi. Pamiętajmy o tym — implementacja DTDHandler do sprawdzania po­praw­ności nie jest potrzebna; potrzebne jest natomiast ustawienie odpowiednich cech parsera lub użycie innej klasy.

W czasie tworzenia priorytetem jest poprawność;
w gotowej aplikacji liczy się szybkość

Programista powinien wiedzieć, kiedy należy korzystać ze sprawdzania poprawności, a kiedy nie. Jednym z największych problemów związanych ze sprawdzaniem poprawności jest często nie­zwy­kle powolne działanie aplikacji produkcyjnej. Programista analizuje przyczyny takiego spadku wy­daj­ności pracy aplikacji, nie pamiętając o tym, że w środowisku produkcyjnym pozostawił włączone sprawdzanie poprawności. Zazwyczaj sprawdzanie poprawności powinno mieć miejsce tylko w cza­sie testowania lub w czasie działania procesu kontrolowania jakości (ang. Quality Assurance). Jeśli część aplikacji generuje lub modyfikuje pliki XML, sprawdzanie poprawności wynikowego do­ku­mentu XML powinno odbywać się właśnie w czasie testowania. To proces kosztowny — parser przetwarza o wiele więcej danych i musi podjąć więcej decyzji dotyczących dokumentu.

Po sprawdzeniu poprawności wyników generowanych przez aplikację zazwyczaj dobrze jest wy­łączyć sprawdzanie poprawności. Aplikacja produkcyjna zyskuje dzięki temu na wydajności, a je­śli testowanie przeprowadzono zgodnie z zasadami tej sztuki, to nie powinny pojawić się żadne problemy. Jedyną sytuacją, w której nie należy wyłączać sprawdzania poprawności w aplikacji produkcyjnej, jest ta, w której w tworzeniu danych XML bierze aktywny udział klient — np. w na­rzędziach XML IDE lub GUI, albo ta, w której dane XML uzyskuje się od innych aplikacji — np. w e-biznesie. Wynika to stąd, że dane wejściowe są poza naszą kontrolą i mogą być niepoprawne — sprawdzanie poprawności jest więc zalecane, a nawet oczekiwane przez klienta. Jednak w wię­kszości przypadków gotowa aplikacja może pracować bez sprawdzania poprawności.

Co dalej?

Czytelnik powinien już dobrze znać zasady tworzenia dokumentów XML oraz sposoby ich za­wę­żania. W niniejszym rozdziale zostały omówione wszystkie najważniejsze aspekty korzystania z interfejsów i klas SAX. Czytelnik poznał już cykle przetwarzania i sprawdzania poprawności oraz dostępne wywołania. Powinien potrafić skonfigurować i użyć parsera XML oraz zareje­stro­wać w nim różne procedury obsługi SAX. W następnym rozdziale zostanie przedstawiona kolejna specyfikacja — XSL, czyli rozszerzalny język arkuszy stylów. Omówienie przekształcania za po­mo­cą XSL to wstęp do dyskusji o obiektowym modelu dokumentu i mechanizmach publikacyjnych, a także do bardziej dogłębnego poznawania zagadnień programowania aplikacji.

Domyślne włączanie obsługi przestrzeni nazw miało miejsce w czasie pisania tej książki, jednakże niektórzy programiści związani z XML-em (np. zespół programistów Apache Xerces) postulowali wyłączenie tej funkcji. Jeśli Czytelnik nie otrzymuje wspomnianego wyżej komunikatu, to być może domyślne ustawienie zostało już zmienione. (W czasie tłu­maczenia książki, w grudniu 2000 r., obsługa przestrzeni nazw była już domyślnie wyłączona — przyp. tłum.).

130 Rozdział 5. Sprawdzanie poprawności składni XML-a

Uwaga! Pułapka! 129

C:\Helion\Java i XML\jAVA I xml\05-08.doc — strona 130

C:\Helion\Java i XML\jAVA I xml\05-08.doc — strona 129

C:\Helion\Java i XML\jAVA I xml\05-08.doc — strona 117



Wyszukiwarka

Podobne podstrony:
02-08, Programowanie, ! Java, Java i XML
12-08, Programowanie, ! Java, Java i XML
01-08, Programowanie, ! Java, Java i XML
14-08, Programowanie, ! Java, Java i XML
00-08-orig, Programowanie, ! Java, Java i XML
r12-05, Programowanie, ! Java, Java Server Programming
r20-05, Programowanie, ! Java, Java Server Programming
Java i XML, programowanie, Java
O Autorach-05, Programowanie, ! Java, Java Server Programming
r05-05, Programowanie, ! Java, Java Server Programming
r07-05, Programowanie, ! Java, Java Server Programming
r03-05, Programowanie, ! Java, Java Server Programming
rE-05, Programowanie, ! Java, Java Server Programming
r19-05, Programowanie, ! Java, Java Server Programming
r17-05, Programowanie, ! Java, Java Server Programming
r11-05, Programowanie, ! Java, Java Server Programming
rD-05, Programowanie, ! Java, Java Server Programming
rF-05, Programowanie, ! Java, Java Server Programming
r04-05, Programowanie, ! Java, Java Server Programming

więcej podobnych podstron