Know How - XML

Hier einige Worte zu XML oder wie ich arbeite.

Links

  • xml2sql-v und eine kleine FAQ dazu.


    Dieses Tool habe ich geschrieben um beliebige korrekte XML-Dateien in beliebige SQL-Datenbanken pumpen zu können, ohne dass vorher das Schema der XML-Datei bekannt sein muss. Man kann danach die XML-Inhalte mit SQL weiterverarbeiten. Es ist also ein "generischer XML-Loader" der dem XML-File keinerlei Strukturen aufzwingt.

Kommentare

Heise

Rein per SQL, raus per XML. XML in der DB brauchte ich nie

MySQL hat die Möglichkeit, den Query als XML zu dumpen. Das nutze ich gerne um das in andere Tools zu pipen.

Das hat natürlich wenig mit XML-Fähigkeit der Datenbank zu tun, aber ich vermisse solch ein natives Feature immer bei anderen Datenbanken (hm, da fällt mir ein, ich könnte dieses Wochenende sq mal XML beibringen:
www.scylla-charybdis.com/tool.php?tool=sq
SQ ist ein Query-Frontend für SQLite3 das für die Nutzung via Bash optimiert ist).

XML hat den entscheidenden Vorteil dass es die Ergebnise (Spalteninhalte) ohne irgendwelche Kopfstände in andere Scripte transportieren kann. Das funktioniert mit anderen Methoden nicht so leicht, da man immer Schwierigkeiten mit Freitexten hat die in so Datensätzen vorkommen können.

Die XML-Fähigkeit von MySQL ist allerdings extrem rudimentär. Es reicht mir jedoch. Und as Gute ist, dass langsam so gut wie jede Datenbank XML als Query-Ergebnis liefern kann.

Rein geht es per SQL, da gibt es im Wesentlichen nur 2 Dialekte, "echtes" SQL (Hochkommata müssen verdoppelt werden um sie zu dequoten) und das MySQL-Derivat (per Backslash, da recht ein kurzes SED/AWK um das aus echtem SQL zu bauen). Rauswärts geht es eben mit XML.

Die Schnittstelle ist also ein beliebiges Commandline-basiertes Query-Tool das SQL ausführen kann und mit den richtigen Schaltern XML erzeugt. Wenn letzteres nicht geht kann man noch ein AWK basteln und muss eben hoffen, dass der Spaltenseparator nicht vorkommt. Geht das nicht muss man den mühsamen Weg über einen Export beschreiten. Eine direkte XML-Fähigkeit des Query-Tools ist da echt von Vorteil.

Der Weg den ich beschreite ist also oft:

Rein per SQL.
Raus per XML.

Wenn ich eine XML-Datei in eine Datenbank pumpen muss, dafür habe ich vor Jahren schon xml2sql-v geschrieben:
www.scylla-charybdis.com/tools.html#xml2sql-v
(Das Tool hat zwar einen Bug im Makefile, aber der ist so lächerlich, dass den jeder schon selber finden sollte.)

Richtig, das Tool ist uralt, aber es gab noch keine besondere Notwendigkeit, es zu ändern weil es genau das tut was es soll: Den Inhalt eines beliebigen (korrekten) XML per SQL in eine Datenbank zu pumpen, so dass man die XML-Strukturen anschließend mit SQL herausfinden kann (naja, braucht meistens eine dieser üblen Pivot-Transformationen eg) und die Inhalte somit aus den Transfertabellen herausfischen kann. (Hey, ich sage nicht, dass das für jeden einfach ist, aber der Vorteil ist, es funktioniert ohne Mucken, da kann also der letzte Mist ankommen aber so lange es XML ist funktioniert es).

