XML parsen mit expat

Kein Durchblick im Quellcode von Ja2? Hier werden sie geholfen.

Moderator: Flashy

Antworten
Realist
Alpha-Squad
Beiträge: 1573
Registriert: 24 Apr 2003, 11:00
Wohnort: Düsseldorf

XML parsen mit expat

Beitrag von Realist » 10 Sep 2005, 18:09

Moin,

hat irgendwer ne Ahnung, wie das XML parsen mittels expat, wie sie es im BP gemacht haben, funktioniert?

Ich werde weder aus deren Code schlau noch aus der expat-online-Dokumentation.

Oder weiß wer einen XML-Parser für C, der einfach handzuhaben ist? Ich versuche mich derzeit an LIBXML, aber auch das will nicht so recht.

Sergeant_Kolja
Bei Tony Rumsteher
Beiträge: 44
Registriert: 20 Mai 2003, 21:38

Beitrag von Sergeant_Kolja » 05 Mai 2007, 10:48

Realist hat geschrieben: hat irgendwer ne Ahnung, wie das XML parsen mittels expat, wie sie es im BP gemacht haben, funktioniert? Ich werde weder aus deren Code schlau noch aus der expat-online-Dokumentation.
Ja, siehe unten
Realist hat geschrieben:Oder weiß wer einen XML-Parser für C, der einfach handzuhaben ist? Ich versuche mich derzeit an LIBXML, aber auch das will nicht so recht.
Kein Wunder, die beiden sind auch arg verwand miteinander ...


(German Description follows: )
Schau Dir doch mal in .\Tactical\XML_ComboMergeInfo.cpp die Funktion
ReadInAttachmentComboMergeStats() an.
Das ist die einfachste, kleinste. Im groben Prinzip funktioniert das so:

Code: Alles auswählen

/*define Your own struct for the data from XML file */
struct {...} typedef attachmentcombomergeParseData;
...
/*create our Data object */
attachmentcombomergeParseData Data;
/*create a parser object */
XML_Parser parser = XML_ParserCreate(NULL);

erzeugt erstmal ein 'Objekt' parser. Dieses Objekt wird in den folgenden Funktionen überall als 1. Argument, quasi als Handle angegeben.

Dann wird ein Speicherblock in der Größe der XML-Datei angelegt (kann sein, daß man auch ohne RAM direkt lesen kann - egal, das findest Du eventuell später selbst heraus).

Code: Alles auswählen

XML_SetElementHandler( parser, 
     attachmentcombomergeStartElementHandle, 
     attachmentcombomergeEndElementHandle);
XML_SetCharacterDataHandler( parser, 
     attachmentcombomergeCharacterDataHandle );
'verbinden' nun das Parserobjekt mit Deinen 3 Behandlungsfunktionen:
  • attachmentcombomergeStartElementHandle()
  • attachmentcombomergeEndElementHandle()
  • attachmentcombomergeCharacterDataHandle()
Die werden später für jedes XML-Datenelement, das der Parser gefunden hat, aufgerufen. Nun mußt Du wissen, daß Data nur eine Hilfsstruktur ist. Die richtigen Daten in unserem Beispiel liegen in AttachmentComboMerge, die wederum irgendwo als ComboMergeInfoStruct AttachmentComboMerge[MAXITEMS+1]; deklariert ist. Wir müssen also 'Data' mitteilen, wo 'AttachmentComboMerge' liegt und wie groß es ist. Und natürlich müssen wir dem Parser mitteilen, daß wir in unseren 3 o.g. Funktionen gerne eine Referenz / Handle auf unser 'Data' haben wollen:

Code: Alles auswählen

/* Init Your own structure to ZERO */
memset( &Data, 0x00, sizeof(pData) );
/* Init some of the Members to useful start values */
Data.curArray = AttachmentComboMerge;
Data.maxArraySize = MAXITEMS; 
/* set parser to tell us the address of 'Data' as 1st arg on each call*/
XML_SetUserData(parser, &Data);
Jedesmal, wenn der Parser nun eine der 3 Funktionen ruft, bekommen wir einen Zeiger auf 'Data' als erstes Argument (hier heißt er 'void*userData'). Dieser Zeiger ist vom typ void*, weil eXpat ja nicht jeden Typ kennen kann. Also müssen unsere 3 Grazien sich den Zeiger als erstes zurück-typecasten:

