XML – ćwiczenia 2: XML Schema

Definiowanie zastosowań XML

XML jest językiem, dużo więcej daje jednak spojrzenie na XML jako metajęzyk, to znaczy zbiór języków, zastosowań XML. Na przykład zastosowaniem XML jest XHTML 1.0, to znaczy każdy dokument XHTML jest dokumentem XML, a dodatkowo jest zgodny z defnicją typu dokumentu XHTML.

Do zastosowań XML należą znane standardy, jak XSLT, XHTML czy WSDL. Można także zdefiniować swój własny język – zastosowanie XML. Definiowanie zastosowania XML nazywa się zwykle definiowaniem struktury dokumentu lub typu dokumentu.

Porządna definicja zastosowania XML, analogicznie do definicji języka programowania, powinna zawierać dwie części:

  1. składniową – podanie dopuszczalnych nazw elementów i atrybutów, oraz ich dopuszczalnej zawartości,
  2. semantyczną – określenie znaczenia poszczególnych elementów i atrybutów.

W praktyce formalnie definiuje się jedynie część składniową, służą do tego wymienione poniżej standardy. Natomiast znaczenie elementów i atrybutów opisuje się słownie w oddzielnym dokumencie tekstowym (jak w przypadku rekomendacji W3C), w komentarzach DTD czy schematu, lub niestety nie opisuje w ogóle.

Standardy

Zalecane sposoby definiowania struktury dokumentu:

  • DTD (Document Type Definition) – część standardu XML,
  • XML Schema – rekomendacja W3C.

Alternatywne standardy (jako ciekawostka):

  • Relax NG – standard podobny do XML Schema, mniej rozbudowana i bardziej czytelna składnia ułatwia ręczną edycję, ale pozwala na mniej niż XML Schema,
  • Schematron – odmienna ideologia, typ dokumentu określa się tu nie w oparciu o gramatykę, ale poprzez listę predykatów, które dokumenty muszą spełniać.

Obecnie najbardziej zalecanym standardem do definiowania nowych typów dokumentów jest XML Schema. Istnieje jednak wiele typów dokumentów już zdefiniowanych za pomocą DTD.

Dwa stopnie poprawności dokumentu, walidacja

Każdy dokument XML musi być poprawny składniowo (well-formed) – być zgodny z gramatyką XML i spełniać well-formedness constraints wymienione w rekomendacji XML.

Dodatkowo dokument XML może być poprawny strukturalnie (valid) – być zgodny z definicją struktury. Jeśli nie jest powiedziane inaczej, chodzi o DTD, a dokument ma spełniać validity constraints wymienione w rekomendacji XML. Tych samych terminów używa się także do określenia zgodności ze schematem XML Schema bądź innych standardów.

Proces sprawdzania poprawności strukturalnej nazywa się walidacją. Po wykonaniu walidacji mamy pewność, że dokument posiada właściwą strukturę. Dzięki temu, np. we własnych programach czy w arkuszach XSLT, nie musimy już rozważać przypadków błędnych i od razu wiemy, że np. wymagane elementy rzeczywiście występują.

XML Schema

Standard XML Schema zdefiniowany jest w trzech rekomendacjach:

XML Schema Part 0: Primer,
wprowadzenie i mniej formalny opis standardu,
XML Schema Part 1: Structures
definiowanie typów złożonych (formalnie),
XML Schema Part 2: Datatypes
definiowanie typów prostych (formalnie).

Być może zamiast mojego, wolicie profesjonalny tutorial po XML Schema...

Ogólnie

XML Schema sam jest zastosowaniem XML, to znaczy schematy są dokumentami XML. Elementy XML Schema znajdują się w przestrzeni nazw http://www.w3.org/2001/XMLSchema, w schematach zwykło się używać dla niej prefiksów xs lub xsd. Standardowym rozszerzeniem nazwy dla pliku ze schematem jest xsd.

W schemacie na najwyższym poziomie, tzn. jako dzieci elementu głównego schema, występują definicje:

  • elementów i atrybutów,
  • typów (prostych i złożonych),
  • grup (elementów i atrybutów),
  • notacji.

