Freitag, 20. März 2009

Ereignisbasierter XML-Parser in ABAP

Oft fällt die Entscheidung, XML für den Datenaustausch zwischen verschiedenen Systemen einzusetzen (und nicht etwa csv, binäre Daten oder andere Datenformate), wenn der Vorteil, die Daten in beliebigen Systemen weiterverarbeiten zu können und ihnen mittels eines Schemas eine feste Struktur zuzuweisen, den Nachteil des Datenoverheads aufwiegt. Mit anderen Worten: Es ist egal, wenn die Daten um "ein paar Bytes" angereichert werden, um ein wohlgeformtes und gültiges XML-Dokument darzustellen - man ist bereit, diesen Preis zu zahlen. Wer mit XML als Austauschformat arbeitet, zeigt meist eine hohe Bereitschaft, Redundanzen in den ausgetauschten Dokumenten zu tolerieren.

Wenn man erst einmal die Redundanzen in der XML-Grammatik hingenommen hat, wird man auch nachlässiger mit den Redundanzen in den eigentlichen Daten. Mit wachsender Dokumentengrösse bekommt man jedoch auch in den Programmen Schwierigkeiten, die die XML-Dokumente automatisch verarbeiten sollen. Ein DOM-Parser beispielsweise erzeugt stets ein Abbild, eine Kopie des kompletten XML-Dokuments in einem internen baumartigen Datenmodell. Dafür hat man ein bequem zu handhabendes API für den Zugriff auf jeden beliebigen Knoten in diesem Dokument. SAX-Parser dagegen arbeiten sich durch das Dokument durch, bis der Aufrufer die gewünschten Daten gefunden hat, ignorieren aber alle Informationen, für die er sich nicht registriert hat. Das ist natürlich viel speichersparender, hat aber den Preis, dass der Aufrufer selbst eine Parsingschleife programmieren muss, um die XML-Knoten zu analysieren, für deren Inhalt er sich interessiert.

Für ABAP-Entwickler enthält das Dokument iXML ABAP Jumpstart eine sehr schöne Einführung in die Philosophie des XML-Parsers. Die Klassen der iXML Familie stellen eine ABAP-Verschalung von in C++ programmierten Klassen für das Einlesen und Verarbeiten von XML-Dokumenten dar.

Ich will an einem Beispiel das ereignisbasierte Parsen mit dem iXML-Parser demonstrieren.

In der Praxis sendet man uns ins PI-SAP-System beispielsweise Dokumente wie den folgenden "Despatch Advise", ein Lieferavis. Im Original hat dieses Dokument 900 Kilobytes, es ist dabei noch von durchschnittlicher Grösse. Wenn man es sich ansieht, enthält es unglaublich viel heisse Luft - was aber bei den heutigen Datentransferraten niemanden besonders zu stören scheint. Offensichtlich hat man sich entschieden, zugunsten eines extrem primitiven Datenmodells hohe Redundanzen in Kauf zu nehmen: Die einfache Datenstruktur hat immerhin den Vorteil, dass auch ihre Verarbeitung einfach ist. So gibt es in diesem Dokument kein Kopfsegment, sondern alle Kopfdaten werden in jeder Position (in jeder <row>) erneut mitgesendet.

