04 08 (10)


4

Zawężanie danych XML

Nauka XML-a, zarówno jako reprezentacji danych, jak i materiału wykorzystywanego przez apli­ka­cję w Javie, to proces wieloetapowy. Kolejne poznawane cechy XML-a lub technologii sio­strza­nych 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 le­piej jednak rozumiemy zasadę działania poszczególnych komponentów składających się na kraj­obraz 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 sfor­matowanego dokumentu oraz do późniejszej manipulacji (w ograniczonym zakresie) tym do­ku­mentem 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 do­kumentów XML, a w następnym — wpływ zawężania na przetwarzanie takiego dokumentu w Javie.

Po co zawężać dane XML?

Wypada najpierw wytłumaczyć, do czego potrzebne są Czytelnikowi wiadomości dotyczące defi­ni­cji DTD i schematów. Niektórzy użytkownicy XML-a twierdzą, że w ogóle nie ma potrzeby za­węż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?

Komentarze w programach

Jako programujący w Javie, Czytelnik zapewne potrafi już dokumentować swój kod za pomocą na­rzę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 pro­je­ktach 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 otrzy­muje 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

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

<strona>

<ekran>

<nazwa>Sprzedaż</nazwa>

<kolor>#CC9900</kolor>

<font>Arial</font>

</ekran>

<ekran>

<nazwa>Komunikaty</nazwa>

<kolor>#9900FF</kolor>

<font>Arial</font>

</ekran>

<ekran>

<nazwa>Nowości</nazwa>

<kolor>#EECCEE</kolor>

<font>Helvetica</font>

</ekran>

<zawartosc>

<p>Tu idzie cała zawartość.</p>

</zawartosc>

</strona>

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 fra­gmentu 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 apli­kacji od razu zrozumieliby, o co tutaj chodzi.

Zawężanie dokumentów XML umożliwia udokumentowanie takich dwuznacznych sytuacji. Gdy­byśmy wiedzieli, że w danej stronie XML dozwolony jest tylko jeden element ekran, mogli­byś­my bezpiecznie poczynić takie założenie, jakie zrobiliśmy odnośnie pierwszego przykładu. Gdy­byś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, po­praw­nie 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 za­danie definicji DTD lub schematu. Umożliwiają one samodokumentowanie się danych XML — te­raz już nie tylko my będziemy wiedzieli, co właściwie chcieliśmy przekazać za pomocą danych XML.

Przenośność

Zawężanie XML-a pomaga nie tylko zrozumieć sposób reprezentacji danych innym osobom; po­maga także zrozumieć dane innym aplikacjom. Wspomnieliśmy o tym już wcześniej — jeśli weź­mie­my 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ć do­stę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, nie­za­leż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 za­rzą­dza­nie 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ą prze­noś­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 da­nych, mogłaby przetworzyć te dane za pomocą narzędzi XML-a. Ponieważ zawężenia doku­mentu 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 wyko­rzystać 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 jed­nym — 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 stru­ktura 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 wy­korzystywanej strukturze publikacji.

W systemach produkcyjnych sprawdzanie poprawności oznacza wyższą jakość aplikacji typu fir­ma-firma (ang. business-to-business). Sprawdzanie poprawności daje gwarancję, że dane otrzy­ma­ne 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.

Definicje typu dokumentu

Jak już to zostało powiedziane, dokument XML ma niewielką wartość bez towarzyszącej mu de­fi­nicji 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ę kon­stru­kcjami DTD. W funkcji przykładowego pliku XML znów zostanie wykorzystany fragment spi­su 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 ele­mentów, a także zewnętrzne encje. Tak naprawdę w definicji DTD można określić jeszcze wie­le 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 version="1.0" encoding="ISO-8859-2"?>

<?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:Rozdzial tematyka="XML">

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

<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:Temat podrozdzialy="0">Co dalej?</JavaXML:Temat>

</JavaXML:Rozdzial>

<JavaXML:PodzialSekcji/>

<JavaXML:Rozdzial tematyka="Java">

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

<JavaXML:Temat podrozdzialy="2">XSP</JavaXML:Temat>

<JavaXML:Temat podrozdzialy="3">Cocoon 2.0 i dalej</JavaXML:Temat>

<JavaXML:Temat podrozdzialy="0">Co dalej?</JavaXML:Temat>

</JavaXML:Rozdzial>

</JavaXML:Spis>

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

</JavaXML:Ksiazka>

Określanie elementów

Najpierw zajmiemy się określaniem, które elementy są w naszym dokumencie dozwolone. Chce­my, aby definicja DTD umożliwiała autorom umieszczanie w naszym dokumencie takich elemen­tów jak JavaXML:Ksiazka i JavaXML:Spis, ale nie JavaXML:jas czy JavaXML:malgo­sia. Określenie zestawu dozwolonych elementów oznacza nadanie takiemu dokumentowi zna­cze­nia 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 roz­wią­za­nie), ale coś takiego uprościłoby pracę autora DTD. W tabeli 4.1 przedstawiono pełne zestawienie ele­mentów dokumentu contents.xml.

Tabela 4.1. Elementy dozwolone w naszym dokumencie XML

Nazwa elementu

Znaczenie

JavaXML:Ksiazka

element główny

JavaXML:Tytul

tytuł dokumentowanej książki

JavaXML:Spis

oznacza spis treści książki

JavaXML:Rozdzial

rozdział książki

JavaXML:Naglowek

nagłówek (tytuł) rozdziału

JavaXML:Temat

tematyka rozdziału

JavaXML:PodzialSekcji

podział pomiędzy rozdziałami oznaczający nową część książki

JavaXML:Copyright

informacje o prawach autorskich

Teraz poszczególne elementy możemy zdefiniować w DTD. Wykonujemy to za pomocą na­stę­pu­jących konstrukcji:

<!ELEMENT [Nazwa elementu] [Definicja/typ elementu]>

Konstrukcja [Nazwa elementu] to faktyczna nazwa elementu, taka jak w tabelce powyżej. Po­winna ona, podobnie jak w tabeli, zawierać przedrostek przestrzeni nazw. W DTD nie istnieje pojęcie elementu z przedrostkiem przestrzeni nazw ani odwzorowania przestrzeni nazw według iden­tyfikatora URI. Nazwa elementu ma postać albo samej nazwy (gdy nie użyto przestrzeni nazw), albo nazwy poprzedzonej przedrostkiem i dwukropkiem.

Najbardziej użytecznym fragmentem jest konstrukcja [Definicja/typ elementu]. Tutaj de­finiujemy element, nadając mu „typ” i określając, czy są to „czyste dane”, czy też typ złożony z danych i innych elementów. Najmniej restrykcyjnym typem elementu jest ten określony za po­mocą słowa kluczowego ANY. Wtedy element może zawierać dane tekstowe, elementy zagnież­dżone, lub też dowolną (prawidłową) kombinację ich obu. Ta wiedza umożliwia już zdefiniowanie wszystkich elementów dokumentu XML, choć może w niezbyt użyteczny sposób. W przykładzie 4.4 jest przedstawiony początek definicji DTD naszego dokumentu XML.

Przykład 4.4. „Gołe” definicje elementów w DTD

<!ELEMENT JavaXML:Ksiazka ANY>

<!ELEMENT JavaXML:Tytul ANY>

<!ELEMENT JavaXML:Spis ANY>

<!ELEMENT JavaXML:Rozdzial ANY>

<!ELEMENT JavaXML:Naglowek ANY>

<!ELEMENT JavaXML:Temat ANY>

<!ELEMENT JavaXML:PodzialSekcji ANY>

<!ELEMENT JavaXML:Copyright ANY>

Oczywiście, taka prosta definicja DTD, nie opisująca ani atrybutów, ani encji, jest niezbyt przy­datna. Określony jest każdy dozwolony element, ale nie ma mowy ani o typach tych dokumentów, ani o dozwolonym zagnieżdżaniu. Można stworzyć dokument zgodny z tą DTD, a jednak zupełnie bezsensowny (przykład 4.5).

Przykład 4.5. Dokument zgodny z DTD, ale bezużyteczny

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

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

<JavaXML:Temat>

<JavaXML:Ksiazka>Oto moja książka</JavaXML:Ksiazka>

<JavaXML:Copyright>

<JavaXML:Rozdzial>Rozdział pierwszy</JavaXML:Rozdzial>

</JavaXML:Copyright>

<JavaXML:PodzialSekcji>A tu inna część</JavaXML:PodzialSekcji>

</JavaXML:Temat>

W powyższym fragmencie zostały użyte wyłącznie elementy dozwolone w DTD, a jednak jego struktura nie jest poprawna. Wynika to stąd, że nasza definicja DTD nie określa, w jaki sposób mo­gą być zagnieżdżane elementy i które z nich mogą zawierać dane tekstowe.

Zagnieżdżanie elementów

Jedną z najważniejszych cech struktury dokumentu XML jest zagnieżdżanie elementów. Naszą wcześ­niejszą tabelkę rozszerzymy teraz o informację o dozwolonym zagnieżdżaniu elementów. W ten sposób powstanie hierarchia elementów, którą następnie zdefiniujemy w DTD. Spójrzmy na tabelę 4.2.

Tabela 4.2. Hierarchia elementów

Nazwa elementu

Dozwolone zagnieżdżone elementy

Znaczenie

JavaXML:Ksiazka

JavaXML:Tytul
JavaXML:Spis
JavaXML:Copyright

element główny

JavaXML:Tytul

brak

tytuł dokumentowanej książki

JavaXML:Spis

JavaXML:Rozdzial
JavaXML:PodzialSekcji

oznacza spis treści książki

JavaXML:Rozdzial

JavaXML:Naglowek
JavaXML:Temat

rozdział książki

JavaXML:Naglowek

brak