Ponadto na początku dokumentu mogą wystąpić elementy związane z łączeniem wielu schematów, a w dowolnym miejscu anotacje, które powinny stanowić dokumentację schematu i, jeśli nie ma tego w osobnym dokumencie, określać znaczenie poszczególnych elementów i atrybutów.

Typy nazwane

W schematach dla elementów i atrybutów o różnych nazwach określa się typ zawartości. Można to zrobić w poniższy sposób, odwołując się do typu nazwanego.

Przykład 1.

Plik: osoby1.xsd.

<xs:element name="osoba" type="OsobaTyp"/>
<xs:element name="imie" type="xs:string"/>
<xs:element name="nazwisko" type="xs:string"/>
<xs:attribute name="plec" type="PlecTyp"/>
<xs:attribute name="email" type="xs:string"/>

W XML Schema istnieją typy predefiniowane, m.in. występujący w przykładzie string. Nazwy tych typów znajdują się w przestrzeni nazw XML Schema i dlatego nazwa jest poprzedzona prefiksem.

Własne typy nazwane można zdefiniować w poniższy sposób, nieco odmienny dla typów prostych i złożonych.

Przykład 2.

Plik: osoby1.xsd.

	<xs:complexType name="OsobaTyp">
		<xs:sequence>
			<xs:element ref="imie" minOccurs="1" maxOccurs="unbounded"/>
			<xs:element ref="nazwisko"/>
		</xs:sequence>
		<xs:attribute ref="plec" use="required"/>
		<xs:attribute ref="email" use="optional"/>
	</xs:complexType>

	<xs:simpleType name="PlecTyp">
		<xs:restriction base="xs:string">
			<xs:enumeration value="k"/>
			<xs:enumeration value="m"/>
		</xs:restriction>
	</xs:simpleType>

Typów nazwanych należy używać wtedy, gdy wiele elementów / atrybutów posiada ten sam typ zawartości. Tylko typy nazwane mogą stanowić podstawę do rozszerzania bądź zawężania (więcej o tym za 2 tygodnie).

Typy anonimowe

Definicję typu można umieścić też wewnątrz definicji elementu (lub atrybutu). Wówczas mamy do czynienia z typem anonimowym.

Przykład 3.

Plik: osoby2.xsd.

<xs:element name="osoba">
	<xs:complexType>
		<xs:sequence>
			<xs:element ref="imie" minOccurs="1" maxOccurs="unbounded"/>
			<xs:element ref="nazwisko"/>
		</xs:sequence>
		<xs:attribute ref="plec" use="required"/>
		<xs:attribute ref="email" use="optional"/>
	</xs:complexType>
</xs:element>

<xs:attribute name="plec">
	<xs:simpleType>
		<xs:restriction base="xs:string">
			<xs:enumeration value="k"/>
			<xs:enumeration value="m"/>
		</xs:restriction>
	</xs:simpleType>
</xs:attribute>

Typów anonimowych należy używać tylko wtedy, gdy tylko jeden element / atrybut posiada dany typ zawartości i nie przewidujemy dodania do schematu nowych elementów / atrybutów z takim typem.

Typy złożone w XML Schema

Typy złożone w XML Schema definiuje się elementem schematu complexType. Typ złożony, w odróżnieniu od typu prostego, może posiadać wewnętrzną strukturę: podelementy i atrybuty. Dlatego tylko elementom (a nie atrybutom) można przypisywać typy złożone.

Podelementy i atrybuty

W definicji typu złożonego określa się jakie podelementy i atrybuty może zawierać element o tym typie.

Przykład 4.

Na przykład elementy o poniższym typie mogą zawierać dowolną liczbę elementów imie, a następnie dokładnie jeden element nazwisko. Poza tym element musi mieć atrybut plec i może mieć atrybut email.