<?xml version="1.0" encoding="utf-8"?>
<DESADV>
<row>
<ABSENDER>7717013000007</ABSENDER>
<MELDUNGS_ID>19000028711877</MELDUNGS_ID>
<EMPFAENGER>7724400080191</EMPFAENGER>
<MELDUNGSTYP>DESADV</MELDUNGSTYP>
<MELDUNGSTYP_VERSION>2598</MELDUNGSTYP_VERSION>
<USER_INFO_1>S</USER_INFO_1>
<USER_INFO_2>%</USER_INFO_2>
<USER_REC_COUNT>518</USER_REC_COUNT>
<STATUS_CODE>8</STATUS_CODE>
<REV_DATUM>2009-03-20 14:24:15.0</REV_DATUM>
<RECORDNUMMER>1</RECORDNUMMER>
<DATENGRUPPE>XGM</DATENGRUPPE>
<FELD_1>251</FELD_1>
<FELD_2></FELD_2>
<FELD_3></FELD_3>
<FELD_4></FELD_4>
<FELD_5>9930036518</FELD_5>
<FELD_6>9</FELD_6>
<FELD_7></FELD_7>
<FELD_8></FELD_8>
<FELD_9></FELD_9>
<FELD_10></FELD_10>
</row>
<row>
<ABSENDER>7717013000007</ABSENDER>
<MELDUNGS_ID>19000028711877</MELDUNGS_ID>
<EMPFAENGER>7724400080191</EMPFAENGER>
<MELDUNGSTYP>DESADV</MELDUNGSTYP>
<MELDUNGSTYP_VERSION>2598</MELDUNGSTYP_VERSION>
<USER_INFO_1>S</USER_INFO_1>
<USER_INFO_2>%</USER_INFO_2>
<USER_REC_COUNT>518</USER_REC_COUNT>
<STATUS_CODE>8</STATUS_CODE>
<REV_DATUM>2009-03-20 14:24:15.0</REV_DATUM>
<RECORDNUMMER>2</RECORDNUMMER>
<DATENGRUPPE>ZTM</DATENGRUPPE>
<FELD_1>145</FELD_1>
<FELD_2>200903201422</FELD_2>
<FELD_3>204</FELD_3>
<FELD_4></FELD_4>
<FELD_5></FELD_5>
<FELD_6></FELD_6>
<FELD_7></FELD_7>
<FELD_8></FELD_8>
<FELD_9></FELD_9>
<FELD_10></FELD_10>
</row>
... und noch viele <row>...</row> Elemente!
</DESADV>


Nehmen wir einmal an, wir haben die Aufgabe, ein solches XML-Dokument in eine durch den Absender bestimmte Queue zu stellen. Wir müssten also das Dokument einlesen, um den Textinhalt des Elements <ABSENDER> zu ermitteln. Je nach dem Wert des Absenders würden wir dann das Dokument in einen jeweils anderen Ordner ablegen. Das Absenderfeld kommt in jeder Zeile vor – für dieses Beispiel gehen wir jedoch davon aus, dass es in jeder Zeile identischen Inhalt hat, also eigentlich ein Kopffeld ist.

Dies ist ein idealer Einsatz für den ereignisbasierten Parser: Nur um den Absender zu ermitteln, wäre es völlig überflüssig, das komplette Dokument als DOM in den Speicher zu pumpen. Viel eleganter ist es, mit einem SAX-Parser gewissermassen hineinzuschnuppern, nach dem ersten ABSENDER-Element Ausschau zu halten, dessen Textinhalt einzulesen und dann das Parsing zu beenden.

Die iXML Schnittstelle hat nun ihrerseits gewisse programmtechnische Redundanzen, ein "Rauschen": Es gibt Codeteile, die man für jeden Parsingvorgang immer wieder hinschreiben muss. Dazu gehören das Bilden der iXML-Instanz, des Stream-Builders und in diesem Fall überflüssigerweise auch das Erzeugen eines initialen DOM-Dokuments, obwohl wir SAX-Parsing verwenden: Die Schnittstelle verlangt es so, und wenn wir eine initiale Referenz übergeben, kommt es zum Kurzdump.
Merken wir uns diese Anweisungen für die Extraktion in einer wiederverwendbaren Klasse fürs SAX-Parsing vor.

In diesem Beispiel für den Parsingvorgang schreiben wir die Anweisungen explizit hin. Wir benötigen die folgenden Objekte, die wir global deklarieren:

data: go_xml type ref to if_ixml,
go_stream_factory type ref to if_ixml_stream_factory.


Um solche globalen Objekte in einem Report immer verfügbar zu haben, instanziieren wir sie zum Zeitpunkt load-of-program:

load-of-program.
perform on_load.
...
form on_load.
go_xml = cl_ixml=>create( ).
go_stream_factory = go_xml->create_stream_factory( ).
endform.

Beim Ausführen des Testreports registrieren wir uns für die beiden Zeitpunkte "Nach Einlesen eines öffnenden Tags" (if_ixml_event=>co_event_element_pre2) und "Nach Einlesen eines Textknotens" (if_ixml_event=>co_event_text_post).
start-of-selection.
perform start.
...
form start.

