smart software solutions


XML

kein Buch mit 7 Siegeln...


Vorbemerkung

Ja, XML ist eine tolle Sache! Ich muß zu meiner Schande gestehen, daß mich die übertriebene Begeisterung mancher Mitbürger zu diesem Thema einige Jahre abgeschreckt hat überhaupt mit dieser Technik zu beschäftigen. Was konnte man nicht alles höhren über syntaxfreie Grammatik und das Ende aller Kommunikationsprobleme. Ich kann getrost sagen, ALLES QUATSCH! Wer ein überzeugendes Beispiel benötigt möge sich mal eine Excel-Tabelle (ab Version 2003) als XML ausgeben lassen.

Grundaufbau

Wer das Thema erschöpfend behandeln will, dem sei der Artikel in der Wikipedia XML ans Herz gelegt. XML ist zunächst mal genauso revolutionär wie das ASCII-Format. Es ist schlicht ein Standard, Informationen abzulegen, und zwar Text-Informationen!

XML1.GIF
Gängige Codierungen sind:
  • 'Windows-1252'
  • 'UTF-8'
  • 'UTF-16'
  • 'iso-8859-1'

Es gibt eine Header-Zeile, in der Informationen über die Zeichencodierung abgelegt sind und es gibt genau einen Wurzelknoten. Dessen Inhalt kann aus weiteren Knoten bestehen, so daß eine Baumstruktur entsteht. Jedes Element besitzt einen öffnenden (</tag>) und einen schließenden Tag (</tag>). Alle Unterknoten müssen geschlossen sein, bevor der übergeordnete Knoten geschlossen wird. Tags können mit Attributen angereichert werden (<tag attr="wert">). Es gibt (anders als im HTML) keine festen Tags und wenig sonstige Konventionen.

Was XML so leistungsfähig macht

Die eigentliche Struktur von XML ist sehr einfach. Sie ähnelt stark z.B. der von HTML, ist aber aufgrund ihrer Einfachheit wesentlich strikter. Eine Stärke ist sicherlich das Fehlen sämtlicher Eigenschaften. XML-Daten können mit standartisierten Methoden bearbeitet werden, die nichts über den Inhalt wissen müssen. Solche Methoden sind z.B. xpath zur Selektion von Daten oder XLST zur Formatierung.

Als Folge gibt es leistungsfähige Implementationen von XML-Parsern, die Files lesen und durchsuchen können. Es gibt eine Reihe von Open-Source-Entwicklungen, ich nutze allerdings den Microsoft-Parser, der seit dem InternetExplorer 5.0 mit jedem Windows-System ausgeliefert wird. Für den Einstieg gibt es hier einige Klippen, daher wird unten die Nutzung unter Delphi anhand konkreter Routinen dargestellt.

Es wird gerne betont, XML sei keine Datenbank. Das stimmt, aber nur zum Teil. Denn in vielen Fällen läßt sich ein XML-Parser als einfache Datenbank zweckentfremden. Insbesondere beim Einfügen von Daten ist hier sicher mit geringer Performance zu rechnen. Es gibt aber auch viele Vorteile. So arbeitet man mit einem einfachen ASCII-Text als Datenbasis, der sich sehr leicht prüfen und verändern läßt. Und jegliche Installation, wie sie z.B. bei der BDE notwendig ist, entfällt. Im übrigen ist die XML-Struktur ausgespriochen flexibel und sticht damit jede Datenbank aus. Da jeder Datensatz gewissermaßen seine eigene Datenmaske besitzt muß diese nicht zentral angelegt und gepflegt werden. Und die Datenstruktur kann bei Bedarf von Knoten zu Knoten wechseln, was große Vorteile bieten kann.

Langer Rede kurzer Sinn

Kurz: ein Versuch lohnt! Probieren Sie es einfach aus. Sie werden mit robusten, unkomplizierten Programmen belohnt. Überall wo Textdaten abgelegt werden sollen, ist XML die erste Wahl.

Doch vor das Probieren hat Microsoft noch ein paar kleine Hürden gesetzt. Denn manche Definitionen sind für Delphi-Programmierer alles andere als selbstverständlich. Ich beschreibe die Nutzung von MSXML unter Delphi7. Die Unit MSXML kapselt die Typelibary zur "MSXML.DLL", die zum Internetexplorer ab Version 5.0 gehört, also in jedem Windows-System bereits existiert.

Das sind die Grundtypen:
uses msxml;