Der XML-Parser meiner Wahl ist übrigens EXPAT. Das Teil ist ziemlich leicht benutzbar, kenn zwei Modi die eigentlich immer ausreichen (Iterativ und Callback) und ist vor allem eine korrekter XML-Parser (also dass der beim ersten falschen Zeichen die Grätsche macht ist kein Feature sondern eine Grundvoraussetzung). Abgesehen davon ist er echt gutmütig. xml2sql-v ist solch ein (übrigens ziemlich einfach gestricktes) EXPAT-basiertes Programm - deshalb muss ich es auch nicht ändern, die eigentliche Arbeit erledigt EXPAT seit Jahren.

Wer sich jetzt fragt, was das mit XML-Fähigkeit einer Datenbank zu tun hat:

Wenn ich mich so umsehe wie die Leute so ihre XML-Dateien bauen, kommt einem gerne das kalte Grausen. Ich habe schon folgendes erlebt (ich bin übrigens kein XML-Gott sondern nur ein XML-Wurm. Sprich, mit Schemata oder XSLT habe ich nie was gemacht):

  • Entitätenchaos: Die Entitäten werden falsch mitgeliefert, in einer UTF-8-Datei kommen direkte Latin-1-Zeichen vor. Entsprechender Fixer ist in xml2sql-v deshalb vorhanden.
  • Landessprachen: Statt UTF-8 wird eine der unbekannteren Landessprachen verwendet, was Expat sofort maulen lässt. Expat um dieverse Zeichensätze zu erweitern ist schwierig, weshalb man sich auf UTF-8 bzw. Latin-1 beschränken sollte. UTF-8 unterstützt auch Asiatisch. Wenn es also nicht ISO-8859-1 ist dann bitte UTF-8 nehmen!
  • Leerelemente: Ein Schema ist eh für die Katz (das zu parsen macht Programme zu kompliziert - man will ja nur an die Daten, das Schema hilft da eher wenig), also fehlt das grundsätzlich oder ändert sich schneller als die Unterhose des Programmierers der den Mist verbrochen hat. Warum dann aber einige Leute auf die Idee kommen, die Daten in die Element-Attribute statt in den Inhalt eines Elements zu sperren und ausschließlich mit Leerelementen zu arbeiten entzieht sich vollständig meinem Verständnis.
  • Singulärelement: Aber das mit den Leerelementen (was vermutlich aus einem rekursiven Strukturdump entspricht) ist noch genial zu nennen gegen die Konsorten, die grundsätzlich nur ein Element kennen, das dann wie wild verschachtelt wird, und dessen Typ über die Attribute definiert wird!
  • Verschärfter: Dann gibt es natürlich die Leute, die jede beliebige Mischform erstellen, und das am besten noch wechselnd im selben XML gerade wie es lustig ist, wobei natürlich jegliches Anfangs-End-Tag vergessen wird, sprich, man hat bei N Datensätzen N Anfänge und N Enden. Das habe ich auch schon Rekursiv erlebt, d. h. die XML-Datei enthält ausschließlich öffnende TAGs bis die Daten alle gedumpt wurden, und dann folgen auf einen Schlag alle schließenden TAGs, "damit es korrrektes XML wird".
An letzterem beißt man sich auch mit "naivem XSLT" die Zähne aus ich löse das mit einem AWK, der einfach jedem OPEN-TAG das fehlende CLOSE-TAG voranstellt und die am Dateiende dann wegwirft.

Also das typische XML sieht so aus, das entspricht dem, was MySQL produziert:
<?xml version="1.0" encoding="ISO-8859-1"?>
<query>
<row>
  <vorname>Werner</vorname>
  <nachname>Müller</nachname>
  <money>100,00€</money>
</row>
<row>
  ..usw..
</row>
</query>
Das EUR-Zeichen (€) ist aber typischeweise nicht in ISO-8859-1 abgelegt, sondern kommt z. B. als Unicode-Zeichen (UTF-8) daher. Und schon streikt der Expat.

Die verschärfte Form sieht dann so aus:
<?xml version="1.0" encoding="ISO-8859-1"?>
<query>
<row>
  <vorname text="Werner" />
  <nachname text="Müller" />
  <money currency="100,00€" />