data: lo_parser type ref to if_ixml_parser,
lo_stream type ref to if_ixml_istream,
lo_doc type ref to if_ixml_document,
lv_absender type string,
lv_events type i.

* Beispieldokument beschaffen
perform get_example changing lo_stream.

* Eine Instanz eines XML-Doc wird zwar benötigt,
* aber hier gar nicht verwendet (SAX!)
lo_doc = go_xml->create_document( ).

* Parser wird pro Dokument beschafft
lo_parser = go_xml->create_parser(
document = lo_doc
istream = lo_stream
stream_factory = go_stream_factory ).

* --- Entscheidende Stelle:
* Hier wird die DOM-Generierung abgeschaltet!
lo_parser->set_dom_generating( space ).

* Ereignisse können mit '+' in ein Bitfeld zusammengesetzt werden
lv_events =
* Bei diesem Ereignis sind der Elementname und alle Attribute bekannt.
if_ixml_event=>co_event_element_pre2
* Bei diesem Ereignis wurde ein Textknoten eingelesen
+ if_ixml_event=>co_event_text_post.

* Registrieren
lo_parser->set_event_subscription( lv_events ).


* Der Parser liest nun das Dokument durch und meldet passende Knoten
* Es wird kein Abbild des XML im Speicher erzeugt! (SAX, nicht DOM)
perform parse_absender using lo_parser
changing lv_absender.

* Ergebnis, hier zur Demonstration ein simpler String
* Im Normalfall ist nun eine passende Datenstruktur
* oder interne Tabelle aufgebaut
if lv_absender is initial.
write: / 'Absender nicht gefunden'.
else.
write: / 'Absender:', lv_absender.
endif.

endform. "start

Schliesslich haben wir in der Routine parse_absender die eigentliche Parsingschleife implementiert. Hier spielt sich ein Hin und Her zwischen ABAP und dem in den Kernel integrierten iXML-Parser ab. Immer wenn der Parser auf ein Objekt trifft, für das wir uns registriert haben, unterbricht er den Lesevorgang und übergibt an ABAP. Das ABAP selbst enthält dann eine Art ausprogrammiertes Bild der erwarteten Dokumentstruktur. Indem man sich Elementnamen in lokalen Variablen merkt, kann man sich durch das Dokument hindurchhangeln, bis man beim gewünschten Element angekommen ist. Sobald man alle benötigten Daten beisammen hat, beendet man die Schleife:
form parse_absender using io_parser      type ref to if_ixml_parser
changing ev_absender type string.

data: lo_event type ref to if_ixml_event,
lv_type type i,
lv_current_node type string.

do.

lo_event = io_parser->parse_event( ).
if lo_event is initial.
exit.
endif.

lv_type = lo_event->get_type( ).
case lv_type.
when if_ixml_event=>co_event_element_pre2.
* Neues Element wurde entdeckt
lv_current_node = lo_event->get_name( ).
when if_ixml_event=>co_event_text_post.
if lv_current_node eq 'ABSENDER'.
* Absender? Dann Text extrahieren...
ev_absender = lo_event->get_value( ).
* ... und Parsingschleife beenden
exit.
endif.
endcase.

enddo.


endform. "parse_absender

Das obige Beispieldokument wird also nur bis zum dritten Element gelesen: Dann ist der Inhalt des Absenderfelds bekannt, und der Parsingvorgang kann beendet werden. Bei Dokumentgrössen im Megabytebereich - wie hier - bringt dies eine enorme Ressourcenersparnis im Vergleich zum DOM-Parsing. Da das Dokument maschinell erzeugt wurde, haben wir hier auf das validierende Parsen verzichtet und sind einfach davon ausgegangen, dass das Dokument wohlgeformt ist und in der gewünschten Struktur vorliegt.

Eine weitere Ersparnis könnte sich dadurch ergeben, dass man sich nicht für einen bestimmten Knotentyp, sondern für eine konkrete Knotenmenge registriert, die z.B. in einer (reduzierten) XPath-Syntax angegeben ist. In einer fiktiven Erweiterung des Parserobjekts könnte man dann z.B. schreiben:

lo_parser->subscribe( '/DESADV/row[1]/ABSENDER::text()' ).

Dadurch würde das häufige Hin- und Herspringen zwischen den ABAP- und C++-Teilen verhindert, und die Kontrolle könnte von C++ genau dann an ABAP zurückgegeben werden, wenn der gewünschte Knoten in der XML-Baumstruktur erreicht ist.

Um die volle XPath-Syntax verwenden zu können, müsste wahrscheinlich intern wieder ein DOM-Parsing gemacht werden, daher würde man, wenn man ein solches Feature implementiert, wohl nur eine reduzierte XPath-Syntax zulassen, die lediglich ein "Hindurchhangeln" durch das Dokument erfordert, wie im obigen Beispiel — denn das lässt sich auch mit dem SAX-Parsing erreichen. Es wäre zu analysieren, welche XPath-Konstrukte gewissermassen ohne Total-Überblick über das Dokument mit einem ereignisbasierten Parser erfüllt werden können.

21 Kommentare :

Anonym hat gesagt…

Hallo Rüdiger,

dein Beitrag hat mir sehr geholfen. Deine Erklärungen sind wirklich prima. Mir gefällt besonders, wie du aufgrund der Performance auf SAX setzt. Und genau an dieser Stelle hätte ich noch eine Frage. In deinem Beispielprogramm lässt du das Unterprogramm "get_example" offen. Ich habe für mich das Unterprogramm ausprogrammiert und erstelle den Inputstream wie folgt:

go_stream_factory->create_istream_string(‘...’).

In meinem konkreten Fall stehe ich vor dem Problem, eine etwa 750 MB große XML-Datei einzulesen. Ich möchte natürlich vermeiden, den gesamten Inhalt vorher in den RAM zu laden. Könntest du dir eine Möglichkeit vorstellen, iterativ (z.B. mit OPEN DATASET) zu arbeiten?

Danke,
Hendrik

Rüdiger Plantiko hat gesagt…

Hallo Hendrik,

das ist eine gute Frage! Leider ist das Lesen der Daten-"Chunks" in den von SAP ausgelieferten Streams verborgen und nicht Teil einer öffentlichen Schnittstelle.

Daher können wir nicht einfach eine eigene Implementierung von IF_IXML_ISTREAM programmieren, die die Chunks z.B. aus einer Datei liest, sondern sind auf die von SAP implementierten Möglichkeiten angewiesen.

Das Interface IF_IXML_STREAM_FACTORY gibt fünf mögliche Input-Streamtypen an: CSTRING, STRING, ITABLE, XSTRING und URI. Die ersten vier erfordern ganz offensichtlich, dass der Stream-Inhalt vollständig im Memory vorliegen muss. Bei URI ist es wahrscheinlich auch so, dass die Ressource vollständig beschafft wird, selbst bei "file://" URIs. Vielleicht fragst Du mal im SDN in einem geeigneten Forum?

Man spart mit dem in meinem Blog beschriebenen SAX-Parsing trotzdem eine Menge, denn der DOM-Parser würde ja den Eingabestream in ein vermutlich um ein Vielfaches grösseres Objekt im Speicher abbilden - das DOM-Modell.

Bei 750 MB für das Roh-XML wird es allerdings trotzdem kritisch. Wenn Dein Prozess etwas mehr macht als nur das XML-Dokument zu lesen und zu parsen, bekommst Du vermutlich Probleme mit dem Speicher. Wenn ich eine solche Aufgabe bekäme und ich sichergestellt hätte (OSS, SDN), dass es wirlkich keinen iterativ arbeitenden ISTREAM gibt, würde ich die Aufspaltung in "Chunks" selbst vornehmen, indem ich mich stückweise durch die Datei durcharbeite (mit OPEN DATASET und READ DATASET ... MAXIMUM LENGTH ... sollte das gehen) und als Gruppenwechselkriterium ein bestimmtes Element auf Stringebene suche (also mit FIND oder SEARCH), das z.B. den nächsten Record einleitet. Sobald ich den nächsten Gruppenwechsel erreicht habe, ist das vorhergehende XML-Fragment komplett. Das Fragment kann ich dann mit dem SAX-Parser wie beschrieben näher untersuchen, z.B. bestimmte Textknoten extrahieren o.ä.