database: IXMLDomDocument;
node, rootnode: IxmlDomNode;
nodeList: IxmlDomNodeList;
Element ist eine Ableitung von Node und enthält ein paar zusätzliche Funktionen. Normalerweise benutzt man Node. Diese Definitionen benötigen Sie zum Laden von XML-Dateien:
    database:= CoDOMDocument.create;
    database.load(filename);
    rootnode := database.documentElement;
Jetzt kommen ein paar Anwendungen:
    if rootnode.hasChildNodes then
       dosomething;
    node := rootnode.selectSingleNode('//kompo[@Id="123"]'); // das Element im Baum mit
                                                             // Attribut Id=123
    nodelist := rootnode.selectNodes('//kompo[@Id]');        // alle Elemente, die ein
                                                             // Attribut Id haben
    text := nodelist[i].text;
    text := nodelist[i].nodeTypedValue; // ist manchmal sauberer!
    rootnode.appendChild(node);
    text := node.attributes.getNamedItem('Id');
    Text := node.attributes[i].text;
    node := node.attributes[i]            // Attributes liefert wieder IXMLDomNodes!
    database.save(filename);
    database := Nil;        // Objekt freigeben nicht mit free!
So erzeugt man einen Baum ganz neu (Alle Kinder werden an Root angehängt):
var
   root: IXMLDomElement;
   PI: IXMLDomProcessingInstruction;
const   CodePage = 'Windows-1252'; // oder 'iso-8859-1'
begin
    database := CoDOMDocument.create;
    database.preserveWhiteSpace := True;
    PI := database.CreateProcessingInstruction('xml',
          Format('version="1.0" encoding="%s"', [codepage]));
    database.AppendChild(PI);
    root := database.CreateElement('ROOT');
    database.AppendChild(root);
end;
Das war eigentlich schon alles. Den Rest findet man in der MSN libary unter XML DOM Reference

XML und Binärdaten

Ich sagte bereits, XML ist ein Textformat. Ich bin stets den Weg gegangen und habe Binärdaten extern abgelegt, also nicht im XML. Sei es, daß Sie Bilder als JPG oder sogar notfalls in einem Zip-File ablegen. Ins XML gehört bestenfalls eine Referenz, also z.B. ein Dateiname. XML-Parser sind keine D-Züge. Bei einigen hundert kB stört das noch nicht so, aber bei großen Dateien werden sie die Verzögerung merken. Daher hat die strikte Trennung durchaus ihren Sinn.

Kritik am Microsoft-Parser

Grundsätzlich arbeitet der MSXML-Parser zuverlässig und schnell und sein Funktionsumfang ist weigehend vollständig, insbesondere was die x-path Implementation angeht. Es gibt dennoch einige unschöne Punkte, die z.T. aber schon vom W3C stammen.

Ein grundsätzlicher Punkt ist die Definition dessen, was denn ein Knoten ist. Nach der "reinen Lehre" ist alles das, was in einem Tag steht, Inhalt. Dies gilt leider auch für Zeilenumbrüche. Ein leerer Tag, der einen Zeilenumbruch enthält, ist also nicht mehr leer. Diese Logik macht es ausgesprochen umständlich, xml-Dateien sauber zu formatieren. Meines Wissens gibt es in MSXML auch keine Funktion dazu. Es gibt aber die Einstellung "PreserveWhitespace", die zumindest die Formatierung unangetastet läßt.

Aus gleicher Logik wird auch das Anlegen von Textknoten umständich. Erzeugt man ein IXMLDOMNode, dann ist dies nur ein leerer Tag. Der Text wird dann mit database.CreateTextNode erzeugt und dann an an den Knoten angehängt. Will ich also einen Text unter einem bestimmten Namen ablegen, dann sieht das gesamte Procedere so aus:
{ -------------------------------------------------
  Sub-Knoten mit Textinhalt erzeugen
  -------------------------------------------------}
Function CreateTextNode(const basenode: IXMLDomNode;
                              const nodeName, text: string): IXMLDomNode;
var node, textnode: IxmlDomNode;
begin
    node := basenode.ownerDocument.createElement(NodeName);
    textnode := basenode.ownerDocument.createTextNode(text)
    basenode.appendChild(node)
    node.appendChild(textnode);
    result := Node;
