Proportional zur wachsenden Bedeutung von XML im Bereich des Datenaustauschs wächst auch bei den RPG-Programmierern der Bedarf, XML-Dateien verarbeiten zu können. Bisher wurde diesbezüglich immer auf Java verwiesen, zum steigenden Verdruss der RPG-Fraktion. Ab der OS/400-Version V5R1M0 bietet IBM jedoch das Lizenzprodukt „XML Toolkit for iSeries V1R1“ (5733XT1) an, mit dem nun auch über prozedurale Sprachen wie RPG und Cobol der Zugriff auf XML ermöglicht wird. Leider gestaltet sich jedoch der Einstieg in das XML-Toolkit aufgrund der recht rudimentären und C-lastigen Dokumentation als schwierig. Dieser Beitrag setzt sich zum Ziel, hier eine Einstiegshilfe zu geben. Vor der Betrachtung der technischen Aspekte müssen zuerst die Begriffe „XML-Dokument“, „DOM-Objekt“ und „.xml-Datei“ eingeordnet werden. Umgangssprachlich wird der Begriff „XML-Dokument“ oft gleichbedeutend mit einer .xml-Datei und einem DOM-Objekt (document object model) gleichgesetzt. Da innerhalb der IBM-Dokumentation „XML-Dokument“ stellvertretend für DOM Objekt steht, erscheint es sinnvoll, dieser Konvention zu folgen. Ein XML-Dokument ist demnach eine nicht lesbare Darstellung einer XML-Baumstruktur im Hauptspeicher der iSeries. Es besteht aus einer Vielzahl von Objekten, welche die XML-Daten im Unicode Zeichensatz verwalten. Eine .xml-Datei hingegen ist eine lesbare Darstellung einer XML-Baumstruktur, die in Form einer PC-Datei vorliegt und die mit jedem beliebigen Editor bearbeitet werden kann.
Eine .xml-Datei kann mit Hilfe eines XML-Parsers eingelesen werden. Als Ergebnis entsteht ein XML-Dokument im Hauptspeicher des Computers. Alternativ dazu kann ein XML-Dokument auch von Hand konstruiert werden. Das XML Toolkit stellt sowohl Parserfunktionen als auch Funktionen für die manuelle Erstellung eines XML-Dokuments zur Verfügung. Um ein XML-Dokument wieder in eine lesbare Darstellung zu überführen, muss es materialisiert (sichtbar gemacht) werden. Dieser Vorgang wird leider weder von dem XML Toolkit noch von den darunter liegenden Java Parsern (Xerces und SAX Parser) unterstützt und muss von Hand durchgeführt werden. Das XML Toolkit stellt hierzu Funktionen für die Navigation (document object) durch ein XML-Dokument zur Abfrage von Attributwerten (node object), sowie zur Ausgabe von Zeichenketten in IFS-Dateien (formatter object) zur Verfügung.
Verwendung des XML Tookit
Um das XML Toolkit in eigenen Programmen verwenden zu können, muss das XML4PR500 copy book in die entsprechenden Module eingebunden werden. Das XML4PR500 copy book enthält die Prototypen der Parserfunktionen sowie Konstanten und Datenstrukturen für die Arbeit mit dem XML Toolkit.
Da es sich bei dem XML Toolkit um ein Wrapper-Serviceprogramm für die darunter liegenden objektorientierten Parser handelt, werden von vielen Funktionen des XML Toolkits Referenzen auf Objekte zurückgegeben. Dies ist für einen RPG-Programmierer nicht nur neu, sondern auch gewöhnungsbedürftig, da er ab sofort peinlich genau darauf achten muss, diese Referenzen wieder freizugeben, sofern die Objekte nicht mehr benötigt werden. Das XML Toolkit versteht an dieser Stelle keinen Spaß und verweigert auch schon einmal die Arbeit, wenn es hier nicht korrekt behandelt wird. Konkret bedeutet dies, dass z.B. nach einem Aufruf der QxmlXercesDOMParser_new-Funktion und dem Parsen einer .xml-Datei die QxmlXercesDOMParser_delete-Funktion aufgerufen werden muss, wenn der Parser und das erzeugte XML-Dokument nicht mehr benötigt werden. Im Prinzip ist es so, dass zu jeder *_new-Funktion eine passende *_delete-Funktion vorhanden sein sollte und diese Funktionen unbedingt paarweise verwendet werden müssen. Die Speicherung und Verarbeitung der Objektreferenzen erfolgt über Variablen vom Typ Pointer.
Initialisierung des XML Tookit
Vor der Verwendung des XML Toolkit muss es mit der QxmlInit-Funktion initialisiert werden. Als Argument erwartet die QxmlInit-Funktion eine Adresse auf eine Datenstruktur vom Typ Qxml_DomExcData:
DQxml_DOMEXCDATA DS D Qxml_DOMRTNCOD 10I 0 D Qxml_RESERVE 257
Über die Datenstruktur Qxml_DomExcData meldet das XML Toolkit alle auftretenden Fehler an die aufrufenden Funktionen zurück. Das Feld Qxml_DomRtnCod enthält den Fehlercode des aufgetretenen Fehlers. Alle Fehlercodes sind in dem XML4PR500 copy book hinterlegt. Der Wert Qxml_DOMNOERROR zeigt an, dass eine Funktion ordnungsgemäß beendet worden ist und stellt keinen Fehler im eigentlichen Sinn dar. Zweckmäßigerweise erstellt man sich über die Anweisung LIKEDS(Qxml_DomExcData) eine entsprechenden Datenstruktur und ruft dann die Qxml_init-Funktion auf:
QxmlInit(%addr(domExcData))
Ab jetzt werden alle auftretenden Fehler in der Datenstruktur domExcData gemeldet.
Parsen eines Dokuments
Nach der Initialisierung des XML Toolkit, kann nun eine .xml-Datei geparst werden, um im Ergebnis ein XML-Dokument im Hauptspeicher der iSeries zu erhalten. Inklusive einer Verarbeitung des Dokuments sind hierzu folgende Schritte erforderlich:
– Erstellen Parser
– Erstellen Eingabequelle
– Starten Parser
– Löschen Eingabequelle
– Ermitteln Referenz auf das XML-Dokument und Verarbeitung
– Löschen XML-Dokument
– Löschen Parser
Erstellen Parser
Ein neuer Parser wird mit Hilfe der QxmlXercesDOMParser_new-Funktion erzeugt. Als Argument erhält die Funktion eine Datenstruktur vom Typ Qxml_SaxExcData. Die Datenstruktur Qxml_SaxExcData dient dazu, Fehler des Parsers an die aufrufende Funktion zu melden. Dies betrifft jedoch ausschließlich semantische oder strukturelle Fehler innerhalb der .xml- oder der .dtd-Datei.
DQxml_SAXExcData DS D Qxml_ErrorType 10I 0 D Qxml_RtnLine 10I 0 D Qxml_RtnCol 10I 0 D Qxml_ErrMsg 256A D Qxml_DataSrc 256A
Sollte es beim Aufrufen der Parserfunktionen andere technische Probleme geben, werden diese weiterhin über die Datenstruktur Qxml_DomExcData signalisiert.
pParser = QxmlXercesDOMParser_new(%addr(SaxExcData))
Warum der Xerces Parser allerdings seine Fehlermeldungen über die Datenstruktur SaxExcData kommuniziert, ist mir nicht bekannt. Möglicherweise hat man die Mühe gescheut, getrennte Kommunikationsbereiche für den SAX und den Xerces Parser zu schaffen.
Erstellen Eingabequelle
Für die Erstellung einer Eingabequelle steht die QxmlLocalFileInputSource_new-Funktion bereit. Diese Funktion erwartet folgende Parameter:
– Pfadangabe auf eine .xml-Datei im IFS
– Datenart der Pfadangabe
– Länge der Pfadangabe
Der Parameter „Länge der Pfadangabe“ kann mit 0 übergeben werden, sofern es sich bei „Pfadangabe“ um eine NULL-terminierte Zeichenkette handelt:
path = '/RPGgoesXML/RPGgoesXML.xml' + x'00') pInpSrc = QxmlLocalFileInputSource_new( %addr(path): Qxml_JOBCCSID: 0)
Starten Parser
Der Parser wird über einen Aufruf der QxmlXercesDOMParser_parse_InputSource-Funktion gestartet. Hierbei wird neben der Objektreferenz des Parser-Objekts die soeben erstelle Eingabequelle als Argument übergeben.
QxmlXercesDOMParser_parse_InputSource(pParser: pInpSrc)
Das Ergebnis des Parse-Vorgangs lässt sich über das Feld Qxml_errorType der Datenstruktur Qxml_SaxExcData kontrollieren. Wenn hier der Wert Qxml_NOERROR steht, ist bei der Erstellung des XML-Dokuments kein Fehler aufgetreten.
Löschen Eingabequelle
Die jetzt nicht mehr benötigte Eingabequelle wird über einen Aufruf der QxmlLocalFileInputSource_delete-Funktion gelöscht. Der Parser selbst wird noch nicht gelöscht, weil sonst auch das soeben erstellte XML-Dokument gelöscht werden würde.
QxmlLocalFileInputSource_delete(pInpSrc)
Ermitteln Referenz auf das XML-Dokument und Verarbeitung
Für die Verarbeitung des XML-Dokuments wird eine Objektreferenz auf das XML-Dokument benötigt. Für das Abrufen dieser Objektreferenz stellt das XML Toolkit die Qxml_XercesDOMParser_getDocument-Funktion bereit. Achtung, hier wird ein neues Objekt erstellt, welches später auch wieder gelöscht werden muss! Die weitere Verarbeitung des XML-Dokuments erfolgt über die entsprechenden Dokumentfunktionen.
pDocument = QxmlXercesDOMParser_getDocument(pParser)
Löschen XML-Dokument
Nach der Verarbeitung des XML-Dokuments muss das Dokument mit Hilfe der QxmlDOMDocument_delete-Funktion gelöscht werden.
QxmlDOMDocument_delete(pDocument)
Löschen Parser
Abschließend wird der Parser über einen Aufruf der QxmlXercesDOMParser_delete-Funktionen gelöscht.
QxmlXercesDOMParser_delete(pParser)
Beenden des XML Tookit
Wenn das XML Toolkit nicht mehr benötigt wird, müssen die allokierten Ressourcen mit Hilfe der QxmlTerm_rtnHandleCount-Funktion freigegeben werden. Die Funktion benötigt keine Parameter und liefert als Funktionswert die Anzahl der noch nicht gelöschten Objektreferenzen zurück.
numHandle = QxmlTerm_rtnHandleCount()
Wenn die Funktion einen Wert ungleich Null (0) zurückliefert, dann ist dies ein untrügliches Indiz dafür, dass vorher in der Verwaltung der Objektreferenzen ein Fehler unterlaufen ist. In diesem Fall hilft nur, den geschriebenen Programmcode genauestens zu überprüfen. Leider gibt es keine Möglichkeit festzustellen, um welche Objektreferenzen (Objektarten) es sich handelt. Dies würde die Fehlersuche deutlich vereinfachen.
Externe DTDs
Üblicherweise ist die DTD des XML-Dokuments nicht in der .xml-Datei enthalten, sondern in einer separaten .dtd-Datei abgelegt. In diesem Fall muss sich die .dtd-Datei im gleichen Verzeichnis wie die .xml-Datei befinden, damit der Parser die DTD-Informationen findet.
Diese Restriktion lässt sich umgehen, indem nach der Erstellung eines Parsers eine Callback-Funktion registriert wird, die immer dann aufgerufen wird, wenn der Pfad einer .dtd-Datei aufgelöst werden muss. Eine entsprechende Callback-Funktion kann wie folgt registriert werden:
– Erstellen Entity Resolver
– Registrieren Entity Resolver
– Registrieren Callback-Funktion
Ein Entity Resolver wird mit Hilfe der QxmlEntityResolver_new-Funktion erstellt. Die Funktion erwarte keine Argumente und liefert eine Referenz auf das Entity Resolver-Objekt zurück.
pEntityRslv = QxmlEntityResolver_new
Durch einen Aufruf der QxmlXercesDOMParser_setEntityResolver-Funktion wird die Callback-Funktion registriert:
QxmlXercesDOMParser_setEntityResolver( pParser: pEntityRslv)
Die Registrierung der eigentlichen Callback-Funktion erfolgt über die QxmlEntityResolver_setCallback-Funktion. Neben einer Referenz auf das Entity Resolver-Objekt und der Art der zu registrierenden Callback-Funktion wird als dritter Parameter die Adresse der Callback-Funktion übergeben.
QxmlEntityResolver_setCallback( pEntityRslv: Qxml_RESOLVEENTITY: g_pCB_entityResolver)
Das XML Toolkit übergibt der Callback-Funktion bei ihrem Aufruf den aktuellen Pfad der Public-ID und der System-ID. Die übergebenen Variablen sind vom Typ Pointer und adressieren im Unicode-Format vorliegende XML-Zeichenketten.
D cbEntityResolver... D PR * extproc('cbEntityResolver') D i_publicID * value D i_systemID * value
Mit Hilfe der QxmlXMLString_stringLen-Funktion lassen sich die Längen der Zeichenketten ermitteln und somit feststellen, ob Pfadangaben vorhanden sind. Die Pfadangaben können dann mit der QxmlTranscode-Funktion in RPG-Zeichenketten umgewandelt und anschließend ausgewertet werden. Abschließend wird über die QxmlLocalFileInputSource_new-Funktion eine Eingabequelle für die zu verwendende .dtd-Datei erstellt und als Funktionswert in der Callback-Funktion eingesetzt. Die Objektreferenz auf die Eingabequelle wird später automatisch vom Parser gelöscht.
Das Beispielprogramm
Das vorliegende Beispielprogramm vereinigt alle aufgezeigten Arbeitsschritte. Darüber hinaus zeigt es die Anwendung eines Entity Resolver zur Überschreibung der DTD-Angaben. Nachdem die Quellenteildatei PARSER.RPGLE auf die iSeries übertragen worden ist, kann das Programm über folgende Befehle erstellt werden:
CRTRPGMOD MODULE(QGPL/PARSER) SRCFILE(QGPL/QRPGXML) SRCMBR(*MODULE) TRUNCNBR(*NO) DBGVIEW(*LIST) CRTPGM PGM(QGPL/PARSER) MODULE(*PGM) BNDSRVPGM(QXMLTOOLS/QXML4PR500) ACTGRP(*NEW)
Anstatt der Bibliothek QGPL und der Quellendatei QRPGXML kann natürlich auch jede andere Bibliothek bzw. Quellendatei benutzt werden. Für die Erstellung des Programms muss lediglich sichergestellt sein, dass das Lizenzprogramm XML Toolkit for iSeries mit seinen Optionen *BASE, 3 und 4 installiert ist.
Die .xml-Dateien müssen in das Verzeichnis ‚/RPGgoesXML‘ kopiert werden, während die .dtd-Datei in das Verzeichnis ‚/RPGgoesXML/DTD‘ gehört. Nachdem das Programm erfolgreich erstellt worden ist, kann es mit dem Namen einer der beiden .xml-Dateien als Parameter aufgerufen werden. Der Unterscheid zwischen den beiden .xml-Dateien besteht darin, dass die Datei „RPGgoesXML1.XML“ die DTD-Angaben beinhaltet, während die Datei „RPGgoesXML.XML2“ auf eine externe DTD verweist. Bei den PC-Dateien ist darauf zu achten, dass die CCSID der angegebenen Kodierung (encoding=“ISO-8859-1“) entspricht. Bei einer Abfrage der Dateiattribute über den OS/400-Befehl WRKLNK ‚/RPGgoesXML/*‘ und anschließender Auswahl 8, sollte daher 819 als CCSID ausgewiesen werden. Wenn dies nicht der Fall sein sollte, kann die Datei umbenannt und anschließend unter Angabe der korrekten CCSID kopiert werden.
Wenn das Programm korrekt ausgeführt worden ist, sollte in der Nachrichtenzeile die Meldung „XML-Dokument ‚rpggoesxml‘ erstellt“ erscheinen.
Weitere Anforderungen
Sicherlich beschränkt sich der Umgang mit XML nicht nur darauf, .xml-Dateien zu parsen und XML-Dokumente zu erstellen. Darüber hinaus muss natürlich der Inhalt eines XML-Dokuments auch ausgewertet werden können. Für die Auswertung von XML-Dokumenten stehen im XML Toolkit Dokumentfunktionen zur Verfügung, die es erlauben, durch ein XML-Dokument zu navigieren und Attributwerte abzufragen.
Sehr wahrscheinlich besteht ebenso der Bedarf, XML-Dokumente von Hand zu erstellen und zu materialisieren, um sie dann im Datenaustausch zu verwenden. Für die Konstruktion eines XML-Dokuments stehen ebenfalls entsprechende Dokumentfunktionen zur Verfügung.
Die Materialisierung eines XML-Dokuments wird jedoch anscheinend von den offiziellen XML-Standards nicht unterstützt. Jedenfalls steht hierfür keine Funktion bereit. Hier ist solide Handarbeit angesagt. Glücklicherweise liefert die IBM mit dem XML Toolkit for iSeries eine Sammlung von Beispielprogrammen mit, unter denen sich auch das Programm DOMPRINT befindet, welches als Ausgangspunkt für eine Routine zur Materialisierung eines XML-Dokuments dienen kann. Das Programm DOMPRINT parst eine .xml-Datei und druckt den Inhalt der .xml-Datei nach STDOUT. Das Herz der Materialisierung wird durch die Funktion OUTPUTDOC gebildet, welche sich rekursiv durch das XML-Dokument arbeitet und die Druckausgaben über die QxmlXMLFormatter_streamoutXMLString-Funktion tätigt. Die Ausgabe nach STDOUT lässt sich leicht per Befehl nach QSYSPRT umleiten, so dass man eine Spooldatei erhält:
OVRDBF FILE(STDOUT) TOFILE(QSYSPRT) OVRSCOPE(*JOB)
Wer direkt ins IFS schreiben möchte, sollte einen Blick auf die QxmlFileFormatTarget_new-Funktion werfen. Mit Hilfe dieser Funktion lässt sich der Datenstrom des Formatters in eine IFS-Datei umlenken.
Den Autor Thomas Raddatzr erreichen Sie unter thomas.raddatz@tools400.de