Hoffe, das hilft Dir ein bisschen!
- Rüdiger

JayJay hat gesagt…
Dieser Kommentar wurde vom Autor entfernt.
JayJay hat gesagt…

Hallo Rüdiger,


ich bin gerade auf deine Antwort hier bzgl. Aufspalten einer XML Datei mit find und Gruppenwechsel gestoßen und hätte eine Frage dazu: Wenn ich weiß, dass mein XML Fragment mit row anfängt und /row aufhört, wie könnte ich dann beispielsweise so einen Satz finden und wegschreiben? Ich arbeite auch mit dem open und read dataset, bekomme aber keine variable Satzlänge hin :-(

Vielleicht kannst du mir helfen? Vielen Dank...
Judith

Rüdiger Plantiko hat gesagt…

Hallo Judith,

das einzige Problem, das sich beim Lesen mit fester Recordlänge stellt, ist, dass der gesuchte String "<row>" am Ende des gerade eingelesenen Records stehen kann und dann zum Teil in den folgenden Record umgebrochen wird. Du brauchst hierfür einen "1-record lookahead", d.h. Du arbeitest mit Übertrag: Du liest jeweils nicht den nächsten, sondern den übernächsten Record ein und fügst die beiden Teile zusammen (am Anfang musst Du natürlich zwei Records lesen, den nächsten und übernächsten; danach ist der nächste immer der Übertrag aus dem vorherigen Schleifendurchlauf).

Findest Du ein oder mehrere "<row>", fügst Du alle Zeichen bis exclusive dem Beginn des letzten in den Ergebnisstring, und alles ab diesem letzten "<row>" bis Ende ist der Übertrag für den nächsten Record. Findest Du kein "<row>", so schmeisst Du den ersten Record weg, und der zweite Record wird zum Übertrag des nächsten Schleifendurchlaufs.

Den SAX-Parser würde ich dann starten, sobald der Ergebnisstring eine ausreichende Grösse erreicht hat, z.B. 1 MB. Er durchsucht die bis dahin gefundenen <row>'s auf das Gewünschte. Wenn dann noch nicht alles Gewünschte beisammen ist, wird der gerade beschriebene Vorgang fortgesetzt - bis entweder das Gewünschte erreicht ist oder die Datei zu Ende ist.

Ich hoffe, die Idee kommt ungefähr rüber...

Gruss,
Rüdiger

flurischt hat gesagt…

Hallo Rüdiger

Ich benutze deinen Ansatz zum Parsen von XML Files der folgenden Struktur:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOKUMENT>
<VERTRAN>200</VERTRAN>
<GFNUM></GFNUM>
</DOKUMENT>

Als Events benutze ich
if_ixml_event=>co_event_element_pre2,
if_ixml_event=>co_event_text_post sowie
if_ixml_event=>co_event_cdata_section_post.

Mir ist jetzt aufgefallen, dass das Event co_event_text_post pro Knoten zweimal ausgelöst wird. Im obigen Fall erhalte ich beim Knoten VERTRAN einmal 200 und einmal ## von get_value( ) zurück.

Ist dir so etwas ähnliches auch schon aufgefallen? Mein Problem ist, dass ich nach dem ersten Event nicht abbrechen kann, da ich den kompletten Inhalt aller Knoten einlesen muss...

Gruss flurin

Rüdiger Plantiko hat gesagt…

Hallo Flurin,

Deine Annahme, das Text-Event würde innerhalb eines Elements zweimal ausgelöst, ist vermutlich falsch. Mit ziemlicher Sicherheit ist das '##' der Zeilenumbruch nach dem Abschluss des Elements (x0D0A = zwei nicht darstellbare Zeichen) und vor dem Beginn des nächsten Elements.

Da Du Dich nur für den Text innerhalb eines Elements interessierst, musst Du das Feld mit dem aktuellen Elementnamen setzen, wenn der Beginn eines Elements entdeckt wird und dieses Feld wieder zurücksetzen, wenn das Ende des Elements (das schliessende Tag)
entdeckt wird. Sowohl "öffnendes Tag entdeckt" (co_event_element_pre oder co_event_element_pre2) als auch "schliessendes Tag entdeckt" (co_event_element_post) sind Ereignisse, für die Du Dich registrieren kannst. Das Textereignis berücksichtigst Du dann nur, wenn der "aktuelle Elementname" der gesuchte ist.

Alternativ kannst Du aber auch einfach nach dem ersten Text-Ereignis nach Entdecken des Elementbeginns abbrechen, so wie ich es in diesem Beispiel gemacht habe.

Hoffe, das hilft. Die Doku der SAX-Parserereignisse zeigt Dir weiterführende Möglichkeiten, den SAX-Parser zu verwenden.

Gruss,
Rüdiger

flurischt hat gesagt…

Hallo Rüdiger

Genial! Du hast recht, es war genau der Zeilenumbruch und das fehlende behandeln des co_event_element_post Events.

Vielen Dank und Gruss
flurin

Enno ◄Tricktresor► hat gesagt…

Hallo Rüdiger!
Ich habe versucht per "Parse_Event" ein XML zweimal zu durchsuchen, das funktioniert aber anscheinend nicht, da nach dem ersten Durchlauf "lr_event = lr_parser->parse_event( )." initial ist.

Gibt es einen Befehl, um den Pointer wieder an den Anfang des Dokuments zu setzen?

Gruß Enno

Enno ◄Tricktresor► hat gesagt…

Habe grade das hier gefunden: Until now, there is no reset() method that would allow to reset an XML parser to its initial state. Therefore a new parser instance is required for each XML document to parse. :(

Rüdiger Plantiko hat gesagt…

Hallo Enno,

Gegenfrage: Wozu brauchst Du das?

Ich kann man mir gerade keinen Fall vorstellen, wo es nötig ist, ein und dasselbe XML-Dokument mehrfach durchzufräsen, nur um Informationen aus ihm zu extrahieren! Der Parser hält ja an jeder relevanten Stelle im Dokument, den Rest kannst Du doch in den Ereignisbehandlern hinbekommen.

Gruss,
Rüdiger

Enno ◄Tricktresor► hat gesagt…

Hallo Rüdiger!
Hintergrund ist der folgende: Ich habe ein XML-Dokument in der Tabelleninformationen und Tabelleninhalte stehen. Die Tabelleninformationen wollte ich auslesen, um dann dynamisch eine interne Tabelle mit der entsprechenden Anzahl Felder zu generieren (1. Durchlauf).
Im zweiten Durchlauf wollte ich dann die Felder füllen.
Die Tabelleninformationen stehen natürlich VOR den eigentlichen Daten. Von daher habe ich es nun auch so gelöst, dass ich als Ereignis so lange abfrage bis das erste mal kommt und zuvor halt die interne Tabelle generiere.
Das finde ich jedoch programmtechnisch nicht sehr schön, da hier in einer Routine zwei völlig unterschiedliche Programmteile zusammen geworfen werden.
Schöner fände ich es halt, wenn ich zuerst programmieren könnte:
GibElement"COLUMN"
GibAnzahlElemente

Um danach ereignisbasiert die Tabellendaten auszulesen.

Es hilft leider noch nicht einmal, das PARSER-Objekt neu zu generieren.

Gruß Enno

Rüdiger Plantiko hat gesagt…

Hallo Enno,

Dein Problem klingt interessant. Ich mag, wie Du, auch lieber elegante als unelegante Programme. :-)

Andererseits beherzige ich auch das amerikanische Sprichwort: "A poor craftsman who blames his tools"!

Hättest Du Lust, ein Beispiel-XML bei pastebin zu plazieren und den Link hier zu posten?

Gruss,
Rüdiger

Enno ◄Tricktresor► hat gesagt…

http://pastebin.com/XkgctkJA
Tabelle SFLIGHT mit ein paar Daten... :)
Mir kam es auf
TABLE|COLUMN
und dann ROW|CELL|DATA an.