<xs:complexType name="OsobaTyp">
	<xs:sequence>
		<xs:element ref="imie" minOccurs="1" maxOccurs="unbounded"/>
		<xs:element ref="nazwisko" minOccurs="1" maxOccurs="1"/>
	</xs:sequence>
	<xs:attribute ref="plec" use="required"/>
	<xs:attribute ref="email" use="optional"/>
</xs:complexType>

Domyślną liczbą wystąpień podelementu jest dokładnie 1, a atrybuty domyślnie są opcjonalne.

Sekwencja, wybór i wszystko

Element schematu sequence, użyty w poprzednim przykładzie, grupuje podelementy w sekwencję. Istnieją trzy elementy schematu służące do konstruowania takich grup elementów:

  • sequence – w dokumencie muszą występować wszystkie podane podelementy w tej samej kolejności,
  • choice – w dokumencie musi wystąpić dokładnie jeden z wymienionych podelementów w takiej liczbie, jaka jest zgodna z jego minOccurs i maxOccurs,
  • all – w dokumencie muszą występować wszystkie podane podelementy, ale w dowolnej kolejności; w tym przypadku dla podelementów musi zachodzić maxOccurs < unbounded.

Sekwencje i wybory można w sobie zagnieżdżać, konstruując bardziej skomplikowane typy zawartości. W zagnieżdżeniach nie mogą brać udziału grupy all.

Przykład 5.

Plik: przyklad1.xsd.

<xs:complexType name="typ1">
		<!-- Dokładnie jeden element A lub od 2 do 3 elementów B. -->
	<xs:choice>
		<xs:element ref="A"/>
		<xs:element ref="B" minOccurs="2" maxOccurs="3"/>
	</xs:choice>
</xs:complexType>
	
<xs:complexType name="typ2">
		<!-- Dokładnie jeden element Start, następnie dowolna liczba wystąpień grup,
			z których każda jest parą elementów A albo parą elementów B i C,
			na końcu dokładnie jeden element Koniec. -->
	<xs:sequence>
		<xs:element ref="Start"/>
		<xs:choice minOccurs="0" maxOccurs="unbounded">
			<xs:element ref="A" minOccurs="2" maxOccurs="2"/>
			<xs:sequence>
				<xs:element ref="B"/>
				<xs:element ref="C"/>
			</xs:sequence>
		</xs:choice>
		<xs:element ref="Koniec"/>
	</xs:sequence>
</xs:complexType>

Zadanie 1.

Otwórz w widoku tekstowym XML Spy ten schemat. Spróuj zdefiniować następujące typy zawartości:

  1. parzysta lub podzielna przez 3 liczba elementów A,
  2. parzysta lub podzielna przez 3 liczba elementów A, dowlonie wymieszana z dowolną liczbą elementów B,
  3. od 2 do 3 elementów A, jeden element B i opcjonalny element C, wszystko w dowolnej kolejności.

Wyjaśnienie: Program XML Spy sprawdza czy typy zawartości w schemacie są deterministyczne. Nie jest to wymagane w ogólności dla XML Schema, ale jest zalecane dla DTD. Prawdopodobnie stąd to wymaganie w XML Spy. W grupach typu all elementy mogą występować najwyżej jeden raz.

Zadanie 2.

Napisz w widoku tekstowym XML Spy schemat dla wizytówek. Wszędzie dla zawartości tekstowej zastosuj predefiniowany typ string.

Wizytówka powinna zawierać imię, nazwisko, telefon, email, adres pocztowy. Zastanów się nad liczbą i kolejnością poszczególnych elementów, opcjonalnością atrybutów.

Zadanie 3.

Napisz wizytówkę zgodną ze swoim schematem i zwaliduj ją.

Grupy elementów

Gdy jakaś grupa (sekwencja, wybór lub all) powtarza się w wielu miejscach (np. w różnych definicjach typu), można w jednym miejscu zdefiniować nazwaną grupę elementów, a w definicjach typów odwoływać się do tej grupy.

Przykład 6.

W schemacie figury1.xsd występują powtarzające się sekwencje elementów x i y. W schemacie figury2.xsd zostały zastąpione grupą współrzędne, jak poniżej.

