PHP/DOM

Aus Mikiwiki
< PHP
Zur Navigation springen Zur Suche springen

PHP 5 hält sich weitgehend an den DOM-Standard - die definierten Funktionsnamen wurden also unverändert übernommen.

Erste Schritte

Zuerst muss eine XML-Datei beschafft werden. Weblogs bieten ihre Daten meist im RSS-Format an, das sich gut für eigene Experimente eignet. Von der Tagesanzeiger-Website (http://www.tagesanzeiger.ch/rss.html) kann man ein Datenpaket der folgenden Art erhalten.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tagesanzeiger Front</title>
    <link>http://tagesanzeiger.ch</link>
    <description>RSS von Tagesanzeiger</description>
    <language>de-de</language>
    <copyright>Tagesanzeiger</copyright>
    <atom:link href="http://tagesanzeiger.ch/rss.html" rel="self" type="application/rss+xml" />
    <image>
      <title>Tagesanzeiger Front</title>
      <link>http://tagesanzeiger.ch</link>
      <url>http://files.newsnetz.ch//rss_logos/2.png</url>
    </image>
    <item>
      <title>Bush unterzeichnet Rettungsplan</title>
      <description>Grosse Erleichterung für die Finanzmärkte in aller Welt: Nach zweiwöchigem dramatischen Tauziehen ist der 700 Milliarden Dollar schwere US-Rettungsplan unter Dach und Fach.</description>
      <category>UnternehmenKonjunktur</category>		
      <link>http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/17156322</link>
      <guid isPermaLink="false">http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/17156322</guid>
      <pubDate>Fri, 3 Oct 2008 21:14:38 +0200</pubDate>
    </item>
    <item>
      <title>Schweiz soll Russland in Georgien vertreten</title>
      <description>Die Schweiz ist bereit, die diplomatischen Interessen Russlands in Georgien zu vertreten. Der Bundesrat hat eine entsprechende Anfrage Russlands im Grundsatz gutgeheissen.</description>
      <category>Standard</category>		
      <link>http://tagesanzeiger.ch/schweiz/story/31131884</link>
      <guid isPermaLink="false">http://tagesanzeiger.ch/schweiz/story/31131884</guid>
      <pubDate>Fri, 3 Oct 2008 19:50:43 +0200</pubDate>
    </item>
    <item>
      <title>Kalifornien geht das Geld aus</title>
      <description>Wegen des Kreditengpasses auf den Finanzmärkten kann Kalifornien seine Rechnungen bald nicht mehr bezahlen.</description>
      <category>UnternehmenKonjunktur</category>		
      <link>http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/12571967</link>
      <guid isPermaLink="false">http://tagesanzeiger.ch/wirtschaft/unternehmen-und-konjunktur/story/12571967</guid>
      <pubDate>Fri, 3 Oct 2008 21:18:59 +0200</pubDate>
    </item>
  </channel>
</rss>

Mit einer solchen Datei kann nun gearbeitet werden. In der Praxis würde statt der lokalen Version direkt der neueste Feed von der jeweiligen Website gelesen werden - fertig ist ein Weblog, das sich mit Daten des yi>Tagesanzeigers füllt. Aufgrund des Standards RSS funktioniert das dann auch mit tausenden von anderen Seiten.

Diese Daten sollen nun geladen und verarbeitet werden. Das folgende Skript "dom.readtitles.php" liest die Titel der RSS-Datei aus und gibt sie aus.

<?php
## Name     dom_readtitles.php

## Erstellen einer Instanz des DOM-Dokuments
$dom    = new DomDocument();
## Lesen der XML-Datei
$dom->load("data/articles.xml");
## Mit der DOM-Funktion "getElementsByTagName" werden alle Elemente mit
## dem betreffenden Namen ermittelt.
$titles = $dom->getElementsByTagName("title");
## Zurückgegeben wird ein Array aus Knoten, die mit der Funktion "foreach"
## durchlaufen werden können.
foreach($titles as $node) 
{ 
  ## Die Ausgabe erfolgt mit der Eigenschaft "textContent".
  echo "<b>Title:</b> {$node->textContent}\n<hr noshade>\n"; 
} 
?>

Title: Tagesanzeiger Front


Title: Tagesanzeiger Front


Title: Bush unterzeichnet Rettungsplan


Title: Schweiz soll Russland in Georgien vertreten


Title: Kalifornien geht das Geld aus


Die Basisklassen und Funktionslisten

Die folgende Tabelle ziegt die wichtigsten zur Verfügung stehenden Klassen.

Klasse Beschreibung
DomAttr Repräsentiert ein Attribut. Die wichtigsten Methoden dieser Klasse sind:
Methode Beschreibung
isId Das Attribut ist ein ID-Attribut.
insertBefore Fügt ein Attribut vor dem aktuellen ein.
isSameNode Vergleicht zwei Attribute auf Identität.
isDefaultNamespace Prüft, ob das Attribut im Standardnamensraum ist.
isEqualNode Vergleicht zwei Attribute auf Gleichheit.
DomCData Ein CDATA-Abschnitt.
DomComment Ein Kommentar.
DomDocument Die wichtigste Klasse - das Dokument selbst. Die wichtigsten Methoden dieser Klasse sind:
Methode Beschreibung
createAttributeNS Erzeugt ein Attribut mit Namensraum.
createAttribute Erzeugt ein Attribut.
createElementNS Erzeugt ein Element mit Namensraum.
createElement Erzeugt ein neues Element.
createTextNode Erzeugt einen Textknoten.
createDocumentFragment Erzeugt ein Fragment neuer Knoten.
createComment Erzeugt einen Kommentarknoten "<!-- ... -->".
createCDATASection Erzeugt einen "<![CDATA[ ... ]]>"-Abschnitt.
createProcessingInstruction Erzeugt eine Prozessanweisung.
createEntityReference Erzeugt einen Verweis.
getElementsByTagName Gibt ein Array aller Elemente (Element-Objekte der Klasse "DomElement") mit dem angegebenen Namen zurück.
getElementsByTagNameNS Gibt ein Array aller Elemente (Element-Objekte der Klasse "DomElement") mit dem angegebenen Namen in einem bestimmten Namensraum zurück.
getElementById Gibt ein Element (Element-Objekte der Klasse "DomElement") mit einem bestimmten ID-Attribut zurück.
renameNode Benennt einen Knoten um.
load Lädt ein Dokument aus einem Stream.
save Speichert das Dokument.
loadXML Lädt eine XML-Datei.
saveXML Speichert eine XML-Datei.
loadHTML Lädt eine HTML-Datei.
saveHTML Speichert eine HTML-Datei.
validate Prüft gegen eine DTD.
validateSchema Prüft gegen ein Schema (XSD).
validateRelaxNG Prüft gegen ein RelaxNG-Schema.
insertBefore Fügt vor einem anderen Knoten ein.
replaceChild Ersetzt ein Kindelement.
removeChild Entfernt ein Kindelement.
appendChild Hängt ein Kindelement an.
hasChildNodes Prüft, ob Kindelemente vorhanden sind.
cloneNode Klont einen Knoten.
hasAttributes Ermittelt, ob ein Element Attribute hat.
isSameNode Vergleicht zwei Knoten auf Identität.
isDefaultNameSpace Prüft, ob der Knoten im Standardnamensraum ist.
isEqualNode Vergleicht zwei Knoten auf Gleichheit.
DomDocumentType Die DOCTYPE-Deklaration des Dokuments.
DomElement Ein vollständiges Element. Die wichtigsten Methoden dieser Klasse sind:
Methode Beschreibung
getAttribute Ermittelt ein Attribut.
setAttribute Erzeugt und definiert ein Attribut.
removeAttribute Entfernt das Attribut.
getAttributeNS Ermittelt ein Attribut in einem bestimmten Namensraum.
setAttributeNS Erzeugt und definiert ein Attribut in einem bestimmten Namensraum.
removeAttributeNS Entfernt das Attribut in einem bestimmten Namensraum.
getAttributeNode Ermittelt das Attribut als Objekt der Klasse "Dom Attr".
setAttributeNode Setzt ein Attribut auf Grundlage eines Objekts der Klasse "Dom Attr".
setIdAttribute Setzt das Attribut mit den Namen ID.
setIdAttributeNS Setzt das Attribut mit den Namen ID in einem bestimmten Namensraum.
isSameNode Vergleicht zwei Attribute auf Identität.
isDefaultNamespace Prüft, ob ein Attribut im Standardnamensraum ist.
isEqualNode Vergleicht zwei Attribute auf Gleichheit.
DomEntity Eine Entität (z. B. "&amp;").
DomEntityReference Eine Verweis-Entität (auf eine externe Datei).
DomNode Allgemeiner Knoten (kann alles ausser die Klasse "DomDocument" sein).
DomProcessingInstruction Eine Prozessanweisung (z. B. "<?id?>").
DomText Freistehender Text.
DomXPath Repräsentiert eine XPath-Abfrage.

Je nach Abfrage entstehen durch Anwendung bestimmter Methoden Objekte dieser Klassen. Über dieses Objekt erhält man dann Zugang zum Dokument, seinen Elementen und deren Attributen. Bestimmte Methoden (die wiederum dem W3C-DOM-Standard entsprechen) werden dann benutzt, um Teile des Dokuments zu lesen, zu ändern oder zu löschen.

Konstanten

Beim Umgang mit DOM sind folgende Konstanten wichtig, welche die Lesbarkeit der Skripte deutlcih steigern.

Konstante Beschreibung
XML_ELEMENT_NODE Element
XML_ATTRIBUTE_NODE Attribut
XML_TEXT_NODE Textknoten
XML_CDATA_SECTION_NODE CDATA-Abschnitt
XML_ENTITY_REF_NODE Entitätsverweis
XML_ENTITY_NODE Entitätsknoten
XML_PI_NODE Prozessanweisung
XML_COMMENT_NODE Kommentar
XML_DOCUMENT_NODE Dokument
XML_DOCUMENT_TYPE_NODE Dokumententyp-Deklaration
XML_DOCUMENT_FRAG_NODE Dokumentenfragment
XML_NOTATION_NODE Notation (in einer DTD)
XML_GLOBAL_NAMESPACE Globaler Namensraum
XML_LOCAL_NAMESPACE Lokaler Namensraum
XML_HTML_DOCUMENT_NODE Dokument im HTML-Modus
XML_DTD_NODE Document Type Definition
XML_ELEMENT_DECL_NODE Element-Deklaration einer DTD
XML_ATTRIBUTE_DECL_NODE Attribut-Deklaration einer DTD
XML_ENTITY_DECL_NODE Entitäts-Deklaration einer DTD
XML_NAMESPACE_DECL_NODE Namensraum-Deklaration
XML_ATTRIBUTE_CDATA CDATA-Abscnitt eines Attributs
XML_ATTRIBUTE_ID ID-Attribut
XML_ATTRIBUTE_IDREF IDREF-Attribut
XML_ATTRIBUTE_IDREFS Kollektion von IDRF-Attributen
XML_ATTRIBUTE_ENTITY Entität in einem Attribut
XML_ATTRIBUTE_NMTOKEN NMTOKEN
XML_ATTRIBUTE_NMTOKENS Kollektion von NMTOKEN
XML_ATTRIBUTE_ENUMERATION Attribut-Aufzählung
XML_ATTRIBUTE_NOTATION Attribut-Notation

Umgang mit Elementen

Das folgende Skript zeigt, wie der Zugriff erfolgen kann.

<html>
<head>
  <title>News-Artikel erfassen</title>
</head>
<body>
<?php
$dom = new DomDocument();
$dom->load("data/articles.xml");
## Sequentielles Durchlaufen aller Elemente mit dem Namen "item".
foreach ($dom->getElementsByTagName("item") as $articles)
{
  ## Für jedes Element werden alle Kindelemente durchlaufen.
  foreach ($articles->childNodes as $item)
  {
    ## Filterung der Elementknoten. Normalerweise sind in der Testdatei
    ## keine anderen Knotentypen, allerdings können Texte oder 
    ## Kommentare eingetragen worden sein, die ebenfalls Knoten sind.
    if ($item->nodeType == XML_ELEMENT_NODE)
    {
      ## Suche der Elemente "title" und "link" und Umsetzung ihres
      ## Inhalts für die Ausgabe.
      switch ($item->nodeName)
      {
        case "title":
          echo "<b>Knoteninhalt:</b> {$item->textContent}";
          break;
        case "link":
          printf(" <a href=\"%s\">Mehr...</a><br />\n", $item->textContent);
      }
    }
  }
}
?>
</body>
</html>

Knoteninhalt: Bush unterzeichnet Rettungsplan Mehr...
Knoteninhalt: Schweiz soll Russland in Georgien vertreten Mehr...
Knoteninhalt: Kalifornien geht das Geld aus Mehr...

XML mit DOM schreiben

Zum Schreiben einer XML-Datei werden die Methoden "saveXML" oder "save" der Klasse "DomDocument" benutzt.

  • "saveXML" erzeugt eine Zeichenkettenausgabe des Dokuments, die zur Anzeige oder zum Schreiben mit Stream- oder Dateifunktionen genutzt werden kann.
  • "save" schreibt direkt in die angegebene Datei.

Das folgende Beispiel erweitert die oben verwendete Datei um einen über ein Formular erfassten Datensatz und gibt diesen direkt als XML an den Webbrowser aus, wenn das Formular abgeschikt wurde. Das Skript besteht aus vier Teilen:

  • Zugriff auf die Datenquelle
  • Erfassen neuer Daten, wenn das Formular abgeschickt wurde
  • Ausgabe des neuen Zustands
  • Formular
<html>
<head>
  <title>News-Artikel erfassen</title>
</head>
<body>
<?php
## Die angegebene Datei muss schreibbar sein
define ("PATH", "data/articles.xml");
$dom = new DomDocument();
$dom->load(PATH);
## Es folgen einige Fehlerabfragen, die sich leicht erweitern lassen,
## um zu prüfen, ob der Benutzer plausible Daten ins Formular
## eingetragen hat.
if (isset($_POST['Submit']))
{
  $error = FALSE;
  if (isset($_POST['Titel']))
  {
    $title = $_POST['Titel'];
  }
  else
  {
    $error = TRUE;
  }
  if (isset($_POST['Link']))
  {
    $link = $_POST['Link'];
  }
  else
  {
    $error = TRUE;
  }
  if (isset($_POST['Text']))
  {
    $text = $_POST['Text'];
  }
  else
  {
    $error = TRUE;
  }
  ## Wenn keine Fehler auftraten
  if (!$error)
  {
    ## Die Struktur der RSS-Datei verlangt folgenden Aufbau des Artikels:
    ## <item>
    ##   <title></title>
    ##   <link></link>
    ##   <description></description>
    ## </item>
    ## Elemente können grundsätzlich nur auf Ebene des Dokuments erzeugt
    ## werden. Wenn sie existieren, können sie irgendwo angehängt werden.
    ## Begonnen wird mit dem Element "item".
    $newItem  = $dom->createElement("item");
    ## Erzeugen des Elements "title".
    $newTitle = $dom->createElement("title");
    ## Zum Element "title" wird ein Textknoten hinzugefügt, der die
    ## Daten aus dem Formular aufnimmt.
    $newTitle->appendChild($dom->createTextNode($_POST['Titel']));
    ## Anhängen des Elements "title" an das Element "item".
    $newItem->appendChild($newTitle);
    ## Erzeugen des Elements "link".
    $newLink  = $dom->createElement("link");
    $newLink->textContent = $_POST['Link'];
    $newLink->appendChild($dom->createTextNode($_POST['Link']));
    $newItem->appendChild($newLink);
    ## Erzeugen des Elements "description".
    $newDesc  = $dom->createElement("description");
    $newDesc->textContent = $_POST['Text'];
    $newDesc->appendChild($dom->createTextNode($_POST['Text']));
    $newItem->appendChild($newDesc);
    ## Beschaffen des Hauptelements "channel", am einfachsten durch
    ## Zugriff auf die Kollektion aller Elemente mit diesem Namen.
    $channels = $dom->getElementsByTagName("channel");
    ## Da das Hauptelement "channel" nur einmal vorhanden ist, wird aus
    ## der Kollektion das erste Element ("item(0)") entnommen und
    ## diesem das Element "item" sofort angefügt.
    $channels->item(0)->appendChild($newItem);
    ## Öffnen eines Schreibzugriffs
    $fh = fopen (PATH, "w");
    ## Das erweiterte Dokument wird in den Schreibzugriff geschrieben.
    fwrite($fh, $dom->saveXML());
    fclose($fh);
  }
}
## Ausgabe der Titel
echo "vorhandene Titel:<hr>\n";
## Sequentielles Durchlaufen aller Elemente mit dem Namen "item".
foreach ($dom->getElementsByTagName("item") as $articles)
{
  ## Für jedes Element werden alle Kindelemente durchlaufen.
  foreach ($articles->childNodes as $item)
  {
    ## Filterung der Elementknoten. Normalerweise sind in der Testdatei
    ## keine anderen Knotentypen, allerdings können Texte oder 
    ## Kommentare eingetragen worden sein, die ebenfalls Knoten sind.
    if ($item->nodeType == XML_ELEMENT_NODE && $item->nodeName == "title")
    {
      echo "<b>Titel:</b> {$item->textContent}<br />";
    }
  }
}
?>
<!-- Formular zur Erfassung -->
<form action="<?=$_SERVER['PHP_SELF']?>" method="post">
  <table>
    <tr>
      <td>Titel</td>
      <td><input type="text" name="Titel" /></td>
    </tr>
    <tr>
      <td>Link</td>
      <td><input type="text" name="Link" /></td>
    </tr>
    <tr>
      <td>Text</td>
      <td><input type="text" name="Text" /></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
     <td><input type="submit" value="Senden" name="Submit"/></td>
    </tr>    
  </table>    
</form>
</body>
</html>

Attribute verwenden

Attribute sind wie Elemente, Text oder andere Teiles des XML-Baumes Knoten. Sie werden mit der Methode "setAttribute" der Klasse "DomElement" erzeugt bzw. gesetzt und mit "getAttribute" gelesen. Um das Attribut als Knotenobjekt weiterzuverarbeiten (die zugrundeliegende Klasse ist dann "DomAttr") bietet sich "getAttributeNode" an.

Erweiterung der Klassen

Das Erzeugen einzelner Elemente mit den Methoden "createElement" und "appendChild" ist zwar recht einfach, in der Masse der möglicherweise benötigten Elemente aber ausgesprochen lästig. Fast immer lohnt es sich, eine spezialisierte Klasse zu erstellen, welche diese Arbeiten übernimmt. Statt die Funktionen einfach nur in einer Methode zu kapseln, ist eine Erweiterung der Grundklassen besser. Die erweiterte Klasse wird dann nämlich den Regeln der Grundklasse folgen und muss nicht oder nur teilweise dokumentiert werden.

Am einfachsten ist die Erweiterung der Grundklasse "DomDocument". Das folgende Beispiel zeigt eine universellere und kompaktere Schreibweise des vorangehenden Beispiels.

<?php
## Die neue Klasse "Article" erbt direkt von der Klasse "DomDocument".
class Article extends DomDocument
{
  ## Wichtig ist, dass der originale Konstruktor aufgerufen wird.
  public function __construct()
  {
    parent::__construct();
  }

  public function addArticle($title, $link, $text)
  {
    $newItem  = $this->createElement('item');
    $newTitle = $this->createElement('title');
    $newTitle->appendChild($this->createTextNode($title));
    $newItem->appendChild($newTitle);
    $newLink  = $this->createElement('link');
    $newLink->appendChild($this->createTextNode($link));
    $newItem->appendChild($newLink);
    $newDesc  = $this->createElement('description');
    $newDesc->appendChild($this->createTextNode($text));
    $newItem->appendChild($newDesc);
    $channels = $this->getElementsByTagName('channel');
    $channels->item(0)->appendChild($newItem);
  }    
}
?>
<html>
<head>
  <title>News-Artikel erfassen</title>
</head>
<body>    
<?php
## Angegebene Datei muss schreibbar sein
define ("PATH", "data/articles.xml");
## Nach Instanziierung der Klasse "Article" steht ein Objekt bereit,
## das sich nicht von "DomDocuemnt" unterscheidet.
$dom = new Article();
$dom->load(PATH);
## Hinzufügen eines neuen Titels
if (isset($_POST['Submit']))
{
  $error = FALSE;
  if (isset($_POST['Titel']))
  {
    $title = $_POST['Titel'];
  }
  else
  {
    $error = TRUE;
  }
  if (isset($_POST['Link']))
  {
    $link = $_POST['Link'];
  }
  else
  {
    $error = TRUE;
  }
  if (isset($_POST['Text']))
  {
    $text = $_POST['Text'];
  }
  else
  {
    $error = TRUE;
  }
  if (!$error)
  {
    $dom->addArticle($_POST['Titel'], $_POST['Link'], $_POST['Text']);
    $dom->save(PATH);
  }
}
## Ausgabe der Titel
echo "Vorhandene Titel:<hr>\n";
foreach ($dom->getElementsByTagName("item") as $articles)
{
  foreach ($articles->childNodes as $item)
  {
    if ($item->nodeType == XML_ELEMENT_NODE && $item->nodeName == 'title')
    {
      echo "<b>Titel:</b> {$item->textContent}<br />";
    }
  }
}
## Formular zur Erfassung
?>
<form action="<?=$_SERVER['PHP_SELF']?>" method="post">
  <table>
    <tr>
      <td>Titel</td>
      <td><input type="text" name="Titel" /></td>
    </tr>
    <tr>
      <td>Link</td>
      <td><input type="text" name="Link" /></td>
    </tr>
    <tr>
      <td>Text</td>
      <td><input type="text" name="Text" /></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td><input type="submit" value="Senden" name="Submit"/></td>
    </tr>
  </table>
</form>
</body>
</html>

Prüfung von XML-Dateien

Zum Umgang mit XML gehört auch die Prüfung (engl. validation) von XML-Dateien. Dazu können grundsätzlich drei Verfahren benutzt werden.

  • DTD: Das klassische Verfahren mit der Document Type Definition. Diese Definition beschreibt die zulässigen Strukturen einer XML-Seite.
  • XSD: Das neue XML-Schema ist umfassend, selbst in XML implementiert und sehr gut für Daten geeignet, wie sie in relationalen Datenbanken vorkommen.
  • RelaxNG: Da XSD einigen zu komplex war, wurde eine vereinfachte Fassung entwickelt, die sich jedoch nicht durchsetzte.

Angenommen, es wurde eine Instanz der Klasse "DomDocument" mit dem Namen "$doc" erstellt, so kann die Überprüfung auf eine der drei folgenden Arten durchgeführt werden.

$doc->validate("checkit.dtd");
$doc->schemaValidate("checkit.xsd");
$doc->relaxNGValidate("checkit.rng");

Grundsätzlich geben alle drei Methoden "TRUE" oder "FALSE" zurück. Im Fehlerfall wird ausserdem eine Warnung ausgegeben. Das ist allerdings fatal, da man damit wenig anfangen kann. Das folgende Skript fängt diese Meldung ab und zeigt den Fehler gezielt und damit kontrolliert an.

<?php
## Aktivierung der internen Fehlerverfolgung.
ini_set("track_errors", 1);
## Laden des Dokuments.
$dom = new DomDocument(); 
$dom->load("data/lib.xml"); 
## Validierung des Dokuments.
if (@$dom->schemaValidate("data/lib.xsd"))
{
  echo "OK";
} 
else 
{
  ## Tritt nun ein Fehler auf, so wird dieser gezielt ausgegeben.
  echo "Fehler: " . $php_errormsg;
}
?>


Warning: DOMDocument::load() [function.DOMDocument-load]: Opening and ending tag mismatch: Isbn line 4 and isbn in /WWW/test/data/lib.xml, line: 4 in /WWW/test/dom_validate.php on line 6
Fehler: DOMDocument::schemaValidate() [function.DOMDocument-schemaValidate]: The document has no document element.

Das Schema verlangt bei diesem Test, dass innerhalb eines Elements mit dem Namen "<book>" ein Element mit dem Namen "<isbn>" auftauchen muss. Um einen Fehler zu provozieren, kann stattdessen "<ISBN>" in die Datei "lib.xml" geschrieben werden.

Die verwendete Datei "lib.xml" sieht wie folgt aus.

<?xml version="1.0" ?>
<library xmlns="http://tempuri.org/lib.xsd">
  <book>
    <isbn>3-334-17548-9</isbn>             
    <title>Das blaue Buch</title>
    <keywords />
    <stock>1</stock>
    <lendout>3</lendout>
  </book>
  <book>
    <isbn>3-7493-8934-0</isbn>
    <title>Webserverprogrammierung</title>
    <keywords />
    <stock>3</stock>
    <lendout>5</lendout>
  </book>
  <book>
    <isbn>1-74749-3932-3</isbn>
    <title>Der PC</title>
    <keywords>pc, computer</keywords>
    <stock>2</stock>
    <lendout>0</lendout>
  </book>
  <book>
    <isbn>1-3954-654965-4</isbn>
    <title>Neues Buch</title>
    <keywords>buch</keywords>
    <stock>2</stock>
    <lendout>0</lendout>
  </book>
</library>

Die verwendete Datei "lib.xsd" sieht wie folgt aus.

<?xml version="1.0" ?>
<xs:schema id="library" targetNamespace="http://tempuri.org/lib.xsd" 
  xmlns="http://tempuri.org/lib.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  attributeFormDefault="qualified" elementFormDefault="qualified">
  <xs:element name="library">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element name="book">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="isbn"     type="xs:string" minOccurs="1" maxOccurs="1" />
              <xs:element name="title"    type="xs:string" minOccurs="1" />
              <xs:element name="keywords" type="xs:string" minOccurs="0" />
              <xs:element name="stock"    type="xs:string" minOccurs="0" />
              <xs:element name="lendout"  type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>