1. Flat File als Input Datei
Im Wesentlichen gibt es zwei Typen von Flat Files: solche, deren Spalten eine feste Breite haben und solche, deren Spalten mit einem Zeichen separiert sind. Ein populäres Beispiel für Letzteres sind CSV Dateien, und genau damit befasst sich dieser Artikel.
1.1 CSV Input Datei
Nehmen wir als Beispiel eine Liste von Ausstellungsstücken für eine Wanderausstellung eines Instrumenten-Museums.
Diese Liste ist separiert mit Strichpunkten und beinhaltet folgende Felder:
- Typ (Text)
- Hersteller (Text)
- Modell (Text)
- Baujahr (Ganzzahl)
- Datum, an dem es vom Museum erstanden wurde (Datum)
- Versicherungsprämie (Dezimalzahl)
Die einzelnen Datensätze sind mit Zeilenumbruch/Zeilenvorschub (CR/LF) separiert.
Hier die Datei:
Git;Gibson;Explorer II;1981;1983-10-10;150
Git;Fender;Stratocaster;1989;1992-05-06;120
Git;Ibanez;Artist;1986;1996-08-24;60
Amp;Framus;Konsul;1958;2022-08-20;30.15
Amp;Fender;Bandmaster;1970;1991-07-14;80
1.2 Schema
1.2.1 Schema und Schema Elemente erstellen
1. In einem BizTalk Projekt an dem Knoten, der das Schema enthalten soll, einen Rechtsklick machen und dann Add --> New Item… auswählen.
2. Im Add New Item Dialog BizTalk Project Items --> Schema Files und dann Flat File Schema anwählen, einen sinnspendenden Namen geben und Add klicken
3. Das Schema im Schema Editor aufmachen, wenn es nicht schon geöffnet ist, <Schema> anwählen und dann im Properties Fenster sicherstellen, dass bei Schema Editor Extension die Flat File Extension angewählt ist.
4. Der erste Knoten in einem Schema heißt per Default immer Root – kann man umtaufen, wenn man will – Rechtsklick auf den Knoten im Schema Editor, Rename aus dem Kontext Menu anwählen und neuen Namen eingeben …
…oder Knoten anwählen und in den Properties den Node Name anpassen
(hier wurde der neue Name 'Items' gerade eben eingegeben – nach Drücken von CR steht dann auch bei RootNode TypeName 'Items')
5. Jetzt brauchen wir unter dem Root Knoten, der neuerdings 'Items' heißt, ein Element für die Datensätze aus der Input Datei. Im Schema Editor Rechtsklick auf den Root Knoten und dann
Insert Schema Node Child Record auswählen:
(der so eingefügte neue Knoten heißt per Default Record und kann wie der Root Knoten auch umgetauft werden – etwa auf 'Item').
6. Jede Nachricht muss mindestens einen Record haben (sonst macht die ganze Nachricht keinen Sinn) und kann beliebig viele Records enthalten, also werden in den Properties dieses Knotens Min- und Max Occurs entsprechend gesetzt: Im Schema Editor den Record Knoten anwählen und in den Properties folgende Werte setzen:
- Min Occurs 1
- Max Occurs unbounded
7. Unter dem Record Knoten Item werden nun die einzelnen Felder angelegt. Dabei wird für jedes einzelne Feld wie folgt vorgegangen:
a) Rechtsklick auf den Record Knoten und dann aus dem Kontext Menu Insert Schema Node --> Child Field Element auswählen
b) Das so erstellte Element heißt per Default Field und kann sofort oder irgendwann später umgetauft werden:
c) Per Default sind alle Felder nach dem Anlegen erstmal vom Datentyp string – ist etwas anderes gewünscht, dann muss das Feld im Schema Editor angewählt und dann in den Properties der Data Type entsprechend gesetzt werden.
Built (Baujahr) beispielsweise soll ein Integer sein (bzw. int – hier reichen 32 bit):
In unserem Beispiel brauchen noch zwei Felder andere Datentypen als string:
- Acquired (Datum des Kaufs): xs:date
- Insurance (Versicherungsprämie): xs:decimal
In seiner Gänze sieht das Schema nun so aus:
Ein Root Knoten Items, ein Record Knoten Item und darunter die einzelnen Felder.
1. Jetzt ist die Struktur des Schemas mit all seinen Elementen fertig definiert und wir können es mal mit der CSV Datei testen.
Im Solution Explorer das Schema anwählen und in den Properties bei Input Instance Filename auf die Schaltfläche mit den drei Punkten rechts des Feldes klicken und die Testdatei auswählen.
Der File Explorer, mit dem das File gesucht werden kann, ist auf den Dateityp XML eingestellt und man muss das erst auf All files (*.*) ändern, um die CSV Datei zu finden.
2. Im Solution Explorer Rechtsklick auf das Schema und dann Validate Instance anwählen:
Und sofort bekommen wir Fehler, die im Output Fenster angeschaut werden können:
- …ItemsTouringExhibition.xsd: warning BEC2004: Record "Item" - The Child Delimiter Type property is not set for a delimited record.
- …ItemsTouringExhibition.csv: warning BEC2004: Data at the root level is invalid. Line 1, position 1.
Die erste Meldung, die sich auf das Schema bezieht, beklagt, dass da gar kein Trennzeichen angegeben ist und die zweite Meldung, die sich auf die Input-Datei bezieht, sagt, diese sei sowieso nicht gültig, denn eigentlich erwartet das Schema, obwohl als Flat File Schema angelegt, immer noch eine XML Datei als Input.
Es gibt also noch einiges zu tun, bevor unser Schema funktioniert.
1.2.2 Schema konfigurieren
1.2.2.1 Input Format
In den Properties des Schemas im Block General, wo auch die Input Instance Datei angegeben wurde, gibt es die Eigenschaft Validate Instance Input Type, welche per Default auf XML steht.
Dies muss auf Native geändert werden:
Wenn jetzt nochmals die Eingabedatei validiert wird (Rechtsklick auf Schema im Solution Explorer, dann Validate Instance auswählen) kriegen wir immer noch den Fehler wegen der fehlenden Delimiter Konfiguration, aber ungültige Daten in der Datei werden nicht mehr gemeldet.
1.2.2.2 Delimiter
Child Delimiter Record
Die Definition der Separatoren, Trennzeichen oder Delimiter bringt dem Schema bei, aus den Datensätzen in der flachen Input-Datei Elemente mit Feldern für die XML Ausgabedatei zu bilden.
- Schema im Schema Editor öffnen (Doppelklick auf das Schema im Solution Explorer)
- Im Schema Editor den Record Knoten anwählen (gleich unter dem Root Knoten – heisst im Beispiel Item)
- Sicherstellen, dass in den Properties im Block Flat File die Eigenschaft Structure auf Delimited gesetzt ist
- Im selben Properties Block müssen noch drei weitere Eigenschaften gesetzt werdena)
a) Child Delimiter Type Character
b) Child Delimiter Das verwendete Trennzeichen, eg. c) Child Order Infix
Der gesamte Properties Block Flat File sieht nun so aus:
So, jetzt erneut testen – und wieder ein Fehler:
error BEC2004: The 'Insurance' element is invalid - The value '150
Git;Fender;Stratocaster;1989;1992-05-06;120
Git;Ibanez;Artist;1986;1996-08-24;60
Amp;Framus;Konsul;1958;2022-08-20;30.15
Amp;Fender;Bandmaster;1971;1991-07-14;80' is invalid according to its datatype 'http://www.w3.org/2001/XMLSchema:decimal'
Offenbar kommt da ein ungültiger Wert für das letzte Feld Insurance daher, welches mit dem Datentyp xs:decimal definiert wurde. Um besser zu sehen, was hier genau passiert ist, kann man den Datentyp von Insurance für einen Test mal aus xs:string setzen und dann die Instanz nochmals validieren. Jetzt gibt es keinen Fehler mehr! Und so sieht jetzt die Output Datei aus:
<Items xmlns="MusicMuseum.Schemas.ItemsTouringExhibition">
<Item xmlns="">
<Type>Git</Type>
<Brand>Gibson</Brand>
<Model>Explorer II</Model>
<Built>1981</Built>
<Acquired>1983-10-10</Acquired>
<Insurance>150
Git;Fender;Stratocaster;1989;1992-05-06;120
Git;Ibanez;Artist;1986;1996-08-24;60
Amp;Framus;Konsul;1958;2022-08-20;30.15
Amp;Fender;Bandmaster;1971;1991-07-14;80</Insurance>
</Item>
</Items>
Die ersten fünf Felder wurden korrekt mit dem Inhalt aus dem ersten Datensatz befüllt – das stimmt schon mal hoffnungsvoll. Im letzten Feld kommt dann der Wert des ersten Datensatzes für dieses Feld gefolgt vom ganzen Rest der Input Datei, was natürlich nicht so schön ist.
Child Delimiter Root
Bis jetzt haben wir dem Schema ja erst gesagt, wie es einen Record in einzelne Elemente zerlegen kann, und wie es die einzelnen Records erkennen soll, weiß es noch nicht.
1. Im Schema Editor den Root Knoten (Items) anwählen
2. Wiederum den Properties Block Flat File editieren, und zwar wie folgt:
- a. Structure Delimited
- b. Child Delimiter Type Hexadecimal
- c. Child Delimiter 0x0D 0x0A (hexadezimale Repräsentation von CR/LF)
- d. Child Order Infix
Nochmals testen – hier das Resultat:
<Items xmlns="MusicMuseum.Schemas.ItemsTouringExhibition">
<Item xmlns="">
<Type>Git</Type>
<Brand>Gibson</Brand>
<Model>Explorer II</Model>
<Built>1981</Built>
<Acquired>1983-10-10</Acquired>
<Insurance>150</Insurance>
</Item>
.
.
.
<Item xmlns="">
<Type>Amp</Type>
<Brand>Fender</Brand>
<Model>Bandmaster</Model>
<Built>1971</Built>
<Acquired>1991-07-14</Acquired>
<Insurance>80</Insurance>
</Item>
</Items>
Der Übersichtlichkeit halber sind hier nur der erste und der letzte Record abgebildet denn die dazwischen sehen genau gleich aus. Das Schema ist fertig und funktioniert! Jetzt nur nicht vergessen, den Datentyp von Insurance wieder auf xs:decimal zu ändern ;-).
1.3 Stolperfallen
1.3.1 Child Order
Die Eigenschaft Child Order bestimmt, wo die Trennzeichen kommen. Lassen wir uns bei einem Blick auf unsere Test-Input-Datei mal die Steuerzeichen anzeigen:
Zwischen jedem Feld kommt ein Strichpunkt und zwischen jedem Datensatz kommt ein CR/LF Paar, und nur dazwischen – nicht am Anfang, nicht am Ende. Deshalb funktioniert es, wenn Child Order auf Infix gestellt ist.
Zuweilen kommen aber CSV Dateien daher, die auch hinter dem letzten Datensatz ein CR/LF Paar stehen haben, oder ein Trennzeichen nach dem letzten Feld oder vor dem ersten.
Schauen wir mal, was passiert, wenn wir mit diesen Einstellungen (Infix) eine Datei verarbeiten, die nach dem letzten Datensatz noch ein CR/LF Paar aufweist:
Es gibt einen Fehler:
error BEC2004: Unexpected end of stream while looking for:
';'
Gemäss Einstellungen erwartet das Schema nach dem letzten CR/LF Paar einen weiteren Record, und auch wenn der völlig leer wäre, müsste da immer noch eine Reihe Strichpunkte kommen.
Um solche Dateien fehlerfrei verarbeiten zu können, muss Child Order am Root Knoten auf Postfix gestellt werden.
Ein kurzer Test:
<Items xmlns="MusicMuseum.Schemas.ItemsTouringExhibition">
<Item xmlns="">
<Type>Git</Type>
<Brand>Gibson</Brand>
<Model>Explorer II</Model>
<Built>1981</Built>
<Acquired>1983-10-10</Acquired>
<Insurance>150</Insurance>
</Item>
.
.
</Items>
Alles wieder im Lot.
Untersuchen wir mal das Verhalten des Schemas, wenn es Dateien mit einem Trennzeichen nach dem letzten Feld verarbeiten soll (wiederum mit der Einstellung Infix):
Es gibt ein paar Fehler – für jede Zeile den gleichen:
error BEC2004: The 'Insurance' element is invalid - The value '150;' is invalid according to its datatype
Das letzte Trennzeichen in der Zeile wird einfach zum Wert für das letzte Feld hinzugenommen, und '150;' ist nicht mehr numerisch.
Dieser Fehler, oder besser gesagt, dieses Fehlverhalten des Schemas ist besonders dann sehr tückisch, wenn das letzte Feld vom Datentyp xs:string ist – dann gibt's nämlich gar keinen Fehler, sondern es wird einfach stillschweigend dem Wert, der in das Feld kommen soll, noch ein Trennzeichen hinten dran gehängt.
Auch hier ist das Heilmittel Child Order auf Postfix zu setzen.
Werfen wir noch einen Blick auf den umgekehrten Fall: ein Trennzeichen ist vor dem ersten Feld gesetzt:
Diesmal gibt es einen ganzen Haufen Fehler – hier der erste:
error BEC2004: The 'Built' element is invalid - The value 'Explorer II' is invalid according to its datatype
Durch das Trennzeichen am Anfang des Records verschieben sich die ganzen Inhalte um ein Feld nach hinten und auf einmal haben wir einen String für ein Feld, das ein Integer verlangt. Diese Fehlkonfiguration ist vor allem dann verheerend, wenn alle Felder im Schema vom Datentyp xs:string sind – auch dann gibt es keinen Fehler, aber falschen Output:
<Items xmlns="MusicMuseum.Schemas.ItemsTouringExhibition">
<Item xmlns="">
<Type> </Type>
<Brand>Git</Brand>
<Model>Gibson</Model>
<Built>Explorer II</Built>
<Acquired>1981</Acquired>
<Insurance>1983-10-10;150</Insurance>
</Item>
.
.
.
</Items>
Das erste Feld bleibt leer (vor dem ersten Trennzeichen steht ja nichts), alle folgenden Werte verschieben sich um ein Feld nach hinten und die Werte für die beiden letzten Felder werden dann einfach zusammen, einschliesslich des dazwischen sitzenden Trennzeichens, in das letzte Feld gepackt.
Um das zu korrigieren, muss Child Order auf Prefix gestellt werden:
Child Order Einstellungen zusammengefasst
- Infix – Trennzeichen stehen zwischen all den Feldern aber nicht vor dem ersten oder nach dem letzten Feld:
Git;Gibson;Explorer II;1981;1983-10-10;150
- Postfix – Trennzeichen stehen zwischen all den Feldern und nach dem letzten Feld:
Git;Gibson;Explorer II;1981;1983-10-10;150;
- Prefix – Trennzeichen stehen zwischen all den Feldern und vor dem ersten Feld:
;Git;Gibson;Explorer II;1981;1983-10-10;150
1.3.2 Trennzeichen in Inhalten von Textfeldern
Immer wieder kommt es vor, dass Trennzeichen in als Teil eines Strings auftauchen, wie etwa hier:
Git;Fender;Stratocaster;Standard;1989;1992-05-06;120
Der Wert für das Feld Model soll jetzt also Stratocaster;Standard sein. Wenn das verarbeitet wird, ist das Resultat nicht sehr überraschend – es gibt wieder eine Reihe Fehler, wie etwa den hier:
error BEC2004: The 'Built' element is invalid - The value 'Standard' is invalid according to its datatype
Wiederum verschieben sich alle Felder nach hinten, und wenn nicht alles Textfelder sind, dann gibt es irgendwann einen Typenfehler (sind alle Felder Textfelder, dann gibt es keinen Typenfehler, aber falsche Feldinhalte, wie schon bei den Beispielen zuvor). Wenn damit zu rechnen ist, dass das Trennzeichen als Teil eines Feldinhaltes daher kommt, dann muss dem Trennzeichen im Feldinhalt ein Escape Charakter vornan gestellt werden. Per Default gibt es keinen Escape Character, aber es kann einer im Properties Block Flat File eingestellt werden, am Root- oder am Record Knoten (in diesem Beispiel hier am Record):
Escape Character Type Character
Escape Character /
Mit diesen Einstellungen und dem Input
Git;Fender;Stratocaster/;Standard;1989;1992-05-06;120
erhalten wir diese Ausgabe:
<Item xmlns="">
<Type>Git</Type>
<Brand>Fender</Brand>
<Model>Stratocaster;Standard</Model>
<Built>1989</Built>
<Acquired>1992-05-06</Acquired>
<Insurance>120</Insurance>
</Item>
Hier wurde ein Slash als Escape Character gewählt, einfach weil das ziemlich üblich ist. Es kann aber auch ein anderes Zeichen verwendet werden, und wenn damit zu rechnen ist, dass ein Slash in einem Feldinhalt vorkommt, dann muss ein anderes Zeichen gewählt werden.
Nun muss man nur noch dafür sorgen, dass die Input Dateien immer mit einem Escape Character vor einem Trennzeichen in einem Feldinhalt daher kommen – dieses Problem lässt sich leider nicht mit dem Flat File Schema lösen…
Dies ist ein Beitrag von Robert, QUIBIQ Schweiz AG