<xs:group name="współrzędne">
	<xs:sequence>
		<xs:element name="x" type="xs:double"/>
		<xs:element name="y" type="xs:double"/>
	</xs:sequence>
</xs:group>

<xs:element name="odcinek">
	<xs:complexType>
		<xs:sequence>
			<xs:group ref="współrzędne" minOccurs="2" maxOccurs="2"/>
		</xs:sequence>
		<xs:attributeGroup ref="atrybuty-krawędzi"/>
	</xs:complexType>
</xs:element>

Grupy atrybutów

Podobnie jak grupy elementów, można także zdefiniować grupy atrybutów i odwoływać się do nich w definicjach typów.

Przykład 7.

W schemacie figury1.xsd występują powtarzające się atrybuty kolor-krawędzi, grubość-krawędzi i kolor-powierzchni. W schemacie figury2.xsd zostały zastąpione grupami atrybutów atrybuty-krawędzi i atrybuty-powierzchni, jak poniżej.

<xs:attributeGroup name="atrybuty-krawędzi">
	<xs:attribute name="kolor-krawędzi" type="KolorTyp"/>
	<xs:attribute name="grubość-krawędzi" type="xs:positiveInteger"/>
</xs:attributeGroup>

<xs:element name="punkt">
	<xs:complexType>
		<xs:sequence>
			<xs:group ref="współrzędne" minOccurs="1" maxOccurs="1"/>
		</xs:sequence>
		<xs:attributeGroup ref="atrybuty-krawędzi"/>
	</xs:complexType>
</xs:element>

Mieszany typ zawartości

W XML węzły tekstowe mogą występować obok węzłów elementów jako dzieci tego samego elementu. W tekstowej reprezentacji XML można to opisać jako tekst z zanurzonymi elementami albo upraszczając znakowany tekst.

Typ zawartości, który dopuszcza tekst z zanurzonymi elementami, nazywa się typem mieszanymmixed content type i można go zdefiniować w XML Schema. Służy do tego atrybut mixed w elemencie complexType.

Włączenie typu mieszanego pozwala na pojawienie się węzłów tekstowych pomiędzy podelementami. Ważne jest to, że liczba i kolejność podelementów nadal jest kontrolowana zgodnie z definicją typu (inaczej niż w DTD).

Przykład 8.

Poniższe przykłady pochodzą z rekomendacji XML Schema.

<xsd:element name="letterBody">
  <xsd:complexType mixed="true">
    <xsd:sequence>
      <xsd:element name="salutation">
        <xsd:complexType mixed="true">
          <xsd:sequence>
            <xsd:element name="name" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="quantity"    type="xsd:positiveInteger"/>
      <xsd:element name="productName" type="xsd:string"/>
      <xsd:element name="shipDate"    type="xsd:date" minOccurs="0"/>
      <!-- etc. -->
    </xsd:sequence>
  </xsd:complexType>
</xsd:element>
<letterBody>
<salutation>Dear Mr.<name>Robert Smith</name>.</salutation>
Your order of <quantity>1</quantity> <productName>Baby
Monitor</productName> shipped from our warehouse on
<shipDate>1999-05-21</shipDate>. ....
</letterBody>

Zadanie 4.

Zapisz schemat opisujący znane z HTML elementy dla list ol, ul, li oraz kilka przykładowych elementów.

Załóżmy, że body może zawierać tekst z zanurzonymi w nim listami, listy tylko elementy wypunktowania (li), a elementy wypunktowania znowu tekst z zanurzonymi listami. W każdym miejscu gdzie dopuszczalny jest tekst, może być on znakowany elementami font z atrybutami size i face.

Globalne i lokalne definicje

Definicje globalne

Elementy i atrybuty można definiować na głównym poziomie schematu i odwoływać się do nich w definicjach typów w atrybucie ref.

Takie podejście jest zalecane dla elementów i atrybutów, które występują w wielu typach, zawsze z tym samym typem zawartości.