Rüdiger Plantiko hat gesagt…

Hallo Enno,

also einfach eine Tabelle im Excel-XML-Format, OK.

Einzige Hürde scheint doch zu sein, dass die Zahl der Spalten nicht im vorhinein bekannt ist. Dann ist eben auch das Zielformat variabel, zum Beispiel eine Tabelle von STRINGTABs. Die Properties der Spalten kannst Du im selben Parse-Prozess einlesen.

In einem zweiten Schritt - der nicht mehr zum Thema "Parsen von XML nach ABAP gehört" !!! - wären dann die eingelesenen Daten gemäss den Microsoft-Spezifikationen für die Spaltentypen zu validieren. Zum Beispiel mit dem Baustein, der trotz seines hässlichen Namens RS_CONV_EX_2_IN_NO_DD die Grundlage der Dynpro-Feldkonvertierung String -> ABAP-Datentyp darstellt.

Hier mein Vorschlag für das simultane Parsen der Daten und der Typeigenschaften:

http://pastebin.com/AFkjvFq1

Gruss,
Rüdiger

Enno ◄Tricktresor► hat gesagt…

Danke für dein Coding! Das ist tatsächlich eine im wahrsten Sinne des Wortes "schöne" Lösung! :)

Ich wollte gerade schreiben, dass es einen Nachteil gibt:
Will man nämlich die gesammelten Daten in eine strukturierte Tabelle überführen, hätte man einen doppelten Speicherverbrauch.
Stimmt ja aber nicht: Ich kann ja, sobald eine Zeile abgearbeitet wurde, diese Einträge aus der Quelltabelle löschen.

