XML Schema 115
Nauka XML-a, zarówno jako reprezentacji danych, jak i materiału wykorzystywanego przez aplikację w Javie, to proces wieloetapowy. Kolejne poznawane cechy XML-a lub technologii siostrzanych przyczyniają się do odkrywania całych pokładów wiedzy o tym języku. Istnieje wiele projektów i specyfikacji związanych z XML-em; pojawia się pokusa „poznania wszystkiego”. A kiedy w końcu wydaje nam się, że poznaliśmy wszystkie strony zagadnienia, pojawiają się nowe wersje... Im lepiej jednak rozumiemy zasadę działania poszczególnych komponentów składających się na krajobraz XML-a, tym lepiej przygotowani jesteśmy na wszelkie nowinki w naszym programistycznym warsztacie. Pamiętając o tym, na jakiś czas porzucimy teraz język Java i wrócimy do specyfikacji związanych z samym XML-em.
W rozdziałach 2. i 3. uzyskaliśmy wiedzę, która powinna wystarczyć do stworzenia poprawnie sformatowanego dokumentu oraz do późniejszej manipulacji (w ograniczonym zakresie) tym dokumentem z poziomu Javy. Powinniśmy także rozumieć, jak przetwarzane są dokumenty XML i jak w tym procesie można wykorzystać klasy SAX. W tym rozdziale zostanie omówione zawężanie dokumentów XML, a w następnym — wpływ zawężania na przetwarzanie takiego dokumentu w Javie.
Wypada najpierw wytłumaczyć, do czego potrzebne są Czytelnikowi wiadomości dotyczące definicji DTD i schematów. Niektórzy użytkownicy XML-a twierdzą, że w ogóle nie ma potrzeby zawężania dokumentów XML i sprawdzania ich poprawności. Jak już powiedzieliśmy, poprawny dokument XML spełnia wymogi zawężeń na niego narzuconych przez odpowiednią definicję DTD lub schemat. Napisaliśmy również, że choć dokument jest poprawnie sformatowany, to nie musi jeszcze być poprawny. Po cóż więc zadawać sobie trud tworzenia definicji DTD lub schematu, który narzuca dodatkowe reguły na dane XML?
Jako programujący w Javie, Czytelnik zapewne potrafi już dokumentować swój kod za pomocą narzędzia Javadoc lub komentarzy w samym kodzie. Prawdopodobnie wie także, jak ważne jest dokumentowanie własnej pracy — być może na kogoś spadnie obowiązek czytania naszego kodu, poprawiania, czy po prostu zrozumienia. Komentowanie kodu jest jeszcze ważniejsze w projektach open source. Być może zdarzyło nam się kiedyś, że poganiani terminami nie byliśmy zbyt rozlewni w komentowaniu programu. A trzy miesiące później programista, któremu powierzono zadanie kontynuowania projektu, zasypywał nas telefonami o przeznaczenie danego fragmentu kodu. Dobrze, jeśli jeszcze pamiętaliśmy; gorzej, jeśli dawno już zapomnieliśmy, jak udało nam się zrobić „tę sztuczkę”. Właśnie w takich chwilach poznajemy wartość dokumentacji.
Dane XML to z pewnością nie kod. W wyniku zagnieżdżania i innych reguł składniowych niemal zawsze łatwiej jest zrozumieć dokument XML niż fragment kodu w Javie. Jednakże nie należy zakładać, że to, jak my widzimy naszą reprezentację danych, będzie identycznie postrzegane przez innych. Świetnie obrazuje to plik XML z przykładu 4.1.
Przykład 4.1. Dwuznaczny plik XML
<?xml version="1.0" encoding="ISO-8859-2"?>
<strona>
<ekran>
<nazwa>Sprzedaż</nazwa>
<kolor>#CC9900</kolor>
<font>Arial</font>
</ekran>
<zawartosc>
<p>Tu idzie cała zawartość.</p>
</zawartosc>
</strona>
Przeznaczenie powyższego pliku wydaje się zupełnie oczywiste. Za jego pomocą aplikacja otrzymuje informacje o sposobie wyświetlenia konkretnego ekranu u klienta. Podany jest kolor, rodzaj czcionki oraz zawartość ekranu. Gdzież tutaj dwuznaczność? Cóż, staje się ona ewidentna dopiero po obejrzeniu innego dokumentu wykorzystywanego w tej samej aplikacji (przykład 4.2).
Przykład 4.2. Mniej dwuznaczny plik XML
<nazwa>Komunikaty</nazwa>
<kolor>#9900FF</kolor>
<nazwa>Nowości</nazwa>
<kolor>#EECCEE</kolor>
<font>Helvetica</font>
I nagle nasza interpretacja pierwszego pliku nie wydaje się już poprawna. Element ekran nie może reprezentować bieżącego ekranu, bo w drugim przykładzie widzimy aż trzy takie elementy. W rzeczywistości aplikacja tworzy na górze strony odsyłacze do dostępnych ekranów i właśnie element ekran opisuje, jak odsyłacze te mają wyglądać — podana jest nazwa odsyłacza, kolor fragmentu ekranu i czcionka. W pierwszym przypadku tak się złożyło, że był tylko jeden ekran, do którego tworzono odsyłacz, i stąd to zamieszanie. Tylko autor dokumentu XML lub programista aplikacji od razu zrozumieliby, o co tutaj chodzi.
Zawężanie dokumentów XML umożliwia udokumentowanie takich dwuznacznych sytuacji. Gdybyśmy wiedzieli, że w danej stronie XML dozwolony jest tylko jeden element ekran, moglibyśmy bezpiecznie poczynić takie założenie, jakie zrobiliśmy odnośnie pierwszego przykładu. Gdybyśmy jednak wiedzieli, że dozwolonych jest wiele elementów ekran, to nawet patrząc tylko na pierwszy przykład moglibyśmy trafniej odgadnąć przeznaczenie dokumentu. Innymi słowy, poprawnie sformatowany dokument XML zawiera słowa występujące w słowniku. Słowa te mają pewne znaczenia, ale mogą być wykorzystywane w różny sposób. Jako przykład weźmy słowa: „Lis kot bieg chleb ser”. Poprawność (ang. validity) dokumentu daje nam gwarancje, że „słowa” te (elementy i atrybuty dokumentu XML) zostaną złożone w sensowny sposób: „Lisy i koty biegną w kierunku chleba z serem”.
Udokumentowanie „poprawnych” czy „właściwych” połączeń elementów i atrybutów to właśnie zadanie definicji DTD lub schematu. Umożliwiają one samodokumentowanie się danych XML — teraz już nie tylko my będziemy wiedzieli, co właściwie chcieliśmy przekazać za pomocą danych XML.
Zawężanie XML-a pomaga nie tylko zrozumieć sposób reprezentacji danych innym osobom; pomaga także zrozumieć dane innym aplikacjom. Wspomnieliśmy o tym już wcześniej — jeśli weźmiemy dwie dowolne aplikacje, to nie możemy zakładać, że korzystają one ze wspólnych zasobów. Mówiąc inaczej, program, który utworzył dokument XML w jednej aplikacji, może nie być dostępny w drugiej; ta druga aplikacja „nie rozumie” logiki, według której powstał dokument XML. Druga aplikacja musi więc określić sama, jaki typ danych jest przekazywany za pośrednictwem dokumentu XML. Nie mając żadnych wskazówek, druga aplikacja mogłaby tylko zakładać, co „autor miał na myśli” — i często byłyby to założenia błędne.
Przypomina to nieco problemy z językiem C, których starali się uniknąć twórcy Javy. Java, niezależna od platformy i nie polegająca na kodzie własnym, jest obecnie najbardziej przenośnym językiem programowania. Wynika to stąd, że nałożono szereg zawężeń na to, co Java może robić i zawężenia te działają na wszystkich platformach. Takie szczegóły implementacyjne jak zarządzanie pamięcią czy wątkami pozostawiono do rozwiązania poszczególnym platformom, ale interfejs do obsługi tych zadań przez programistę jest zawsze taki sam.
Zawężanie dokumentów za pomocą definicji DTD lub schematów gwarantuje podobną przenośność w języku XML. Przypomnijmy sobie pierwszy z dwóch powyższych przykładów — gdyby inna aplikacja mogła uzyskać dostęp do zasobu opisującego dozwolone formaty przetwarzanych danych, mogłaby przetworzyć te dane za pomocą narzędzi XML-a. Ponieważ zawężenia dokumentu nie zostały zakodowane bezpośrednio w aplikacji (pierwszej czy drugiej), logika aplikacji nie zmieni się po zmianie formatu dokumentu. Definicja DTD lub schemat mógłby ulec zmianie, ale ponieważ pracujemy na tekstowym opisie zawężeń, aplikacja mogłaby natychmiast wykorzystać dokument o zmienionej strukturze, bez konieczności modyfikacji samej aplikacji. W ten sposób uzyskuje się przenośność danych XML bez konieczności ingerencji w kod aplikacji — tak jak próbuje się unikać kodu własnego w programach w Javie.
Czy to na potrzeby dokumentacji, przenośności pomiędzy aplikacjami i systemami, czy po prostu ze względu na bardziej restrykcyjne sprawdzanie poprawności danych XML, zawężanie przydaje się w niemal wszystkich przypadkach. Przeciwnicy zawężania mają rację właściwie tylko w jednym — sprawdzanie poprawności danych ma duży wpływ na wydajność całego procesu. Jednakże w wielu strukturach publikacji, np. takich jak Apache Cocoon, można określić, czy dany dokument ma być sprawdzany pod kątem poprawności, czy nie. Oznacza to, że tworzenie dokumentu można przeprowadzać przy włączonym sprawdzaniu poprawności i wyłączyć je dopiero wtedy, gdy struktura dokumentu została już solidnie przetestowana. Aplikacje otrzymujące dane również mogą sprawdzać lub nie sprawdzać poprawności dokumentu. W razie potrzeby zawsze jest to możliwe, bo dokument ten zawierać będzie odwołanie do definicji DTD lub schematu. A więc możliwe jest korzystanie ze sprawdzania składni bez jednoczesnego zmniejszania wydajności aplikacji. Wcześniej należy jednak sprawdzić, czy funkcja włączania-wyłączania sprawdzania jest dostępna w wykorzystywanej strukturze publikacji.
W systemach produkcyjnych sprawdzanie poprawności oznacza wyższą jakość aplikacji typu firma-firma (ang. business-to-business). Sprawdzanie poprawności daje gwarancję, że dane otrzymane z innych aplikacji — często takich, na które nie mamy wpływu — są poprawnie sformatowane. Dzięki temu unikamy błędów wynikających z niepoprawnego formatu danych wejściowych. Tutaj definicje DTD i schematy są wprost nieocenione.
Jak już to zostało powiedziane, dokument XML ma niewielką wartość bez towarzyszącej mu definicji DTD. XML w wydajny sposób opisuje dane, a DTD przygotowuje te dane do użycia w wielu różnych programach poprzez zdefiniowanie ich struktury. W tej części książki zajmiemy się konstrukcjami DTD. W funkcji przykładowego pliku XML znów zostanie wykorzystany fragment spisu treści niniejszej książki, dla którego zbudujemy definicję DTD.
Zadaniem definicji DTD jest określenie sposobu formatowania danych. Zdefiniowany musi zostać każdy element dozwolony w danym dokumencie XML, sposoby występowania i zagnieżdżania elementów, a także zewnętrzne encje. Tak naprawdę w definicji DTD można określić jeszcze wiele innych aspektów dokumentu, ale tutaj skoncentrujemy się na tych podstawowych. Poznamy konstrukcje oferowane przez DTD — za ich pomocą zawęzimy nasz przykładowy plik z rozdziału 2. Ponieważ do pliku tego będziemy się często odwoływali w tym rozdziale, warto przytoczyć go tutaj jeszcze raz (przykład 4.3).
Przykład 4.3. Plik XML zawierający spis treści
<?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:Title>Java i XML</JavaXML:Title>
<JavaXML:Spis>
<JavaXML:Rozdzial tematyka="XML">
<JavaXML:Naglowek>Wprowadzenie</JavaXML:Naglowek>
<JavaXML:Temat podrozdzialy="7">Co to jest?</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="3">Jak z tego korzystać?</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="4">Dlaczego z tego korzystać?</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="0">Co dalej?</JavaXML:Temat>
</JavaXML:Rozdzial>
<JavaXML:Naglowek>Tworzenie dokumentów XML</JavaXML:Naglowek>
<JavaXML:Temat podrozdzialy="0">Dokument XML</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="2">Nagłówek</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="6">Zawartość</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="1">Co dalej?</JavaXML:Temat>
<JavaXML:Rozdzial tematyka="Java">
<JavaXML:Naglowek>Przetwarzanie kodu XML</JavaXML:Naglowek>
<JavaXML:Temat podrozdzialy="3">Przygotowujemy się</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="3">Czytniki SAX</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="9">Procedury obsługi zawartości</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="4">Procedury obsługi błędów</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="0">
Lepszy sposób ładowania parsera
</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="4">"Pułapka!"</JavaXML:Temat>
<JavaXML:PodzialSekcji/>
<JavaXML:Naglowek>Struktury publikacji WWW</JavaXML:Naglowek>
<JavaXML:Temat podrozdzialy="4">Wybór struktury</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="4">Instalacja</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="3">
Korzystanie ze struktury publikacji
<JavaXML:Temat podrozdzialy="2">XSP</JavaXML:Temat>
<JavaXML:Temat podrozdzialy="3">Cocoon 2.0 i dalej</JavaXML:Temat>
</JavaXML:Spis>
<JavaXML:Copyright>&OReillyCopyright;</JavaXML:Copyright>
</JavaXML:Ksiazka>
Najpierw zajmiemy się określaniem, które elementy są w naszym dokumencie dozwolone. Chcemy, aby definicja DTD umożliwiała autorom umieszczanie w naszym dokumencie takich elementów jak JavaXML:Ksiazka i JavaXML:Spis, ale nie JavaXML:jas czy JavaXML:malgosia. Określenie zestawu dozwolonych elementów oznacza nadanie takiemu dokumentowi znaczenia semantycznego; innymi słowy określamy dopuszczalny (sensowny) kontekst. Najpierw należy więc stworzyć listę dopuszczalnych elementów. Najprostszym sposobem jest przejrzenie dokumentu i odnotowanie każdego wykorzystanego elementu. Dobrze jest także zdefiniować przeznaczenie poszczególnych znaczników. Co prawda na określenie przeznaczenia elementów nie pozwala bezpośrednio DTD (chyba że za pomocą komentarzy — to całkiem niezłe rozwiązanie), ale coś takiego uprościłoby pracę autora DTD. W tabeli 4.1 przedstawiono pełne zestawienie elementów dokumentu contents.xml.
Tabela 4.1. Elementy dozwolone w naszym dokumencie XML
Nazwa elementu
Znaczenie
JavaXML:Ksiazka
element główny
...
Zabr7