nagłówek (tytuł) rozdziału

JavaXML:Temat

brak

tematyka poruszana w rozdziale

JavaXML:PodzialSekcji

brak

podział pomiędzy rozdziałami oznaczający nową część książki

JavaXML:Copyright

brak

informacje o prawach autorskich

Teraz możemy już określić sposób zagnieżdżania w DTD. Robimy to za pomocą następującej konstrukcji:

<!ELEMENT [Nazwa elementu] ([Zagniezdzony element] [,Zagniezdzony element]...)>

W tym przypadku typem elementu staje się lista elementów rozdzielonych przecinkami. Istotna jest kolejność elementów, z czego wynika dalsze zawężenie dokumentu XML. W ten sposób gwa­rantujemy, że np. element copyright znajdzie się na końcu książki, albo że tytuł występuje przed spisem. Teraz możemy naszą definicję DTD wzbogacić o opis zagnieżdżania (przykład 4.6).

Przykład 4.6. Definicja DTD określająca hierarchię elementów

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ELEMENT JavaXML:Tytul ANY>

<!ELEMENT JavaXML:Spis (JavaXML:Rozdzial, JavaXML:PodzialSekcji)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek, JavaXML:Temat)>

<!ELEMENT JavaXML:Naglowek ANY>

<!ELEMENT JavaXML:Temat ANY>

<!ELEMENT JavaXML:PodzialSekcji ANY>

<!ELEMENT JavaXML:Copyright ANY>

Choć niektóre elementy — te, które zawierają przetwarzane dane — nie uległy zmianie, stwo­rzy­liśmy hierarchię, która nadaje znaczenie zawężonemu dokumentowi. Wcześniejszy bezsensowny przykład teraz traktowany byłby już jako niepoprawny. Wciąż jednak jest tutaj sporo problemów związanych z dozwolonymi typami danych w pozostałych elementach.

Przetwarzane dane

Typ elementu wykorzystywany do określania danych tekstowych to #PCDATA. Nazwa tego typu wywodzi się ze słów Parsed Character Data (przetwarzane dane tekstowe). Za jego pomocą ozna­czamy elementy zawierające dane tekstowe, które mają być w zwyczajny sposób traktowane przez parser XML. Zastosowanie tego typu wyklucza jednak możliwość obecności w elemencie innych, zagnieżdżonych elementów. Tego typu sytuacje zostaną przedstawione dalej. Tymczasem możemy już zmienić elementy określające tytuł, nagłówek i temat — w nich mogą być zawarte dane teksto­we (przykład 4.7).

Przykład 4.7. Definicja DTD opisująca hierarchię elementów i elementy zawierające dane tekstowe

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ELEMENT JavaXML:Tytul (#PCDATA)>

<!ELEMENT JavaXML:Spis (JavaXML:Rozdzial, JavaXML:PodzialSekcji)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek, JavaXML:Temat)>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ELEMENT JavaXML:PodzialSekcji ANY>

<!ELEMENT JavaXML:Copyright ANY>

Elementy puste

Oprócz elementów zawierających dane tekstowe oraz tych, które zawierają inne elementy, mamy je­den element, JavaXML:PodzialSekcji, który nie ma zawierać żadnych danych. Oczy­wiś­cie, można by określić, że element ten zawiera przetwarzane dane tekstowe i nigdy ich tam nie wstawiać, ale nie tak należy korzystać z zawężeń. Lepiej jawnie określić, że element ma być pusty — w ten sposób zapobiegamy przypadkowym błędom. Tym razem odpowiednie słowo kluczowe


to EMPTY. Nie musi ono pojawiać się w nawiasach, ponieważ określa pewien typ i nie może być łączone z innymi elementami, na co — jak wkrótce zobaczymy — pozwalają właśnie nawiasy. Wpro­wadźmy więc kolejną poprawkę do definicji DTD (przykład 4.8).

Przykład 4.8. Definicja DTD z określonym elementem EMPTY

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ELEMENT JavaXML:Tytul (#PCDATA)>

<!ELEMENT JavaXML:Spis (JavaXML:Rozdzial, JavaXML:PodzialSekcji)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek, JavaXML:Temat)>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ELEMENT JavaXML:PodzialSekcji EMPTY>

<!ELEMENT JavaXML:Copyright ANY>

Encje

Ostatnim elementem, który należy uściślić, jest JavaXML:Copyright. Jak już to było wspo­m­niane, element ten faktycznie zawiera encję odwołującą się do innego pliku, który w tym miejscu ma zostać zawarty. Kiedy dokument XML „zobaczy” &OReillyCopyright;, będzie usiłował odnaleźć encję OReillyCopyright w DTD, która w naszym przypadku powinna stanowić odwołanie do zewnętrznego pliku. Plik ten zawiera informacje o prawach autorskich wszystkich książek udokumentowanych za pomocą języka XML. Zadaniem DTD jest określenie miejsca położenia pliku oraz sposobu uzyskania dostępu do niego. Tutaj zakładamy, że plik z prawami autorskimi znajduje się na dysku lokalnym i że do niego właśnie będziemy się odwoływali. Encje określane są w ramach definicji DTD za pomocą następującego zapisu:

<!ENTITY [Nazwa encji] "[Znaki podstawiane/identyfikator]">

Zauważmy, że umożliwia się podanie zestawu znaków podstawianych (zamiast pliku zew­nętrz­nego). To właśnie w ten sposób określane są „sekwencje unikowe” w dokumentach XML:

<!ENTITY &amp; "&">

<!ENTITY &lt; "<">

<!ENTITY &gt; ">">

...

Tak więc jeśli nasza informacja o prawach autorskich byłaby krótka, moglibyśmy napisać:

<!ENTITY &OReillyCopyright;

"Copyright O&apos;Reilly and Associates, 2000">

Jednakże my zamierzamy użyć dłuższego tekstu, a więc wygodniej będzie pobrać go z zew­nętrz­ne­go pliku. W ten sposób możemy z niego korzystać w wielu dokumentach bez konieczności duplikowania danych w poszczególnych definicjach DTD. Wymaga to podania identyfikatora za­sobu systemowego, dlatego korzystamy tutaj z następującego zapisu:

<!ENTITY [Encja] SYSTEM "[URI]">

Tak jak w przypadku dokumentu XML, URI może być zarówno zasobem lokalnym, jak i sie­cio­wym. W naszym przypadku odwołujemy się do pliku znajdującego się na serwerze zewnętrznym, więc musimy zastosować adres URL:

<!ENTITY OReillyCopyright SYSTEM

"http://www.oreilly.com/catalog/javaxml/docs/copyright.xml">

Dzięki takiemu zapisowi parser XML rozpozna referencję OReillyCopyright w dokumencie i od­po­­wiednio przetłumaczy ją w czasie przetwarzania. Z tego właśnie powodu musieliśmy ten frag­ment oznaczyć jako komentarz w rozdziale 3. W następnym rozdziale usuniemy symbole komentarza i zobaczymy, w jaki sposób parser sprawdzający poprawność składni obsłuży tę encję z wykorzys­ta­niem definicji DTD.

Na koniec w elemencie zawierającym encję musimy podać, że oczekujemy w tym miejscu prze­twa­rzanych danych tekstowych:

<!ELEMENT JavaXML:Copyright (#PCDATA)>

Powtórz to, a...

Ostatnią ważną konstrukcją, jaką omówimy w związku z definicjami DTD, jest opis grupowania, wielokrotnego występowania i dopuszczalnych kombinacji w ramach elementu. Innymi słowy, cho­dzi tu np. o sytuacje, w których element X może wystąpić tylko raz, lub też po elemencie Y musi wystąpić element Z. To niezwykle istotne konstrukcje DTD. Domyślnie, jeśli nie podaliśmy w elemencie specjalnych modyfikatorów, element może wystąpić dokładnie raz:

<!ELEMENT MojElement (ZagniezdzonyElement, InnyElement)>

W tym przypadku ZagniezdzonyElement ma wystąpić tylko raz, a po nim koniecznie musi znajdować się InnyElement. Jeśli struktura dokumentu XML jest inna, to dokument ten nie jest poprawny. Aby zmienić to domyślne zawężenie, można zastosować specjalne modyfikatory.

Zero, jeden lub więcej

Najczęściej do elementu dodawany jest operator rekurencji. Umożliwia on określenie, czy dany ele­ment ma się pojawić zero lub więcej razy, jeden lub więcej razy, wcale lub (ustawienie domyślne) dokładnie raz. W tabeli 4.3 przedstawione są operatory rekurencyjne i ich znaczenia.

Tabela 4.3. Operatory rekurencyjne

Operator

Opis

[Domyślnie]

musi wystąpić dokładnie raz

?

musi wystąpić raz albo wcale

+

musi wystąpić przynajmniej raz (1 ... n razy)

*

może wystąpić dowolną liczbę razy (0 ... n razy)

Modyfikatory dodawane są na końcu nazwy elementu. W naszym poprzednim przykładzie, aby ze­zwolić na jedno lub więcej wystąpień elementu ZagniezdzonyElement, a potem na tylko jed­no (lub wcale) wystąpienie elementu InnyElement, należałoby użyć następującej konstrukcji:

<!ELEMENT MojElement (ZagniezdzonyElement+, InnyElement?)>

Wtedy taki zapis XML byłby zupełnie poprawny:

<MojElement>

<ZagniezdzonyElement>Jeden</ZagniezdzonyElement>

<ZagniezdzonyElement>Dwa</ZagniezdzonyElement>

</MojElement>

W definicji DTD, którą tworzymy, z taką sytuacją mamy do czynienia w przypadku elementu JavaXML:Rozdzial. Chcemy, aby nagłówek rozdziału (JavaXML:Naglowek) mógł wystą­pić raz lub wcale, chcemy również pozwolić na wystąpienie jednego lub więcej elementów Ja­vaXML:Temat. Wykorzystujemy modyfikatory rekurencyjne:

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

Ta prosta zmiana czyni naszą reprezentację rozdziału w XML bardziej realistyczną. Musimy także dokonać zmian w definicji elementu JavaXML:Spis. Może się w nim pojawić jeden lub więcej rozdziałów, a potem, ewentualnie, podział sekcji. Podział jest opcjonalny, bo książka może się składać wyłącznie z rozdziałów. Rekurencję rozdziałów i podziałów sekcji zapiszemy następująco:

<!ELEMENT JavaXML:Spis (JavaXML:Rozdzial+, JavaXML:PodzialSekcji?)>

Wciąż jednak definicja DTD „nie wie”, że po elemencie JavaXML:PodzialSekcji może wy­stą­pić więcej rozdziałów. Chcemy, aby cała ta struktura mogła pojawić się kilkakrotnie. Po roz­dzia­le podział sekcji, potem znów rozdziały i znów podziały sekcji. Musimy zastosować grupowanie.

Grupowanie

Grupowanie umożliwia rozwiązywanie problemów takich jak ten z zagnieżdżaniem elementów we­wnątrz JavaXML:Spis. Często rekurencja dotyczy bloku elementów, a nie jednego elementu. Dlatego operatory rekurencji można dodawać także do grup elementów. Grupę elementów two­rzymy poprzez ujęcie elementów w nawiasy. Jeśli w tej chwili Czytelnikowi przypomniały się zajęcia z języka LISP, to niech się nie zamartwia — w naszych przykładach wszystko będzie do­syć proste, a liczba nawiasów nie wymknie się spod kontroli. Oczywiście, zagnieżdżanie na­wia­sów jest dozwolone. Tak więc, aby stworzyć grupę elementów, zastosujemy następującą konstrukcję:

<!ELEMENT PrzykladGrupowania ((Grupa1El1, Grupa1El2),

(Grupa2El1, Grupa2El2))>

Do każdej grupy (a nie tylko do poszczególnych jej elementów) można dopisać modyfikator. W opi­sywanym scenariuszu musimy tak zmienić zapis, aby możliwe było wielokrotne wystąpienie gru­py zawierającej elementy rozdziału i podziału sekcji:

<!ELEMENT JavaXML:Spis (JavaXML:Rozdzial+,JavaXML:PodzialSekcji?)+>

Teraz dozwolone są już różne kombinacje — kilka rozdziałów plus jeden podział sekcji i to wszy­stko powtórzone wiele razy lub też nie powtórzone wcale. Możliwy jest także przypadek, w któ­rym wystąpią same tylko rozdziały, bez podziałów sekcji. Jednakże tutaj sprawa nie jest zupełnie jasna (według naszego opisu w DTD). Lepiej byłoby określić, że pojawić się może jeden lub więcej rozdziałów lub taka struktura. Zachowanie DTD się nie zmieni, ale z pewnością definicja będzie bardziej czytelna. Aby to uzyskać, należy wprowadzić pojęcie „lub”.

To lub tamto

W definicjach DTD możliwe jest stosowanie pojęcia „lub”, oznaczanego symbolem kreski pio­nowej. Symbol ten jest często wykorzystywany w połączeniu z grupowaniem. Częstym, choć nie­ko­niecznie udanym przykładem użycia operatora „lub” jest umożliwienie wystąpienia wewnątrz elementu pewnego innego elementu (elementów) lub danych:

<!ELEMENT ElementKumulujacy (#PCDATA|(Element 1, Element2))>

W przypadku tej definicji DTD oba poniższe fragmenty będą poprawne:

<ElementKumulujacy>

<Element1>Jeden</Element1>

<Element2>Dwa</Element2>

</ElementKumulujacy>

<ElementKumulujacy>

Dane tekstowe

</ElementKumulujacy>

Nie zaleca się jednak stosowania takiego rozwiązania, gdyż wówczas staje się niejasne znaczenie ele­mentu zamykającego. Zazwyczaj element powinien zawierać dane tekstowe, dane przetwarzane lub inne elementy — ale nie wszystko razem.

W naszym dokumencie zależy nam na bardziej przejrzystej reprezentacji elementu JavaXML: Spis. Zastosujemy następujące rozwiązanie:

<!ELEMENT JavaXML:Spis ((JavaXML:Rozdzial+)|

(JavaXML:Rozdzial+, JavaXML:PodzialSekcji?)+)>

Teraz jest już jasne, że może pojawić się tutaj albo pewna liczba rozdziałów, albo rozdziały, po których następuje podział sekcji. W ten sposób tworzy się przejrzyście udokumentowaną definicję, a jednocześnie zapewnia odpowiednie zawężenie dokumentu XML.

Teraz nasze elementy XML są już poprawnie określone i zawężone. Definicja przedstawiona w przy­kładzie 4.9 powinna działać bezproblemowo na naszym przykładowym dokumencie. Pozo­s­ta­ły nam jeszcze definicje argumentów, które omawiamy poniżej.

Przykład 4.9. Opis elementów w definicji DTD

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ELEMENT JavaXML:Tytul (#PCDATA)>

<!ELEMENT JavaXML:Spis ((JavaXML:Rozdzial+)|

(JavaXML:Rozdzial+, JavaXML:PodzialSekcji?)+)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ELEMENT JavaXML:PodzialSekcji EMPTY>

<!ELEMENT JavaXML:Copyright (#PCDATA)>

<!ENTITY OReillyCopyright SYSTEM

"http://www.oreilly.com/catalog/javaxml/docs/copyright.xml">

Definiowanie atrybutów

Po tym dość dokładnym omówieniu sposobu definiowania elementów możemy przejść do defi­nio­wa­nia atrybutów. Ponieważ tutaj nie będziemy mieli do czynienia z zagnieżdżaniem, czynność ta jest nieco prostsza niż opisywanie elementów. Nie będziemy też potrzebowali operatorów reku­rencji, ponieważ o tym, czy obecność danego atrybutu jest wymagana, mówi określone słowo klu­czowe. Definicje atrybutów przyjmują następującą formę:

<!ATTLIST [Element zamykajacy]

[Nazwa atrybutu] [typ] [modyfikator]

...

>

Pierwsze dwa parametry, nazwa elementu i atrybutu, nie powinny sprawiać trudności. Za pomocą jednej konstrukcji ATTLIST dowolnemu elementowi możemy przypisać wiele atrybutów. Taki sche­mat jak powyżej zostanie dodany teraz do naszej definicji DTD, dzięki czemu lepiej będzie przedstawiona struktura opisywania atrybutów. Najlepiej definicje atrybutów dodawać zaraz po opisie elementu (to kolejny krok w kierunku utworzenia samodokumentującego się opisu DTD). Spójrz­my na przykład 4.10.

Przykład 4.10. Definicja DTD opisująca elementy i zawierająca ogólny szkielet opisów atrybutów

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ATTLIST JavaXML:Ksiazka

xmlns:JavaXML [typ] [modyfikator]

>

<!ELEMENT JavaXML:Tytul (#PCDATA)>

<!ELEMENT JavaXML:Spis ((JavaXML:Rozdzial+)|

(JavaXML:Rozdzial+, JavaXML:PodzialSekcji?)+)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

<!ATTLIST JavaXML:Rozdzial

tematyka [typ] [modyfikator]

czesc [typ] [modyfikator]

>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ATTLIST JavaXML:Temat

podRozdzialy [typ] [modyfikator]

>

<!ELEMENT JavaXML:PodzialSekcji EMPTY>

<!ELEMENT JavaXML:Copyright (#PCDATA)>

<!ENTITY OReillyCopyright SYSTEM

"http://www.oreilly.com/catalog/javaxml/docs/copyright.xml">

Teraz zdefiniujmy typy dozwolone dla poszczególnych atrybutów.

Typy atrybutów

Wartością wielu atrybutów będą dane tekstowe. To najprostszy typ wartości atrybutu, ale też naj­mniej zawężony. Typ ten określamy za pomocą słowa kluczowego CDATA (Character Data — da­ne tekstowe). Jest to ta sama konstrukcja CDATA, która w samym dokumencie XML reprezentuje dane wchodzące w skład „sekwencji unikowej”. Typ ten jest zazwyczaj używany wtedy, gdy atry­but może przyjąć dowolną wartość i służy jako komentarz lub dodatkowa informacja o danym elemencie. Wkrótce Czytelnik przekona się, że lepszym rozwiązaniem jest zdefiniowanie zestawu wartości dozwolonych dla atrybutu danego elementu. W naszym dokumencie typem tekstowym jest atrybut xmlns. Co prawda xmlns to słowo kluczowe XML oznaczające deklarację prze­strzeni nazw, ale wciąż jest to atrybut, którego poprawność ma zostać sprawdzona — i musi zostać opisany w definicji DTD. Atrybut podRozdzialy elementu JavaXML:Temat przyjmuje także wartość tekstową:

<!ATTLIST JavaXML:Ksiazka

--> xmlns:JavaXML CDATA [modyfikator][Author:AJ]

>

<!ELEMENT JavaXML:Tytul (#PCDATA)>

<!ELEMENT JavaXML:Spis ((JavaXML:Rozdzial+)|

(JavaXML:Rozdzial+, JavaXML:PodzialSekcji?)+)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

<!ATTLIST JavaXML:Rozdzial

tematyka [typ] [modyfikator]

>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ATTLIST JavaXML:Temat

--> podRozdzialy CDATA [modyfikator][Author:AJ]

>

Następny typ atrybutu (jeden z najczęściej używanych) to wyliczenie. Umożliwia określenie kon­kretnych wartości atrybutu. Wartości nie wymienione w wyliczeniu powodują, że dokument XML jest niepoprawny. Rozwiązanie to przydaje się wszędzie tam, gdzie zestaw wartości atrybutu zna­my już w czasie tworzenia dokumentu i umożliwia on bardzo ścisłe zawężenie definicji elementu. Taki typ ma nasz atrybut tematyka, ponieważ dozwolone są dla niego jedynie wartości „Java” lub „XML”. Wartości dozwolone umieszczane są w nawiasach i rozdzielane znakiem operatora „lub”, podobnie jak przy określaniu zagnieżdżania elementów:

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

<!ATTLIST JavaXML:Rozdzial

tematyka (XML|Java) [modyfikator]

>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

Być albo nie być ...

W definicji atrybutu należy jeszcze określić, czy atrybut ten jest w danym elemencie wymagany. Słu­żą do tego trzy słowa kluczowe: #IMPLIED, #REQUIRED lub #FIXED. Atrybut niejawny (ang. implied) nie jest wymagany. Takim modyfikatorem można określić atrybut pod­Roz­dzia­ly, jako że nie jest on wymagany do stworzenia poprawnego dokumentu:

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ATTLIST JavaXML:Temat

podRozdzialy CDATA #IMPLIED

>

Jeśli chodzi o atrybut xmlns, to chcemy zagwarantować, żeby autor zawartości zawsze określał prze­strzeń nazw dla danej książki. W przeciwnym razie nasze przedrostki określające przestrzeń nazw na nic by się nie zdały. W takim przypadku należy zastosować słowo #REQUIRED (wy­magany). Gdyby atrybut ten nie został zawarty w elemencie JavaXML:Ksiazka, dokument nie byłby poprawny:

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ATTLIST JavaXML:Ksiazka

xmlns:JavaXML CDATA #REQUIRED

>

Ostatnie słowo kluczowe, #FIXED, nie jest zbyt często używane w aplikacjach. Najczęściej sto­suje się je w systemach bazowych; określa ono, że użytkownik nie może nigdy zmienić wartości danego atrybutu. Format jest tutaj następujący:

<!ATTLIST [Nazwa elementu]

[Nazwa atrybutu] #FIXED [Stala wartosc]

>

Ponieważ atrybut ten nie przydaje się w aplikacjach dynamicznych (nie jest możliwa jego zmiana), nie będziemy się nim tutaj zajmować.

Do omówienia pozostał jeszcze atrybut tematyka. Wymieniliśmy wszystkie możliwe wartości, ja­kie może przyjmować, ale ponieważ w tej książce koncentrujemy się głównie na Javie, chcie­libyśmy, aby autor nie musiał jawnie definiować atrybutu jako „Java” w tych rozdziałach, w któ­rych rzeczywiście Java jest głównym tematem. W książce składającej się z dwudziestu czy trzydziestu rozdziałów ciągłe wpisywanie atrybutu może okazać się męczące. Wyobraźmy sobie listę książek w bibliotece naukowej, w której jako temat każdej książki należałoby podać „nauka”! Duplikowanie danych wydłuża proces, a więc wymaganie podawania atrybutu za każdym razem nie jest dobrym rozwiązaniem. Chcemy więc przypisać atrybutowi pewną domyślną wartość, kie­dy żadna wartość nie zostanie podana przez autora (i nie załatwia tego słowo #IMPLIED, które powoduje, że atrybutowi nie zostaje przypisana żadna wartość). Domyślną wartość można określić poprzez wpisanie jej w cudzysłowach zamiast wpisania słowa kluczowego modyfikatora. Jeśli ty­pem atrybutu jest wyliczanie (enumeration), wartość ta powinna być jednym z wymienionych łańcuchów znaków. Zdefiniujmy więc nasz atrybut tematyka:

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

<!ATTLIST JavaXML:Rozdzial

tematyka (XML|Java) "Java"

>

Nasza definicja DTD jest już gotowa! Składnia definicji DTD może wydawać się nieco dziwaczna, ale mam nadzieję, że Czytelnikowi nie sprawiło trudności śledzenie kolejnych etapów definiowania elementów, atrybutów i encji. Nie omówiliśmy oczywiście wszystkich zagadnień związanych z definicjami DTD, ponieważ niniejsza książka nie traktuje o samym XML-u, ale o Javie i XML-u. Mam jednak nadzieję, że przykładowa definicja DTD jest zrozumiała i że Czytelnik będzie potrafił stworzyć proste definicje dla własnych dokumentów. Zanim przejdziemy do omawiania schematów, spójrzmy raz jeszcze na gotową definicję DTD (przykład 4.11).

Przykład 4.11. Gotowa definicja DTD

<!ELEMENT JavaXML:Ksiazka (JavaXML:Tytul,

JavaXML:Spis,

JavaXML:Copyright)>

<!ATTLIST JavaXML:Ksiazka

xmlns:JavaXML CDATA #REQUIRED

>

<!ELEMENT JavaXML:Tytul (#PCDATA)>

<!ELEMENT JavaXML:Spis ((JavaXML:Rozdzial+)|

(JavaXML:Rozdzial+, JavaXML:PodzialSekcji?)+)>

<!ELEMENT JavaXML:Rozdzial (JavaXML:Naglowek?,JavaXML:Temat+)>

<!ATTLIST JavaXML:Rozdzial

tematyka (XML|Java) "Java"

>

<!ELEMENT JavaXML:Naglowek (#PCDATA)>

<!ELEMENT JavaXML:Temat (#PCDATA)>

<!ATTLIST JavaXML:Temat

podRozdzialy CDATA #IMPLIED

>

<!ELEMENT JavaXML:PodzialSekcji EMPTY>

<!ELEMENT JavaXML:Copyright (#PCDATA)>

<!ENTITY OReillyCopyright SYSTEM

"http://www.oreilly.com/catalog/javaxml/docs/copyright.xml">

Po dokładnym przyjrzeniu się tej definicji DTD można uznać, że jest ona zbyt złożona. Struktura definicji DTD rządzących organizacją plików XML zupełnie nie przypomina samych plików XML. Struktura DTD jest odmienna od struktury schematu, arkusza stylu XSL i niemal każdego innego dokumentu związanego z językiem XML. Definicje DTD opracowano jako część spe­cy­fi­kacji XML 1.0. Niektóre decyzje projektowe podjęte w ramach tej specyfikacji odbiły się piętnem na użytkownikach i programistach korzystających z XML. Podstawy definicji DTD w standardzie XML oparte są w dużej części na SGML-u — specyfikacji o wiele starszej. Jednakże struktura definicji DTD z czasów SGML-a stanowi niezbyt dobry wybór w odniesieniu do XML-a. Te róż­nice strukturalne stara się naprawić standard XML Schema, który skoncentrowany jest właśnie na XML-u i nie narusza stylu poprawnego programowania. Schematy XML Schema zostaną wkrótce omówione. Najprawdopodobniej schematy całkowicie zastąpią definicje DTD, ale zastępowanie to przebiega powoli — w wielu aplikacjach XML został już wdrożony w systemach produkcyjnych, w których są używane dokumenty zawężane definicjami DTD. To dlatego właśnie zrozumienie de­finicji DTD jest tak istotne — nawet jeśli mają one odejść do lamusa.

Czego brak w definicji DTD?

Co ciekawe, aż całą sekcję poświęcić trzeba sprawom, które nie zostały ujęte w definicji DTD. W DTD podać trzeba wszystkie elementy i zdefiniować wszystkie atrybuty dokumentu XML, ale nie muszą się tam znaleźć instrukcje przetwarzania (PI). Co więcej, w ogóle nie ma możliwości określenia instrukcji PI, podobnie jak deklaracji XML znajdującej się na samym początku doku­mentu XML. Definicja DTD rozpoczyna się wraz z pierwszym wystąpieniem pierwszego ele­men­tu w pliku XML. Takie podejście może się wydawać naturalne — po co określać, że dokument ma zawierać taką, a nie inną instrukcję przetwarzani?. Odpowiedzią jest przenośność.

Są powody, dla których określanie instrukcji przetwarzania przydałoby się w definicjach DTD. Na przykład możliwa jest sytuacja, w której autor zawartości chciałby mieć gwarancję, że jego do­ku­ment XML zostanie zawsze przekształcony — a więc wymaga instrukcji opisującej arkusz stylu xml-stylesheet. Ale który arkusz chcemy wykorzystać? To trzeba również określić. A jaki mechanizm ma wykonać przekształcenia? Cocoon? Servlet Jamesa Clarka? Inna struktura? To wszystko również trzeba zdefiniować. Jednak kiedy wszystko zostanie dokładnie określone, doku­ment utraci przenośność. Będzie mógł być wykorzystany już tylko do jednego konkretnego celu i w jednej specyficznej strukturze publikacji. Nie będzie go łatwo przenieść na inną platformę, strukturę czy aplikację. Dlatego instrukcje PI i deklaracje XML pozostają niezawężone w ramach definicji DTD. Zajmujemy się tylko elementami i atrybutami dokumentu, począwszy od elementu głównego.

XML Schema

XML Schema to nowy projekt roboczy grupy W3C, uwzględniający dotychczasowe problemy i ograniczenia definicji DTD. Umożliwia on bardziej precyzyjną reprezentację zawężeń struktury XML i, co istotne, wykorzystuje w tym celu sam język XML. Schematy to właściwie dokumenty XML — dobrze sformatowane i poprawne. Mogą więc być obsługiwane przez parsery i inne apli­kacje „znające XML” w sposób podobny jak zwykłe dane XML. Nie trzeba stosować specjalnych technik, jak przy obsłudze definicji DTD.

Ponieważ XML Schema to jeszcze „młoda” i niepełna specyfikacja, zostanie omówiona pokrótce. Szczegóły implementacji mogą w każdej chwili ulec zmianie; jeśli więc Czytelnik ma problemy z omawianymi przykładami, może przejrzeć najnowszą wersję projektu pod adresami http://www.­w3.org/TR/xmlschema-1/ i http://www.w3.org/TR/xmlschema-2/. Należy także pamiętać, że wiele parserów XML nie obsługuje schematów XML, bądź też obsługuje je tylko częściowo. Stopień ob­­słu­gi XML Schema opisany jest w instrukcji parsera.

Istnieje różnica pomiędzy dokumentem poprawnym a dokumentem poprawnym z punktu widzenia schematu. XML Schema nie stanowi części specyfikacji XML 1.0, a więc dokument zgodny z da­nym schematem może być niepoprawny. Tylko dokument XML zgodny z definicją DTD okreś­lo­ną w deklaracji DOCTYPE może być uważany za poprawny. W związku z tym w środowisku użyt­ko­wników XML-a rodzą się wątpliwości dotyczące sposobu obsługi sprawdzania poprawności poprzez XML Schema. Ponadto, nawet jeśli dokument posiada odpowiadający mu schemat, to nie znaczy jeszcze, że jego poprawność będzie sprawdzana — ponieważ XML Schema nie jest opi­sany w specyfikacji XML 1.0, aplikacja lub parser wcale nie muszą dokonywać takiego spraw­dza­nia (bez względu na poziom obsługi schematów przez parser). Dlatego zawsze trzeba sprawdzić, czy parser będzie sprawdzał poprawność dokumentu, oraz jak obsługuje schematy. Na potrzeby przejrzystości opisu będziemy zakładać, że sprawdzanie poprawności to jedno pojęcie, obej­mu­ją­ce zarówno schematy, jak i definicje DTD; znaczenie tego pojęcia będzie zaś musiało być inter­pre­to­wa­ne w odpowiednim kontekście. Na ewentualne dwuznaczności zawsze będziemy zwracali uwagę.

Najważniejsze przy budowaniu schematu jest to, że w trakcie tego procesu powstaje po prostu nowy dokument XML. W przeciwieństwie do definicji DTD, korzystających z zupełnie od­mien­nego formatu opisu elementów i definiowania atrybutów, schemat jest zwyczajnym dokumentem XML. Składnia nie będzie więc znacząco odbiegała od tej opisanej w rozdziale 2. Co ciekawe, sam schemat XML jest zawężony za pomocą definicji DTD. Jeśli to wydaje się nieco osobliwe, to należy przypomnieć, że przed powstaniem XML Schema jedynym sposobem zawężania doku­men­tów było właśnie zastosowanie definicji DTD. Aby poprzez XML Schema można było sprawdzać poprawność, trzeba najpierw określić zawężenia samego schematu, korzystając z „zewnętrznego mechanizmu”. A tym zewnętrznym mechanizmem musi być definicja DTD. Jednak już po stwo­rze­niu tej początkowej definicji wszystkie inne dokumenty XML w ogóle nie będą korzystały z DTD. Ta nieco dziwna kolej rzeczy jest zjawiskiem dość pospolitym w ewoluującym świecie spe­cy­fi­ka­cji — nowe wersje powstają na bazie starych.

Przestrzeń nazw schematu

Można oczekiwać, że dokumenty XML Schema rozpoczynają się standardową deklaracją XML, po czym następuje odwołanie do przestrzeni nazw schematu. I tak jest w rzeczywistości. Ponadto istnieją standardy nazywania elementu głównego. Przyjęło się, że element główny nosi nazwę schema. Przy tworzeniu elementu głównego buduje się również definicje przestrzeni nazw — po­dobnie jak to robiliśmy w naszym przykładowym dokumencie XML. Przede wszystkim konieczne jest podanie domyślnej deklaracji przestrzeni nazw:

<xsd:schema xmlns:xsd="http://www.w3.org/1999/XMLSchema">

Jak to zostało przedstawione w rozdziale 2., pominięcie identyfikatora po atrybucie xmlns po­wo­duje zastosowanie w dokumencie domyślnej przestrzeni nazw. W naszym wcześniejszym doku­mencie XML definicja przestrzeni nazw odpowiadała konkretnie przestrzeni JavaXML:

<JavaXML:Ksiazka xmlns:JavaXML="http://www.w3.org/1999/XMLSchema">

W ten sposób informowaliśmy parser XML, że wszystkie dokumenty z przedrostkiem JavaXML należą do tej konkretnej przestrzeni nazw, skojarzonej z podanym adresem URL. W naszym doku­mencie XML dotyczyło to wszystkich elementów, bo wszystkie opatrzone były tym przed­rost­kiem. Ale przecież mogły tam pojawić się także elementy bez takich przedrostków. Elementy takie nie są, oczywiście, skazane na niebyt — one również muszą zostać przypisane jakiejś przestrzeni nazw. Przypisuje się im przestrzeń domyślną, która nie jest zdefiniowana w dokumencie. Można zdefiniować ją za pomocą dodatkowej deklaracji przestrzeni nazw w elemencie głównym:

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

xmlns="http://www.jakisInnyUrl.com"

>

Spowodowałoby to, że dowolny element nie poprzedzony przedrostkiem JavaXML lub innym ko­jarzony byłby z domyślną przestrzenią nazw, identyfikowaną za pomocą adresu http://www.jakis­In­ny­Url.com. Tak więc w następującym fragmencie dokumentu elementy Ksiazka, Spis i Ty­tul skojarzono z przestrzenią JavaXML, zaś element1 i element2 z domyślną przestrzenią nazw:

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

xmlns="http://www.jakisInnyUrl.com"

>

<JavaXML:Tytul>Mój tytuł</JavaXML:Tytul>

<JavaXML:Spis>

<element1>

<element2 />

</element1>

</JavaXML:Spis>

</JavaXML:Ksiazka>

Ponieważ w naszym schemacie opisujemy inny dokument, wszystkie elementy związane z kon­stru­kcjami samego XML Schema powinny należeć do domyślnej przestrzeni nazw — i dlatego po­da­jemy na początku jej deklarację. Jednakże konstrukcje te oddziaływają na przestrzeń nazw w zawężanym dokumencie XML. Innymi słowy, konstrukcje XML Schema stanowią część prze­strzeni nazw XML Schema, ale wykorzystywane są do zawężenia elementów w innych prze­strze­niach nazw — w tych, które opisują dokument lub dokumenty XML opisywane przez ten schemat. W naszym przykładzie dotyczyłoby to przestrzeni nazw JavaXML. Tak więc do elementu sche­ma trzeba dodać następującą deklarację:

<schema xmlns="http://www.w3.org/1999/XMLSchema"

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

>

Teraz jeszcze należy „poinformować” schemat, że przedmiotem zawężania jest druga przestrzeń nazw. W tym celu należy zastosować atrybut targetNamespace (docelowa przestrzeń nazw), któ­rego działanie jest zgodne z nazwą:

<schema targetNamespace="http://www.oreilly.com/catalog/javaxml/"

xmlns="http://www.w3.org/1999/XMLSchema"

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

>

W ten sposób otrzymaliśmy definicje dwóch przestrzeni nazw (domyślna oraz JavaXML) oraz okreś­liliśmy przedmiot zawężania w postaci drugiej przestrzeni nazw (JavaXML). A skoro jest już zdefiniowany element główny, można rozpocząć nakładanie zawężeń na tę przestrzeń nazw. Na­leży pamiętać jeszcze o jednej rzeczy: w świecie protokołu HTTP i serwerów WWW możliwe jest, że adres URL skojarzony z daną przestrzenią nazw może być prawdziwym adresem; w na­szym przypadku, gdybyśmy do przeglądarki wpisali http://www.oreilly.com/catalog/javaxml, otrzy­malibyśmy odpowiedź HTML. Ale zwracany dokument tak naprawdę nie jest wykorzystywany; adres URL wcale nie musi być osiągalny — służy wyłącznie jako element kojarzony z prze­strzenią nazw. Nie zawsze jest to łatwo zrozumieć, dlatego Czytelnik nie powinien zaprzątać sobie głowy tym, na co wskazuje podany identyfikator URI; ważniejsze jest skoncentrowanie się na deklarowanej przestrzeni nazw oraz na sposobie, w jaki wykorzystywana jest ona w dokumencie.

Uwaga! Zagadnienia omawiane w dalszej części rozdziału nie są proste, więc Czytelnik nie powinien przejmować się ewentualnymi trudnościami z ich zrozumieniem. Koncepcje związane ze schematami XML Schema nie są „łatwe i przyjemne”, a cała ta specyfikacja jest dopiero w fazie rozwoju. Wielu autorów zawartości korzysta z technologii XML Schema, my zaś staramy się tutaj zrozumieć ją — to subtelna, ale istotna różnica. Dzięki zrozumieniu zagadnień związanych z XML Schema Czytelnik będzie potrafił bardziej efektywnie projektować dokumenty XML i tworzyć lepsze aplikacje. Szczególnie złożoną sprawą jest sposób wykorzystania w schematach definicji DTD i przestrzeni nazw; na szczęście większość pozostałych konstrukcji nie jest już tak skom­pli­kowana. Tak więc warto teraz zrelaksować się, napić kawy i uważnie, bez pośpiechu kontynuować lekturę. To na pewno zaowocuje w przyszłości.

Określanie elementów

Zagadnienie określania elementów zostało już poruszone przy omawianiu definicji DTD. W sche­matach określanie elementów okaże się nieco bardziej logiczne. Proces ten dość dokładnie od­zwierciedla strukturę deklaracji w Javie (plus kilka dodatkowych opcji). Do określania elementów posłużymy się elementem... element:

<element name="[Nazwa elementu]"

type="[Typ elementu]"

[Opcje...]

>

[Nazwa elementu] określa element w zawężanym dokumencie XML. Jednakże w przeci­wień­stwie do definicji DTD, nie powinno być tutaj przedrostka przestrzeni nazw. Czytelnik winien przypomnieć sobie uwagi na temat docelowej przestrzeni nazw. Zostało tam powiedziane, że prze­strzenią docelową (ang. target) jest JavaXML, a więc specyfikacje wszystkich elementów, a także typy zdefiniowane przez użytkownika, są kojarzone i przypisywane właśnie tej docelowej prze­strzeni nazw. To przyczynia się do utworzenia bardziej przejrzystego dokumentu XML Schema, ponieważ najpierw definiowane są wszystkie elementy, a potem narzuca się przestrzeń nazw. Konstrukcja [Typ elementu] to albo wstępnie zdefiniowany typ danych w schemacie, albo typ zdefiniowany przez użytkownika. W tabeli 4.4 zestawiono typy danych obsługiwane przez bieżącą wersję standardu XML Schema.

Tabela 4.4. Typy danych w XML Schema

Typ

Podtypy

Znaczenie

string

NMTOKEN, language

łańcuchy znaków

boolean

brak

binarna wartość logiczna (prawda lub fałsz)

float

brak

32-bitowy typ zmiennoprzecinkowy

double

brak

64-bitowy typ zmiennoprzecinkowy

decimal

integer

standardowy zapis dziesiętny, dodatni i ujemny

timeInstant

brak

połączenie daty i czasu, wskazujące konkretny moment w czasie

timeDuration

brak

czas trwania

recurringInstant

date, time

specyficzny czas, powtarzający się przez okres timeDuration

binary

brak

dane binarne

uri

enumeration

jednolity identyfikator zasobów (URI).

W naszych przykładach wykorzystamy tylko niektóre typy. Widać jednak od razu, że wybór jest tutaj większy niż w DTD.

Zaczynamy od dołu

Możliwe jest także tworzenie złożonych typów danych, definiowanych przez użytkownika. Typy takie budowane są z kombinacji elementów. Na przykład można określić, że typ Ksiazka składa się z elementu Tytul, Spis i Copyright (zwróćmy uwagę, że przy opisywaniu elementów nie jest już podawany przedrostek przestrzeni nazw — w schematach najpierw odczytywana jest tylko nazwa elementu, dopiero potem definiuje się odpowiednią przestrzeń). Te zaś elementy mogą znów być typami zdefiniowanymi przez użytkownika, składającymi się z dalszych elementów. Tak powstaje piramida hierarchii; na jej spodzie znajdują się elementy o podstawowych typach XML Schema; wyżej — warstwy elementów zdefiniowanych przez użytkownika; na samej górze — element główny.

Taka struktura wymusza określony sposób działania — najlepiej zaczynać budowanie schematu od określenia elementów znajdujących się u podstaw tej hierarchii — czyli tych, które można zde­fi­niować jako standardowe typy XML Schema. Sytuacja jest więc tutaj nieco inna niż w definicjach DTD, gdzie zazwyczaj postępuje się zgodnie z kolejnością elementów w dokumencie XML. Ale dzięki temu budowanie schematu jest prostsze. Jeśli spojrzymy na nasz dokument XML, to mo­żemy określić, które elementy stanowią typy „prymitywne” — (patrz tabela 4.5).

Tabela 4.5. Elementy „prymitywne”

Nazwa elementu

Typ

Tytul

string

Naglowek

string

Temat

string

Teraz elementy te można wpisać do schematu (przykład 4.12). Aby budowany schemat nie utracił przejrzystości, zostaną w nim pominięte deklaracje XML i DOCTYPE; znajdą się one w wersji ostatecznej, ale teraz tylko zmniejszałyby czytelność przykładów.

Przykład 4.12. Schemat z elementami „prymitywnymi”

<schema targetNamespace="http://www.oreilly.com/catalog/javaxml/"

xmlns="http://www.w3.org/1999/XMLSchema"

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

>

<element name="Tytul" type="string" />

<element name="Naglowek" type="string" />

<element name="Temat" type="string" />

</schema>

Zbyt proste? Cóż, to jest proste. Po zdefiniowaniu tych „podstawowych”, czy też „prymitywnych” elementów możemy przejść do budowania elementów bardziej złożonych.

Typy danych definiowane przez użytkownika

Podobnie jak w przypadku elementów niepodzielnych, elementy bardziej złożone należy budować także „od dołu” piramidy. Niemal zawsze oznacza to, że programista rozpoczyna tworzenie od ele­mentów najgłębiej zagnieżdżonych i posuwa się „w górę”, aż do elementu głównego. W naszym przypadku najbardziej zagnieżdżone są Naglowek i Temat. Ponieważ elementy te już zostały określone jako prymitywne, można przenieść się o poziom wyżej — do elementu Rozdzial. To właśnie będzie pierwszy element zdefiniowany przez użytkownika. W definicji musi zostać za­war­ta informacja, że składa się on z jednego elementu Naglowek oraz jednego lub więcej elementów Temat. Typy złożone definiujemy za pomocą elementu complexType:

<complexType name="[Nazwa typu">

<[Specyfikacja elementu]>

<[Specyfikacja elementu]>

...

</complexType>

Naszemu elementowi przypisujemy nowy typ. Dla elementu Rozdzial stworzymy typ Typ­Rozdzial:

<complexType name="TypRozdzial">

...

</complexType>

Powstały w ten sposób typ staje się, oczywiście, częścią docelowej przestrzeni nazw JavaXML. Tak więc aby przypisać ten typ naszemu elementowi Rozdzial, użyjemy następującej specy­fi­kacji dokumentu:

<element name="Rozdzial" type="JavaXML:TypRozdzial" />

Teraz, jakąkolwiek strukturę określimy wewnątrz typu TypRozdzial, będzie ona zawężała ele­ment Rozdzial. Należy zauważyć, że typ elementu określany jest jako JavaXML:Typ­Rozdzial, a nie po prostu jako TypRozdzial. Typ ten był tworzony w ramach docelowej prze­strzeni nazw, JavaXML. Jednak elementy, jakie wykorzystujemy w schemacie (element, complexType itd.), nie mają przedrostka przestrzeni nazw, ponieważ należą do przestrzeni do­myślnej schematu. Gdybyśmy więc próbowali określić typ jako TypRozdzial, parser przeszu­kałby przestrzeń domyślną (tę odnoszącą się do schematu), a nie znajdując tam naszego elementu, zgłosiłby wyjątek. Aby parser „wiedział”, gdzie szukać definicji typu, należy wskazać poprawną przestrzeń nazw — w tym przypadku JavaXML.

Ogólną strukturę Czytelnik już zna, pora zająć się szczegółami. W przypadku tego elementu trzeba zdefiniować w schemacie dwa elementy, które są w tym typie zagnieżdżane. Ponieważ już zostały określone dwa zagnieżdżane elementy (prymitywne Naglowek i Temat), należy się do nich do­wo­łać w nowym typie:

<complexType name="TypRozdzial">

<element ref="JavaXML:Naglowek" />

<element ref="JavaXML:Temat" />

</complexType>

Atrybut ref informuje parser XML, że definicja danego elementu znajduje się w innej części sche­matu. Tak jak przy określaniu typu i tutaj również trzeba wskazać parserowi przestrzeń nazw, w jakiej znajdują się elementy (zazwyczaj jest to przestrzeń docelowa). Ale można tutaj dopatrzyć się pew­nej nadmiarowości i „przegadania”. Dwa elementy definiujemy jako prymitywy, a potem się do nich odwołujemy — to daje cztery linijki. Elementy te jednak nie są wykorzystywane nig­dzie in­dziej w naszym dokumencie, a więc czy nie logiczniej byłoby zdefiniować element od razu we­w­nątrz typu? Nie trzeba byłoby wtedy odwoływać się do elementu i wymuszać na czytających nasz schemat przeszukiwanie dokumentu w celu odnalezienia elementu wykorzystanego tylko raz. Tak można zrobić. Specyfikacja elementu może zostać zagnieżdżona w typie zdefiniowanym przez użyt­kownika, a więc nieco przerobimy teraz nasz schemat — tak, aby był bardziej czytelny:

<element name="Tytul" type="string" />

<element name="Rozdzial" type="JavaXML:TypRozdzial" />

<complexType name="TypRodzial">

<element name="Naglowek" type="string" />

<element name="Temat" type="string" />

</complexType>

Oprócz usunięcia niepotrzebnych wierszy, pozbyliśmy się także niepotrzebnych odwołań do prze­strzeni nazw JavaXML. To na pewno zwiększy czytelność, szczególnie wśród odbiorców nie mających doświadczenia w pracy z XML-em. Znając sposób tworzenia typów definiowanych przez użytkownika, możemy teraz określić pozostałe elementy, tak jak w przykładzie 4.13.

Przykład 4.13.

<schema targetNamespace="http://www.oreilly.com/catalog/javaxml/"

xmlns="http://www.w3.org/1999/XMLSchema"

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

>

<element name="Ksiazka" type="JavaXML:TypKsiazka" />

<complexType name="TypKsiazka">

<element name="Tytul" type="string" />

<element name="Spis" type="JavaXML:TypSpis" />

<element name="Copyright" type="string" />

</complexType>

<complexType name="TypSpis">

<element name="Rozdzial" type="JavaXML:TypRozdzial" />

<element name="PodzialSekcji" type="string" />

</complexType>

<complexType name="TypRozdzial">

<element name="Naglowek" type="string">

<element name="Temat" type="string">

</complexType>

</schema>

Dzięki temu każdy użyty element XML zostanie zdefiniowany, a schemat będzie czytelny. Nie znaczy to jednak, że wszystkie problemy zostały rozwiązane.

Typy niejawne i pusta zawartość

Do tej pory korzystaliśmy wyłącznie z typów nazwanych, często określanych mianem jawnych. Typ jawny to taki, w którym podano nazwę typu; element wykorzystujący ten typ zazwyczaj znaj­duje się w innym miejscu pliku. To bardzo obiektowe podejście — ten sam typ jawny może być zastosowany dla różnych elementów. Jednakże istnieją sytuacje, kiedy taka struktura jest roz­wią­zaniem na wyrost; innymi słowy, typ jest tak specyficzny względem elementu, że jego nazywanie nie jest potrzebne. W naszym przykładzie definicję elementu Rozdzial moglibyśmy skon­so­li­do­wać poprzez zdefinio­wa­nie typu w samej definicji elementu. Służą do tego typy niejawne, zwane także nienazwanymi:

<complexType name="TypSpis">

<element name="Rozdzial">

<complexType>

<element name="Naglowek" type="string" />

<element name="Temat" type="string" />

</complexType>

</element>

<element name="PodzialSekcji" type="string" />

</complexType>

Zastosowanie typu niejawnego powoduje, że schemat jest jeszcze bardziej czytelny. Jednakże ża­den inny element nie może być tego samego typu, co zdefiniowany w ten sposób typ niejawny — chyba że zdefiniowano kolejny typ niejawny. Innymi słowy, typów niejawnych używamy tylko wtedy, gdy jesteśmy zupełnie pewni, że typ ten nie będzie wykorzystywany przez wiele elementów.

Typy niejawne mogą także służyć do podawania informacji o definiowanych elementach. Na przy­kład, do tej pory definiowaliśmy element PodzialSekcji jako string. Faktycznie nie jest to określenie zbyt precyzyjne, ponieważ element ten ma być pusty. Aby określić element jako pusty, użyjemy typu niejawnego:

<element name="PodzialSekcji">

<complexType content="empty" />

</element>

To może wydawać się dziwne — dlaczego nie można po prostu przypisać temu elementowi „pus­tego” (ang. empty) typu danych? Czy twórcy specyfikacji XML Schema coś przeoczyli? Wręcz przeciwnie — we wcześniejszych wersjach specyfikacji istniał typ danych empty, ale go usu­nięto. Zrobiono tak, aby wymóc definicje typu elementu. Aby to zrozumieć, zauważmy, że większość pustych elementów może przyjmować atrybuty do określania danych:

<img src="obrazki/takiGif.gif" />

<komentarz tekst="A tu komentarz" />

W takich przypadkach określenie elementu jako empty nie pozwoliłoby na intuicyjne określenie, jakie atrybuty są w nim dozwolone. Sprawa jest natomiast prosta, jeśli użyjemy typu elementu:

<element name="img">

<complexType content="empty">

<attribute name="src" type="string" />

</complexType>

</element>

Sposoby definiowania atrybutów zostaną omówione dalej. Tymczasem warto pamiętać, że typy nie­jawne pomagają budować schemat w sposób bardziej intuicyjny oraz umożliwiają definiowanie właściwości elementu — np. tego, że jest pusty.

Ile?

Oprócz powyższych, w definicji elementu należy określić rodzaj rekurencji (lub zdefiniować jej brak). Schemat zachowuje się tutaj podobnie jak DTD — jeśli elementu nie opatrzono mody­fi­ka­to­rami, może pojawić się tylko raz. Jak już wcześniej wspomniano, nie zawsze akurat tak musi być. Nasza książka może mieć wiele rozdziałów, ale może nie mieć podziału sekcji; niektóre roz­dzia­ły mogą mieć nagłówki, a inne nie. Musimy umieć zdefiniować te wszystkie szczegóły w naszym sche­ma­cie. Podobnie jak w definicji DTD, służy do tego odpowiedni mechanizm. Ale w przeci­wień­stwie do DTD stworzono intuicyjny zestaw atrybutów (zamiast nieco zagadkowych opera­to­rów rekurencji typu ?, + czy *). W XML Schema wykorzystuje się atrybuty minOccurs i max­Occurs:

<element name="[Nazwa elementu]"

type="[Typ elementu]"

minOccurs="[Ile razy najmniej może się pojawić]"

maxOccurs="[Ile razy najwięcej może się pojawić]"

>

Jeśli atrybuty te nie zostaną określone, przyjmują domyślną wartość 1 — czyli jeden dozwolony element na definicję. Jeśli maksymalna wartość nie jest określona, wykorzystuje się symbol wie­lo­zna­czny. Konstrukcje te pozwalają w prosty sposób narzucić zawężenia związane z rekurencją (przykład 4.14).

Przykład 4.14. Schemat XML z definicjami elementów

<schema targetNamespace="http://www.oreilly.com/catalog/javaxml/"

xmlns="http://www.w3.org/1999/XMLSchema"

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

<element name="Ksiazka" type="JavaXML:TypKsiazka" />

<complexType name="TypKsiazka">

<element name="Tytul" type="string" />

<element name="Spis" type="JavaXML:TypSpis" />

<element name="Copyright" type="string" />

</complexType>

<complexType name="TypSpis">

<element name="Rozdzial" maxOccurs="*">

<complexType>

<element name="Naglowek" type="string" minOccurs="0" />

<element name="Temat" maxOccurs="*">

</complexType>

</element>

<element name="PodzialSekcji" minOccurs="0" maxOccurs="*">

<complexType content="empty" />

</element>

</complexType>

</schema>

Zdefiniowaliśmy jeden element główny, Ksiazka, typu TypKsiazka. Element ten zawiera trzy bezpośrednio potomne elementy: Tytul, Spis i Copyright. Z tych trzech dwa są pry­mi­tyw­ny­mi łańcuchami XML, a trzeci (Spis) to kolejny typ zdefiniowany przez użytkownika, Typ­Spis. Typ ten zawiera z kolei element potomny Rozdzial, który może pojawić się raz lub więcej razy, oraz element potomny PodzialSekcji, który wcale nie musi się pojawić. Element Rozdzial zawiera dwa elementy zagnieżdżone, Naglowek i Temat. Każdy z nich jest pry­mi­tywnym łańcuchem XML; Naglowek może pojawić się zero lub więcej razy, a Temat jeden lub więcej razy. Element PodzialSekcji może wystąpić zero lub więcej razy i jest elementem pu­stym. Teraz w schemacie zdefiniowane są już wszystkie elementy — pozostaje dodać atrybuty.

Definiowanie atrybutów

Proces definiowania atrybutów jest o wiele prostszy od definiowania elementów, przede wszy­stkim dlatego, że nie trzeba zwracać uwagi na tyle spraw, co w przypadku elementów. Domyślnie atrybut nie musi występować, a zagnieżdżanie w ogóle atrybutów nie dotyczy. Choć istnieje wiele zaawansowanych konstrukcji, których można użyć do zawężania atrybutów, my przyjrzymy się tylko tym podstawowym, koniecznym do zawężenia omawianego dokumentu XML. W razie po­trzeby zastosowania tych trudniejszych konstrukcji Czytelnik powinien zajrzeć do specyfikacji XML Schema.

Czego brakuje?

Przy zawężaniu atrybutów na potrzeby dokumentu XML wchodzą w grę pewne pominięcia; wszy­stkie mają związek z różnymi definicjami przestrzeni nazw opisywanego dokumentu. Dokument XML, jak to już było powiedziane, musi zawierać definicje przestrzeni nazw w odniesieniu do sche­matu oraz definicje przestrzeni nazw „na własne potrzeby” — tj. odnoszące się do jego za­war­tości. Definicje te tworzy się za pomocą atrybutu xmlns:[PrzestrzenNazw] elementu głów­ne­go. Atrybuty te nie powinny być definiowane w schemacie. Próba zdefiniowania każdej doz­wo­lonej prze­strzeni nazw dałaby bardzo zagmatwany schemat. Położenie deklaracji przestrzeni nazw nie musi być stałe; można ją przemieszczać, o ile tylko pozostaje dostępna dla wszystkich od­po­wie­dnich elementów. Dlatego w schemacie zezwala się na pominięcie definicji atrybutów prze­strzeni nazw.

Pora przypomnieć sobie wiadomości dotyczące definicji DTD. Aby zezwolić na deklaracje prze­strzeni nazw w dokumencie XML, trzeba było wstawić taką definicję atrybutu jak poniżej:

<!ATTLIST JavaXML:Ksiazka

xmlns:JavaXML CDATA #REQUIRED

>

Aby można było korzystać z DTD, wystarczyło określić przestrzeń nazw w danym dokumencie XML, bo definicje DTD nie posiadają „świadomości” istnienia przestrzeni nazw w XML. W sche­ma­tach XML Schema sprawa jest trochę bardziej skomplikowana.

Czytelnik wie już, że istnieją trzy różne atrybuty służące do określenia schematu dla dokumentu:

<?xml version="1.0"?>

<ksiazkaAdresowa xmlns:xsi="http://www.w3.org/1999/XMLSchema/instance"

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

xsi:schemaLocation="http://www.oreilly.com/catalog/javaxml/

mySchema.xsd"

Gdyby Czytelnik miał napisać schemat w oparciu o poznane dotąd wiadomości o DTD, praw­do­po­do­bnie zadeklarowałby, że xmlns:xsi, xmlns i xsi:schema-Location to poprawne atry­bu­ty elementu głównego. Jednakże deklaracje te można pominąć, ponieważ schematy są „świadome” istnienia przestrzeni nazw i wystarczająco „inteligentne”, żeby nie wymagać wstawiania takich deklaracji w zawężanym dokumencie XML.

Definicja

Atrybuty definiuje się za pomocą elementu XML Schema attribute (ciekawe, prawda?). In­nymi słowy, oprócz elementu element, w schematach zdefiniowany jest element attribute, za pomocą którego określa się atrybuty dozwolone dla bieżącej definicji elementu lub typu. Oto format tego elementu:

<attribute nazwa="[Nazwa atrybutu]"

typ="[Typ atrybutu]"

[Opcje atrybutu]

>

Format ten jest podobny do definicji elementów — właściwie jest niemal identyczny. Przy defi­nio­waniu atrybutów dostępne są te same typy danych, co przy definiowaniu elementów. Oznacza to, że dodanie atrybutów do naszego schematu nie będzie przedstawiało trudności. Dla każdego ele­mentu posiadającego zdefiniowany typ dodamy wymagane atrybuty wewnątrz definicji tego typu. W przypadku elementów nie posiadających zdefiniowanego typu musimy typ taki dodać. W ten sposób „poinformujemy” schemat, że atrybuty, które deklarujemy, „należą” do opisywa­ne­go typu elementu. W tych nowych typach elementów można określić typ zawartości za pomocą atry­butu content elementu complexType — zachowując oryginalne zawężenia i dodając de­fi­nicje atrybutów. Po tych zmianach uzyskamy schemat taki jak w przykładzie 4.15.

Przykład 4.15. Schemat XML z definicjami atrybutów

<schema targetNamespace="http://www.oreilly.com/catalog/javaxml/"

xmlns="http://www.w3.org/1999/XMLSchema"

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

<element name="Ksiazka" type="JavaXML:TypKsiazka" />

<complexType name="TypKsiazka">

<element name="Tytul" type="string" />

<element name="Spis" type="JavaXML:TypSpis" />

<element name="Copyright" type="string" />

</complexType>

<complexType name="TypSpis">

<element name="Rozdzial" maxOccurs="*">

<complexType>

<element name="Naglowek" type="string" minOccurs="0" />

<element name="Temat" maxOccurs="*">

<complexType content="string">

<attribute name="podRozdzialy" type="integer" />

</complexType>

</element>

<attribute name="tematyka" type="string" />

</complexType>

</element>

<element name="PodzialSekcji" minOccurs="0" maxOccurs="*">

<complexType content="empty" />

</element>

</complexType>

</schema>

Po przyjrzeniu się elementowi Temat Czytelnik z pewnością zauważy, że na potrzeby zdefi­nio­wa­nia atrybutu podRozdzialy konieczne jest stworzenie nowego typu. Wewnątrz tego typu, za pomocą atrybutu content, wymagamy, aby atrybut był typu integer. Z tego samego me­cha­nizmu korzystaliśmy wcześniej, przy przypisywaniu elementowi PodzialSekcji typu empty — w ten sposób zapewnialiśmy, że element ten pozostanie pusty. Pozostałe dodane atrybuty nie wymagają już tak wielu modyfikacji, bo odpowiednie typy dla złożonych elementów już istniały.

Atrybuty wymagane, wartości domyślne i wyliczenia

Pozostaje już tylko „dostroić” definicje atrybutów. W definicjach DTD, w celu określenia, czy atry­buty mają się pojawić oraz czy posiadają wartości domyślne (jeśli ich nie podano), uży­wa­liśmy słów kluczowych #IMPLIED, #FIXED i #REQUIRED. Podobnie jak w przypadku ope­ra­torów rekurencji w elementach, w schematach zdefiniowano prostszy sposób zapisu tego samego. Jeśli atrybut jest wymagany, wykorzystuje się ten sam atrybut minOccurs, który używany był przy definiowaniu elementów, przypisując mu wartość 1. W naszym przykładzie chcemy zde­fi­niować, że w elemencie Rozdzial istnieje atrybut o nazwie sekcja. Użyjemy takiego zapisu:

<attribute name="sekcja" type="string" minOccurs="1" />

Jak wspomnieliśmy, domyślnie każdy element wymagany jest w jednym egzemplarzu (domyślna wartość minOccurs to 1). Atrybuty natomiast nie są wymagane (minOccurs ma wartość do­myślną 0).

W XML Schema nie istnieje pojęcie wartości stałej atrybutów (#FIXED); jak to zostało powie­dzia­ne wcześniej, wartość ta jest wykorzystywana rzadko i nie stanowi zapisu „intuicyjnego”. Przy­daje się za to możliwość określenia wartości domyślnej i służy do tego atrybut default. Na przykład, aby określić domyślną wartość atrybutu tematyka elementu Rozdzial jako „Java”, sko­rzystamy z zapisu:

<attribute nazwa="tematyka" type="string" default="Java" />

Mam nadzieję, że Czytelnik zdążył już polubić prostotę XML Schema! Jest to standard bardziej intuicyjny i przez to prostszy niż DTD. Kolejnym dowodem na to są wyliczenia.

Dla atrybutu tematyka określiliśmy za pomocą DTD tylko dwie możliwe wartości: Java i XML. Skorzystaliśmy z operatora LUB (OR):

<!ATTLIST JavaXML:Rozdzial

tematyka (XML|Java) "Java"

>

Nie jest to może zapis trudny, ale nie jest też najbardziej intuicyjny. Dozwolone wartości nie są na­wet umieszczane w cudzysłowach — a to jest właśnie de facto standard reprezentacji wartości. W XML Schema, aby uzyskać to samo, trzeba napisać więcej — ale za to zrozumienie zapisu jest o wiele pro­stsze. Należy otworzyć definicję atrybutu, po czym skorzystać z elementu simpleType. Ele­ment ten umożliwia zawężenie istniejącego typu danych (np. string) do podanych wartości. W tym przypadku zależy nam na podaniu dwóch wartości (wyliczenia) i każdą z nich określimy za po­mocą elementu enumeration. Typ bazowy elementu określimy za pomocą słowa kluczo­we­go base. Zmieńmy więc definicję atrybutu tematyka:

<attribute name="tematyka" default="Java">

<simpleType base="string">

<enumeration value="XML" />

<enumeration value="Java" />

</simpleType>

</attribute>

Tak, tekstu jest więcej niż w przypadku bliźniaczej konstrukcji w DTD, a jednak powyższy zapis jest prostszy w zrozumieniu, szczególnie dla nowych użytkowników standardu XML. Po wpro­wa­dzeniu tej zmiany nasz schemat jest już gotowy; zawarto w nim wszystkie zawężenia, które są zamieszczone w stworzonej wcześniej definicji DTD. Spójrzmy na przykład 4.16.

Przykład 4.16. Gotowy dokument XML Schema

<?xml version="1.0"?>

<schema targetNamespace="http://www.oreilly.com/catalog/javaxml/"

xmlns="http://www.w3.org/1999/XMLSchema"

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

<element name="Ksiazka" type="JavaXML:TypKsiazka" />

<complexType name="TypKsiazka">

<element name="Tytul" type="string" />

<element name="Spis" type="JavaXML:TypSpis" />

<element name="Copyright" type="string" />

</complexType>

<complexType name="TypSpis">

<element name="Rozdzial" maxOccurs="*">

<complexType>

<element name="Naglowek" type="string" minOccurs="0" />

<element name="Temat" maxOccurs="*">

<complexType content="string">

<attribute name="podRozdzialy" type="integer" />

</complexType>

</element>

<attribute name="tematyka" default="Java">

<simpleType base="string">

<enumeration value="XML" />

<enumeration value="Java" />

</simpleType>

</attribute>

</complexType>

</element>

<element name="PodzialSekcji" minOccurs="0" maxOccurs="*">

<complexType content="empty" />

</element>

</complexType>

</schema>

Co dalej?

W niniejszym rozdziale Czytelnik poznał dwa sposoby zawężania dokumentów XML: zawężanie z wykorzystaniem definicji DTD oraz sposób nowszy — zawężanie z wykorzystaniem XML Sche­ma. Czytelnik zapewne dostrzegł już, jak ważne jest zawężanie dokumentów, szczególnie jeśli ma­ją one być wykorzystane przez aplikacje. Jeśli aplikacja nie rozpoznaje typu informacji zawartych w dokumencie, manipulacja i przetwarzanie takich danych stają się o wiele trudniejsze. W na­stępnym rozdziale zostaną omówione klasy interfejsu SAX. Czytelnik dowie się, jak z programu w Javie uzyskać dostęp do definicji DTD oraz schematów. Do parsera dodamy przykładowy pro­gram, który zbudowaliśmy w rozdziale 3. — będzie on odczytywał zawężenia dokumentu i zwra­cał błędy, jeśli dokument XML nie będzie poprawny; będzie również wykorzystywał odwołania wste­cz­ne dostępne w procesie sprawdzania poprawności składni.

116 Rozdział 4. Zawężanie danych XML

XML Schema 115

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

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

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

W.D.: Bold powinien być w linii xmlns (39) i w linii podRozdziały CDATA (52).

W.D.: jw.



Wyszukiwarka

Podobne podstrony:
535 0a56c Art 10 orto 04 08 czamara
IS wyklad 04 23 10 08 MDW
04 08 e 04 10 EPATITI VIRALI
535 0a56c Art 10 orto 04 08 czamara
2009 04 08 POZ 06id 26791 ppt
04 08 Lowiectwo cw7
Harmonogram ćwiczeń s5 2014 TABL 03 (08 10 14 )
umowy cywilnoprawne 25.04.08, Administracja UKSW Ist, umowy cywilnoprawne w administracji
Cw 08 10 Badania epidemiologiczne
F 04 08 Release Notes
04 08 belki i ramy zadanie 08id 4924
04 08 Lowiectwo cw1
01 04 08 sem VIid 2717
ag kolokwium 21 04 08 rozwiazania
umowy cywilnoprawne 04.04.08, Administracja UKSW Ist, umowy cywilnoprawne w administracji
monter konstrukcji budowlanych 712[04] z2 10 n

więcej podobnych podstron