Ich probiere noch mal, ob eine "nicht-eventbasierte" Verarbeitung mittels "parse" und "direktem Zugriff" (wie nennt man das dann??) probieren. Vielleicht sieht das noch eleganter aus... ;)

Danke!!

Gruß Enno

Rüdiger Plantiko hat gesagt…

Hallo Enno,

ich wusste nicht, dass Du Excel-Tabellen mit mehreren Millionen Datenzeilen bearbeiten musst - warum hat Microsoft nicht die 65'535er Grenze beibehalten! :-) [Wenn es weniger als Millionen sind, dürfte meine Lösung funktionieren]

Tabellenzeilen zu löschen, hilft wenig, da nur der belegte, aber nicht der reservierte Platz der Tabelle reduziert wird.

Wenn Du wirklich mit Millionen von Tabellenzeilen rechnest, musst Du blockweise arbeiten.

In meinem Beispielprogramm (immer noch http://pastebin.com/AFkjvFq1 ) wäre dann in Zeile 162 einzugreifen. Wenn die Tabelle CT_DATA mehr als N Zeilen enthält, soll ihr Inhalt durch Aufruf einer anderen Routine in das endgültige Ergebnis überführt werden, danach wird CT_DATA mit CLEAR gelöscht.

Dadurch verhinderst Du, dass das Zwischenergebnis sich unnötig aufbläht. Denn Du hast ja schon den String selbst im Speicher (das Quell-XML), und das Ziel wird auc gross. Dann wäre es günstig, wenigstens die Zwischentabelle klein zu halten (ein paar Tausend Zeilen dürften auf einer normalen Maschine sicher immer noch gehen... ).

Gruss,
Rüdiger

Enno ◄Tricktresor► hat gesagt…

ich wusste nicht, dass Du Excel-Tabellen mit mehreren Millionen Datenzeilen bearbeiten musst
ich auch nicht... ;)
Der Einwand war wohl eher theoretischer Natur...
Ich zeige dir zu gegebener Zeit, wozu ich das brauchte!
Gruß Enno

Enno ◄Tricktresor► hat gesagt…

SUBMIT ... EXPORTING ALV TO MEMORY
:)
Gruß
Enno

Rüdiger Plantiko hat gesagt…

Hallo Enno,

danke fürs Aufschreiben - ein interessanter Anwendungsfall!

Gruss,
Rüdiger

Unknown hat gesagt…

Hallo Rüdiger,

Ich kann mich meinen Vorredner nur anschließen, wirklich guter Post.

Eine Frage hätte ich dazu, die von dir beschriebene Optimierung

lo_parser->subscribe( '/DESADV/row[1]/ABSENDER::text()' ).

führ zu einem Compilerfehler da es die Methode nicht(mehr?) gibt. Kennst du hier eine Alternative?

Danke und Gruß

Christian