</row>
<row>
  ..usw..
</row>
</query>
Klar, man sieht deutlich, wie das Räderwerk vom Programmierer da gearbeitet hat. Das ist aber Hirnriss par Excellance. Dass sich das Attribut auch noch anhand des Elementinhalts ändert ist einfach schlecht vollautomatisch zu parsen! Ein
<money type="Currency">100,00&euro;</money>
ist da viel besser. Auf diese Weise ist das Attribut das, was es sein soll, ein Hinweis den man ignorieren kann wenn man ihn nicht braucht.

Die zweite Verschärfungsstufe ist dann diese:
<?xml version="1.0" encoding="ISO-8859-1"?>
<data name="row1">
  <data name="Vorname" text="Werner" />
  <data name="Nachname" text="Müller" />
  <data name="Money" currency="100,00€" />
</data>
<data name="row2">
  ..usw..
</data>
Hier fehlt das gruppierende äußere TAG, man weiß also nicht, hat man alle Daten oder fehlt da etwas, und einige Parser (nicht Expat) parsen dann natürlich auch nur row1.

Besonders hilfreich ist auch das Durchnummerieren der Rows im Inhalt des Namens-Attributs. Diese Information ist ja sehr wichtig, da Computer schlecht zählen können. Auf ein wesentlich brauchbares wurde natürlich aus naheliegenden Gründen verzichtet.

So, und nun die ultimative Steigerungsform des Ganzen:
<?xml version="1.0" encoding="ISO-8859-1"?>
<data row=1 name="Vorname" text="Werner">
<data row=1 name="Nachname" text="Müller">
<data row=1 name="Money" currency="100,00€">
<data row=2 name="Vorname" text="Hans">
<data row=2 name="Nachname" text="Meier">
<data row=2 name="Money" currency="20,00€">
..usw..
</data></data></data></data></data></data>
Der Vorteil ist natürlich, dass diese Schreibweise auch das Ende der Datei klar markiert. Nur haben rekursive XML-Parser selten diese Millionen von Rekursionen.

Auch "gut gelöst" ist die Frage, wie man die nächste Zeile erkennt. Natürlich kann man das problemlos sehen indem man die ROW-Nummer anguckt, aber das muss man dem Computer erst einmal beibringen.

Typische DOM-Parser brauchen für das Parsen einer nach dem Ultimativen Schema erstellten 100 MB-XML-Datei übrigens gerne mal mehr als 1 GB an RAM. Jetzt wissen wir auch, warum 64 Bit Adressraum bei Computern keinerlei Luxus mehr darstellen darf.

Wer fragt wie es zu so etwas kommt:

Typischer Projektmurx. Der eine schreibt den Exportfilter der dann Semi-XML-artige Rows rauswirft, und irgendwer stellt dann fest, dass das nicht korrektes XML ist. Da es sich um einen Entladevorgang handelt, will man zwingend auch sicherstellen, dass ein Abbruch des Daten-Dumps auch erkannt wird (was die Form
<data ... />
nicht kann).

Den naheliegenden Schritt, es in ein weiteres Tag einzuschachteln, kommt man nicht, vielelicht weil man befürchtet, dass andere weiterverarbeitende Tools (die arbeiten zeilenweise auf der Textdatei) dies in den falschen Hals bekommen, also hängt man hinten einfach per zweitem
select '</data>' from table"
(kein Scherz) die fehlenden Tags hinten dran.

Es funktioniert natürlich in den Tests mit den 1000 Datensätzen wunderbar, bis der erste Load ansteht der ca. 3 Mio Datensätze via XML in einem Batch pumpen muß. Nachdem der DOM-Parser dann allen verfügbaren Adressraum aufgebraucht dumpt DrWatson ein über 2 GB Dumfile auf die Platte und alle sind ratlos, warum es plötzlich nimmer geht.

Noch irgendwelche Fragen zu XML und wie man es damit auch schlimmer machen kann?

-Tino