Code: Alles auswählen

attachmentcombomergeParseData * pData = 
(attachmentcombomergeParseData*) userData;
'pData->' zeigt nun auf alle Elemente in 'Data'. Und 'pData->curArray.' greift auf alle Elemente aus 'AttachmentComboMerge', der eigentlichen, echten Datentabelle zu. Die ist übrigens ein Array von:

Code: Alles auswählen

typedef struct
{
	UINT16	usItem;
	UINT16	usAttachment[2];
	UINT16	usResult;
	UINT32  uiIndex;
} ComboMergeInfoStruct;
...
ComboMergeInfoStruct AttachmentComboMerge[MAXITEMS+1];
5002 Elementen.

Was kommt nun noch? Nun ja, das Parsen des gesamten Speicherblockes (der ja eine 1:1 Kopie der XML-Datei enthält). Und wenn der Parser fertig ist, das Freigeben des Parser-Objektes.
Und letztlich das Freigeben des Speicherblocks
[/CODE]
XML_Parse(parser, lpcBuffer, uiFSize, TRUE);
XML_ParserFree(parser);
[/CODE]

ja ... aber ... wo wird denn nun gelesen? :confused:
Das ist ganz einfach: in den 3 Callback-Funktionen, die oben schon mehrfach erwähnt wurden! Die XxxxStartElement() Funktion wird immer gerufen, wenn die XML-Datei ein Element öffnet. Das ist nicht nur eine einfache Variable wie

Code: Alles auswählen

<uiIndex>0</uiIndex>
sondern auch jede darüberliegende Ebene:

Code: Alles auswählen

<ATTACHMENTCOMBOMERGELIST>
	<ATTACHMENTCOMBOMERGE>
		<uiIndex>0</uiIndex>
Jede 'öffnende' Ebene ruft die XxxxStartElement() Funktion. bei dieser Datei also 3x, bevor wir an das Eingemachte kommen. In der vorliegenden Implementation von XxxxStartElement() prüfen wir hier einfach nur, ob wir in der Ebene der Variablen (uiIndex & Co.) oder in einer der Ebene darüber sind. Oder ob wir überhaupt im korrekten Abschnitt (also unterhalb von <ATTACHMENTCOMBOMERGELIST>) sind. Man könnte hier auch schon die Array-Indizees weiterschalten - TIMTOWDI...

In der XxxxCharacterDataHandle() Funktion wird hier - entgegen dem Namen - recht wenig gemacht, nämlich einfach nur der Inhalt aller aktuellen Datenelemente an pData->szCharData angehängt. Auf der ersten Daten-Ebene angekommen, müßte in pData->szCharData also "0" stehen, weil es nur ein einziges Datenelement 'uiIndex' mit Wert '0' gibt: "<uiIndex>0</uiIndex>". Das 'richtige' Auswerten der Daten kommt erst, wenn "</uiIndex>" gefunden wurde, und zwar in

XxxxEndElement(). Das ist durchaus schlau, denn erst jetzt wissen wir, daß der Parser das letzte Zeichen des Dateninhaltes gefunden hatte. in unseren Beispielen ist das zwar immer nur einen einzelne Zahl die wir schon in XxxxCharacterDataHandle() hätten auswerten können, aber das ist in XML durchaus nicht immer der Fall. in JA2.1.13 sind die uiIndex-Felder übrigens sehr sensibel, da alle XML-Lesefunktionen Ihre Indizees für die internen C- Arrays direkt aus diesen XML-Variablen lesen. Und fast alle der externalisierten Arrays sind 5000 Zeichen groß.

Ein letzter Hinweis:
unsere XML-Dateien verwenden UTF-8. UTF-8 erlaubt optional einen BOM (Byte Order Marker), d.h. 3 Bytes am Anfang der Datei, die in Hex "EF BB BF" lauten. Spätestens beim Einsatz von deutschen Texten mit äÄöÖüÜß spinnen einige Tools (u.a. Beyound Compare), wenn die Datei mal mit, mal ohne BOM abgespeichert wurde. Tip: BOM immer benutzen.
:erdbeerteechug:

Antworten