Przykładami schematów napisanych w ten sposób są osoby1.xsd, osoby2.xsd i przyklad2.xsd.

Definicje lokalne

Elementy i atrybuty można także definiować lokalnie, wewnątrz definicji typu (nazwanego lub anonimowego). Definicje można zagnieżdżać. Nazwę podaje się wówczas w atrybucie name.

Takie podejście jest zalecane dla elementów i atrybutów, które występują tylko w jednym miejscu schematu lub w różnych miejscach schematu mają różny typ zawartości (patrz poniższy podrozdział).

Schemat jest czytelniejszy, gdy definicje nie są zbytnio zagnieżdżone (chociaż to może kwestia gustu). Dlatego lepiej, aby lokalne definicje elementów / atrybutów (w jakimś typie) same nie zawierały już skomplikowanego typu anonimowego, a najlepiej odwoływały się do typu nazwanego.

Przykładami schematów napisanych w ten sposób są osoby3.xsd, osoby4.xsd i przyklad3.xsd.

Różne typy zawartości dla elementów (atrybutów) o tych samych nazwach

Lokalna definicja elementu / atrybutu obowiązuje tylko w miejscu jej wystąpienia. W innym miejscu schematu można zdefiniować element / atrybut o tej samej nazwie, ale o innym typie zawartości.

Na przykład w schemacie przyklad2.xsd element c zdefiniowany jest globalnie, w każdym miejscu posiada ten sam typ zawartości. Dokument przyklad2.xml jest zgodny z tym schematem.

Natomiast w schemacie przyklad3.xsd element c zdefiniowany jest lokalnie dwa razy. Wewnątrz elementów a dopuszczalna jest zawartość A i AAA, wewnątrz elementów b dopuszczalna jest zawartość B i BBB. Dokument przyklad2.xml nie jest zgodny z tym schematem, natomiast zgodny jest dokument przyklad3.xml.

Stosowanie lokalnych definicji z (potencjalnie) różnym typem zawartości może być zaletą. Dzięki temu możemy stosować te same nazwy, jeśli są naturalne w danym miejscu. Jak w przykładzie z poprzednich zajęć, element kod wewnątrz artykułu może oznaczać kod źródłowy, a wewnątrz adresu kod pocztowy. Dzięki XML Schema możemy dla takich elementów podać różne typy zawartości (nawet bez przestrzeni nazw).

Jednak nadawanie różnego znaczenia i różnego typu zawartości elementom o tej samej nazwie może prowadzić do niejednoznaczności i utrudniać rozumienie schematu oraz odczytywanie informacji z dokumentu. Dlatego tej techniki można używać tylko w naprawdę uzasadnionych przypadkach.

W pełni uzasadnione jest lokalne definiowanie atrybutów, gdyż ich znaczenie (i typ zawartości) są ściśle związane z elementem, w którym występują. Jednak w przypadku atrybutów, które występują w wielu elementach (typach zawartości) w tym samym znaczeniu, zalecane jest ich globalne definiowanie.

Schematy w XML Spy

W programie XML Spy schematy można tworzyć w wizualnym edytorze (prezentacja na ćwiczeniach). Szczególnie czytelne jest wizualne tworzenie typów złożonych. Jest to wygodne, jednak nie zwalnia autora z dbałości o czytelność i estetykę schematu zapisanego tekstowo.

XML Spy domyślnie tworzy typy anonimowe wewnątrz definicji elementów i atrybutów, z podelementami definiowanymi lokalnie. Schemat po prostu "wyklikany" jest zazwyczaj bardzo zagnieżdżony. I nie definiuje atrybutów, tylko same elementy :).

Edytor wizualny pozwala jednak na definiowanie typów nazwanych, rozszerzanie i zawężanie typów, definiowanie grup oraz globalne definicje elementów i atrybutów (i referencje do nich w innych definicjach typów). Należy z tych możliwości korzystać, aby schematy były modularne (bez redundancji, łatwo rozszerzalne), estetyczne i czytelne.


Valid XHTML 1.1Valid CSS