Uwaga! Pułapka! 129
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 — sprawdzanie 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 poprawności dokumentów XML względem ich zawężeń. Czytelnik dowie się, jak ustawić cechy i właściwości parsera zgodnego z SAX, aby możliwe było proste sprawdzanie poprawności, obsługa przestrzeni nazw i wykonywanie innych czynności. Ponadto szczegółowo omówione zostaną błędy i ostrzeżenia zgłaszane przez parsery sprawdzające poprawność.
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 parseró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 poprawności, obsługi przestrzeni nazw i innych kluczowych cech są odmienne w różnych implementacjach 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ń.
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, ustawienia separatora przestrzeni nazw czy obsługi innych cech nie trzeba zmieniać zbyt wiele w istnieją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()
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 identyfikator URI. Lista najważniejszych właściwości i cech jest zamieszczona w dodatku B. Producent parsera XML powinien udostępnić dodatkową dokumentację informujacą o obsługiwanych cechach i właściwościach. Pamiętajmy jednak, że identyfikatory te, podobnie jak identyfikatory URI przestrzeni nazw, służą wyłącznie do kojarzenia odpowiednich cech. Dobry parser umożliwi korzystanie 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 przetworzenie identyfikatora, często jako stałej reprezentującej odpowiednią czynność, którą należy podjąć.
W kontekście konfiguracji parsera właściwość wymaga obecności obiektu, z którego można skorzystać. 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 celu 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 dodawanie i zmianę różnych cech. Nowe lub zaktualizowane właściwości wymagają obsługi ze strony parsera, ale dostęp do nich uzyskuje się wciąż za pomocą tych samych metod — konieczne jest tylko zdefiniowanie 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.
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 odwołać się do historii. W interfejsie SAX 1.0 implementacje parserów musiały udostępniać własne rozwią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 sposób polegał na dostarczeniu dwóch niezależnych klas przetwarzających. Na przykład, aby wykonać 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
"com.sun.xml.parser.ValidatingParser");
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 programistyczne wykorzystuje kod, który sprawdza poprawność danych XML wytwarzanych przez aplikację. To sprawdzanie poprawności, choć obniża wydajność, zapewnia, że aplikacja tworzy zawsze poprawny kod XML albo że otrzymuje poprawne dokumenty XML na wejściu. Często takie zawężenia po intensywnym testowaniu można usunąć, dzięki czemu odzyskuje się wysoką wydajność działania aplikacji. Pozbawienie parsera możliwości sprawdzania poprawności jest uzasadnione, ponieważ dogłębne testy potwierdziły poprawność tworzonego dokumentu XML; ale zmiana ta wymaga modyfikacji i rekompilacji kodu. Wydaje się to być sprawą banalną, ale wiele firm nie pozwala na wdrożenie do produkcji kodu, który był modyfikowany później niż przed jakimś określonym czasem — często kilka dni, a nawet tygodni. Taka niewielka zmiana może więc spowodować 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 rozdziale 2. przy okazji opisywania aspektów przenośności aplikacji XML). Tak, ale zmiana całej implementacji parsera tuż przed wdrażaniem aplikacji do produkcji to zmiana poważna, która powinna być należycie przetestowana. Jeśli porównamy to ze zmianą wartości zestawu cech (zakładając, że wartość do ustawiania tej cechy także pobierana jest z pliku właściwości), to łatwo zgadnąć, 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 identyfikatora URI: http://xml.org/sax/features/validation. Moglibyśmy zażądać również przetwarzania encji zewnętrznych i przestrzeni nazw, ale tymczasem „włączymy” tylko sprawdzanie poprawnoś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();
// 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
} 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 poprzednim 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>
...
b.senni