end;
Auch für den Zugriff ist dieses Verfahren sehr umständlich, denn man kann durch x-path Abfragen gar nicht direkt auf Texte zugreifen, denn Textnodes haben keinen eigenen Namen. Sie können nur Kind eines (benannten) Knotens sein. Ich muß also zunächst den Elternknoten suchen,Prüfen ob er nur einen Text enhält und diesen dann auswerten.

Unter dem Namen XMLUtil.pas habe ich mir daher eine Unit angelegt, die dieses Verhalten kapselt. Als Textnode wird hier abweichend von der Microsoft-Logik ein XML-Knoten verstanden, der nichts anderes als einen Text enthält. Im Gegenzug soll ein Knoten, der weiter Unterknoten enthält, keine Texte enthalten.

Die Unit kapselt Funktionen in der oben gezeigten Weise und vereinfacht so den Umgang mit XML-Daten. Als zweites wichtiges Element sind Funktionen enthalten, die das Anlegen von Dubletten vermeiden. Hierdurch können Index-Elemente erzeugt werden, die für Datenbankanwendungen unverzichtbar sind. Die Unit inst noch nicht voll dokumentiert. Außerdem finden sich noch Reste aus einer Programmentwicklung. Unter anderem wurden dort Knoten mit dem Namen "Id" verwendet, durch die Gleichnamige Tags unterschieden wurden. Dies schränkt den allgemeinen Nutzen der Unit aber nicht ein. Sie können sich die Testverstion gerne herunterladen und sie verwenden. Bei Fragen schicken Sie mir einfach eine Mail. Für sinnvolle Weiterentwicklungen bin ich ebenfalls immer zu haben.

Funktionen der Unit XMLUtil.pas:

    function nodetype(const n: IxmlDomNode): cardinal;
             // Typ des ersten Kindknotens
    function GetNodeText(const n0: IXMLDomNode;const  tag: string): string;
             // Text aus Knoten holen
    function GetNodeInt(const XmlNode: IXmlDomNode;const  tag: string): integer;
             // Text direkt in Int wandeln
    function GetNodeDouble(const XmlNode: IXmlDomNode;const  tag: string): double;
             // Text direkt in double wandeln
    function GetNodeAttribute(const n0: IXMLDomNode;const  tag: string): string;
             // Benanntes Attribut holen
    procedure GetNodeTextList(const n0: IXMLDomNode;const  tag: string; const result: TstringList);
              // Texte gleichnamiger Knoten als Stringlist auslesen
    function CreateXmlDoc(const RootNodeName: string = 'ROOT'): IXmlDomDocument;
             // xml-Dokument erzeugen
    procedure ClearNode(const node: IXMLDomNode);
              // Einen Knoten komplett löschen
    procedure copyDoc(xml,xml2: IxmlDomDocument);
              // Ein ganzes Dokument kopieren
    Function CreateNode(node: IXMLDomNode; nodeName: string): IXMLDomNode;
             // Unterknoten erzeugen
    Function CreateSingleNode(const node: IXMLDomNode;const  nodeName: string): IXMLDomNode;
             // Unterknoten suchen oder erzeugen
    Function CreateText(const node: IXMLDomNode;const  text: string): IXMLDomNode;
             // Text erzeugen
    Function CreateTextNode(const node: IXMLDomNode;const  nodeName, text: string): IXMLDomNode;
             // Subnode mit Textinhalt erzeugen
    function IsTextNode(const node: IXMLDomNode): boolean;
             // Auf Textnode prüfen
    Function CreateSingleTextNode(const node: IXMLDomNode; const  nodeName, text: string): IXMLDomNode;
             // Textknoten suchen und überschreiben, ggfs. neu erzeugen
    Function CreateNodeAttribute(const node: IXMLDomNode;const  attrib, text: string): IXMLDomNode;
             // Benanntes Attribut erzeugen
    Function CreateSingleAttribute(const node: IXMLDomNode;const  attrib, text: string): IXMLDomNode;
             // Attribut suchen und überschreiben, ggfs. neu erzeugen
    function SelectOrCreateSubNode(const node: IXMLDomNode;const  subnodeName: string): IXMLDomNode;
             // Benannten Knoten auswählen oder neu erzeugen
    function SelectOrCreateTextNode(const node: IXMLDomNode;const  TextnodeName, Text: string): IXMLDomNode;
             // Benannten Textknoten auswählen oder neu erzeugen ohne Überschreiben



Weitere Quellen: w3schools  SelfHTML

Eckehard Fiedler

Merke: Die dümmsten Programmierer haben die dicksten Programme!