wann ruft der Parser 'expat' Funktionen auf?
Andreas-Lindig
- php
Hallo Forum,
Ich versuche gerade eine XML-Datei zu parsen, mit expat. Schön - geht auch, aber ich verstehe das Vorgehen dieses Parsers nicht, besonders wann er den Handler für Textdaten aufruft.
//XML-Datei
<?xml version="1.0" encoding="UTF-8"?>
<sect1>
<title>
Absatztitel
</title>
<para>
Absatztext
neue Zeile
</para>
</sect1>
//Handler für Daten ( CDATA ) setzen
xml_set_character_data_handler( $parser, "daten" );
//Funktion "daten()"
function daten( $parser, $data )
{
echo $data;
}
für einfache Beispiele funktioniert das soweit. Aber, wenn ich z.B. Zeilenumbrüche in der XML-Datei mit nl2br() umsetzen will:
function daten( $parser, $data )
{
echo nl2br($data);
}
stelle ich fest, daß diese Funktion nicht etwa einmal für "Absatztitel" und einmal für "Absatztext" aufgerufen wird, sondern andauernd. Z.B. auch zwischen "</title>" und "<para>" und bei Leerzeichen im Text usw.
Keine Ahnung, wie bringe ich das Teil dazu den Text innerhalb zweier zusammengehöriger Tags als _ein_ Ganzes aufzufassen?
Gruß, Andreas
Halihallo Andreas
//Handler für Daten ( CDATA ) setzen
Der wird auch bei PCDATA aufgerufen ;)
Und zwar, was du noch merken wirst, falls ein Entity geparsed werden
soll genau einmal für das Entity alleine.
z.B. wird 'hello&world' dreimal data() aufrufen.
stelle ich fest, daß diese Funktion nicht etwa einmal für "Absatztitel" und einmal für "Absatztext" aufgerufen wird, sondern andauernd. Z.B. auch zwischen "</title>" und "<para>" und bei Leerzeichen im Text usw.
Richtig.
http://www.xml.com/pub/a/1999/09/expat/index.html?page=3
<cite>
A single block of contiguous text free of markup may still result in a sequence of calls to this handler. In other words, if you're searching for a pattern in the text, it may be split across calls to this handler.
</cite>
Soweit ich es gesehen habe, wird für jeden Zeilenumbruch, für
jedes Entity, sowie nach dem Zeilenumbruch die Leerschläge bis zum
ersten nicht-Leerschlag der CharacterData-Handler aufgerufen.
Es ist eigentlich auch völlig egal, wann genau er aufgerufen wird.
Beim Programmieren muss man sich einfach bewusst sein, dass er
einfach "irgendwie" aufgerufen wird. Dass die Reihenfolge stimmt und
der Inhalt komplett ist, ist das einzige was man zu wissen braucht.
Das Zusammenhängen der einzelnen, separaten CharacterData-Strings,
nennt man normalize. Bei XML-DOM gibt es eine entsprechende Methode,
die dies dem Programmierer abnimmt. Bei Expat musst du eine
entsprechende Funktion selber schreiben.
Keine Ahnung, wie bringe ich das Teil dazu den Text innerhalb zweier zusammengehöriger Tags als _ein_ Ganzes aufzufassen?
Schreib dir eine eigene normalize-Funktion...
Ich kann dir hierbei einen Ansatz geben:
Wann immer der CharacterHandler aufgerufen wird, hängst du den Text
an eine (globale) Variable an. Falls ein anderer Handler (z.B. Start)
aufgerufen wird, rufst du einen eigenen Handler "RealCharacterData"
auf, dem du diese (globale) Variable übergibst (natürlich bevor der
andere Handler Aktionen zum Verarbeiten vornimmt).
So hast du sozusagen ein erweitertes Expat-Handler-Handling, wo
der (dein eigener) CharacterData-Handler auf normalisierten
Textnodes operiert.
Viele Grüsse
Philipp
Hallo Philipp,
vielen Dank erstmal, ich muß mir das gleich zuhause nochmal reintun ;-)
Aber eins kann ich nicht nachvollziehen:
...Wann immer der CharacterHandler aufgerufen wird, hängst du den Text an eine (globale) Variable an.
soweit sogut,
aaaber:
Falls ein anderer Handler (z.B. Start) aufgerufen wird, rufst du einen eigenen Handler "RealCharacterData" auf, dem du diese (globale) Variable übergibst (natürlich bevor der andere Handler Aktionen zum Verarbeiten vornimmt).
warum soll ich "start()" den Inhaltstext übergeben? start arbeitet bei mir doch richtig, also macht aus z.B. <titel> ein <h1>
[...Vorschau lesen...]
oder habe ich das jetzt falsch verstanden: "RealCharacterData()" soll den bisherigen Inhaltsstring - der ja jetzt komplett ist, weil eine andere Funktion aufgerufen wurde - so umformen, wie ich ihn haben will? Also ich rufe "RealCharacterData()" aus "start()" heraus als erstes auf?
Gruß, Andreas
Halihallo Andreas
oder habe ich das jetzt falsch verstanden: "RealCharacterData()" soll den bisherigen Inhaltsstring - der ja jetzt komplett ist, weil eine andere Funktion aufgerufen wurde - so umformen, wie ich ihn haben will? Also ich rufe "RealCharacterData()" aus "start()" heraus als erstes auf?
Ich glaube nicht ganz:
Wenn du den normalen Callback CharacterData() verwendest bekommst du
wie bereits festgestellt die Text-Stückchen happenweise zugesendet...
Daran wird sich auch nichts ändern, da du die Expat-Internals nicht
verändern kannst/willst.
Was ich nun vorschlage ist ein zweiter "externer" Callback zu
definieren, welcher nur dann aufgerufen wird, wenn ein Text-Stück
"komplett" also normalisiert ist. Wie kann dies umgesetzt werden?
Nun, wie ist ein ganzes Text-Stück definiert?
<definition type="versuch">
Es wird solange Text eingelesen, bis z.B. ein Element, Kommentar o.ä.
auftaucht. D.h. die vom CharacterData() Callback [Expat-Callback!]
gelieferten Texte werden solange zusammengehängt, bis ein Element
oder Kommentar (oder PI) auftaucht.
</definition>
Nun gut, das heisst für unseren Expat-CharacterData-Callback, dass
er die ankommenden Textstückchen solange in einer globalen Variable
"zusammenbastelt", bis dann einmal ein anderer Callback ausgelöst
wird.
Falls dann also ein anderer Callback ausgelöst wird
(Element,PI,Comment), ist der normalisierte Text, der sich in der
globalen Variablen angesammelt hat, fertig. Bevor jetzt aber
irgendwelche Aktionen für das Verarbeiten des Elementes/Kommentar
ausgeführt werden, muss dein Programm den normalisierten String
verarbeiten => ein "NormalizedCharacterData()" Callback muss
ausgelöst werden (dieser kommt jetzt aber nicht von Expat, sondern
von deinem Programm selber).
Dieser NormalizedCharacterData() Callback wird von jedem
Callback (ausser dem "Standard-Data-Callback") ausgelöst werden, denn
das ist ja unsere Definition von "normalisiertem Text", erst wenn
dein Programm den NormalizedCharacterData() verarbeitet hat, wird
der eigentliche Callback von Expat ausgeführt (Comment|Element etc).
Lange Rede kurzer Sinn. Ein Beispiel spricht 1000 Worte:
xml_set_character_data_handler( $parser, "CharacterData" );
xml_set_element_handler($xml_parser, "StartElement", "EndElement");
xml_set_processing_instruction_handler($xml_parser, "PIData");
...
function CharacterData($parser,$data) {
global $normalizedString;
// Normalisierungsprozess ist hier:
$normalizedString .= $data;
// Keine weitere Verarbeitung! - Der normalisierte String wird
// NormalizedCharacterData() verarbeitet!
}
function StartElement($parser,$name,$attributes) {
// Wurde vor dem Start dieses Elementes irgendwelche CDATA's
// empfangen? - So können diese (zusammengehängt in $normStr)
// dem NormalizedCharacterData()-Callback übergeben werden und
// dort so verarbeitet werden, als wäre es der normale
// Expat-Callback für 'Char'.
if (strlen($normalizedString)) {
NormalizedCharacterData($parser,$normalizedString);
$normalizedString = '';
}
// hier jetzt das "normale Verarbeiten von StartElement".
echo("found element $name\n");
}
function EndElement($parser,$name) {
if (strlen($normalizedString)) {
NormalizedCharacterData($parser,$normalizedString);
$normalizedString = '';
}
// hier jetzt das "normale Verarbeiten von EndElement".
echo("element end for $name\n");
}
function PIData($parser,$target,$data) {
if (strlen($normalizedString)) {
NormalizedCharacterData($parser,$normalizedString);
$normalizedString = '';
}
// hier jetzt das "normale Verarbeiten von PIData".
echo("ProcessingInstruction for $target with $data.\n");
}
function NormalizedCharacterData($parser,$data) {
// so, das ist jetzt unser "erweiterter CharacterData-Callback"
// hier kommen aber nur die wirklich normalisierten Strings hin!
// hier einfach das hinschreiben, was du sonst in den Expat-
// 'Char'-Handler geschrieben hättest.
echo("Endlich habe ich die TextNodes normalisiert: $data\n");
}
Das ist jetzt alles ungetestet. Es sollte aber so (ggf. mit kleinen
Anpassungen) funktionieren...
Ich hoffe, du weisst jetzt, was ich mit Worten vergeblich versucht
habe zu erklären (das ligt nicht an dir - Programmieren konnte ich
schon immer besser als Reden :-) - Nicht zuletzt, weil meine
Muttersprache Perl ist...).
Viele Grüsse
Philipp
Ich hoffe, du weisst jetzt, was ich mit Worten vergeblich versucht
habe zu erklären (das ligt nicht an dir - Programmieren konnte ich
schon immer besser als Reden :-) - Nicht zuletzt, weil meine
Muttersprache Perl ist...).
*g*...
war gut. Im Grunde habe ich Dich auch vorher schon so verstanden. Ich habe gerade was ausprobiert und es funktioniert auch! Ich habe aber die "NormalizedCharacterData()"-Funktion in den anderen Handlern immer aufgerufen und das untersuchen des Strings mache ich dann _in_ "NormalizedCharacterData()". Eins wundert mich aber noch: warum übergibst Du an "NormalizedCharacterData()" nochmal den Parser?
Gruß, Andreas
Halihallo Andreas
war gut. Im Grunde habe ich Dich auch vorher schon so verstanden. Ich habe gerade was ausprobiert und es funktioniert auch!
Nein! - Du warst schneller :-)
Ich hab's soeben auch erfolgreich umsetzen können. Zu meiner
Verteidigung: Es war purer selbstnutzen, ich wollte auch mal ein
XML-Dokument mit PHP Parsen ;-)
Ich habe aber die "NormalizedCharacterData()"-Funktion in den anderen Handlern immer aufgerufen und das untersuchen des Strings mache ich dann _in_ "NormalizedCharacterData()".
Genau! - Gut.
Eins wundert mich aber noch: warum übergibst Du an "NormalizedCharacterData()" nochmal den Parser?
Vielleicht brauchst du Parser-Informationen. Linie oder byteOffset...
Zudem ist es Expat-CharacterData-Callback "kompatibel".
Ich hatte noch ein paar Fehler in meinem Beispiel: In den anderen
Callbacks muss natürlich auch ein 'global $normalizedString' stehen.
Hm. Falls sich sonst grad noch wer für den Source interessiert:
(normalerweise gibts das von mir nicht!)
<source>
<?
$xml_source = '<?xml version="1.0" encoding="UTF-8"?>
<sect1>
<title>
Absatztitel
</title>
<para>
Absatztext
neue Zeile
</para>
</sect1>
';
$normalizedString = '';
$parser = xml_parser_create();
xml_set_character_data_handler( $parser, "CharacterData" );
xml_set_element_handler($parser, "StartElement", "EndElement");
xml_set_processing_instruction_handler($parser, "PIData");
if (!xml_parse($parser,$xml_source,1)) {
die( 'Fehler im XML-Parsing: ' .
xml_error_string(xml_get_error_code($parser)) .
' in Linie: ' .
xml_get_current_line_number($parser));
}
function CharacterData($parser,$data) {
global $normalizedString;
// Normalisierungsprozess ist hier:
$normalizedString .= $data;
// Keine weitere Verarbeitung! - Der normalisierte String wird
// NormalizedCharacterData() verarbeitet!
}
function StartElement($parser,$name,$attributes) {
// Wurde vor dem Start dieses Elementes irgendwelche CDATA's
// empfangen? - So können diese (zusammengehängt in $normStr)
// dem NormalizedCharacterData()-Callback übergeben werden und
// dort so verarbeitet werden, als wäre es der normale
// Expat-Callback für 'Char'.
global $normalizedString;
if (strlen($normalizedString)) {
NormalizedCharacterData($parser,$normalizedString);
$normalizedString = '';
}
// hier jetzt das "normale Verarbeiten von StartElement".
echo("found element $name\n");
}
function EndElement($parser,$name) {
global $normalizedString;
if (strlen($normalizedString)) {
NormalizedCharacterData($parser,$normalizedString);
$normalizedString = '';
}
// hier jetzt das "normale Verarbeiten von EndElement".
echo("element end for $name\n");
}
function PIData($parser,$target,$data) {
global $normalizedString;
if (strlen($normalizedString)) {
NormalizedCharacterData($parser,$normalizedString);
$normalizedString = '';
}
// hier jetzt das "normale Verarbeiten von PIData".
echo("ProcessingInstruction for $target with $data.\n");
}
function NormalizedCharacterData($parser,$data) {
// so, das ist jetzt unser "erweiterter CharacterData-Callback"
// hier kommen aber nur die wirklich normalisierten Strings hin!
// hier einfach das hinschreiben, was du sonst in den Expat-
// 'Char'-Handler geschrieben hättest.
// Hm. Dem Design zu Liebe einfach mal nur Strings ausgeben, wie auch
// wirklich was sinnvolles enthalten...
if (preg_match( '/[1]*$/', $data )) { return; }
echo("Endlich habe ich die TextNodes normalisiert: '$data'\n");
}
?>
</source>
Viele Grüsse
Philipp
\n ↩︎
Hm. Falls sich sonst grad noch wer für den Source interessiert:
(normalerweise gibts das von mir nicht!)
Du bist ja echt fix! wie wär's mit einem "Tips und Tricks"-Artikel? Einfach kopieren - überschrift: XML-Parser in PHP.
Gruß, Andreas
Halihallo Andreas
Hm. Falls sich sonst grad noch wer für den Source interessiert:
(normalerweise gibts das von mir nicht!)
Du bist ja echt fix!
Copy&Past mit Erfahrung, das ist alles ;-)
wie wär's mit einem "Tips und Tricks"-Artikel? Einfach kopieren - überschrift: XML-Parser in PHP.
Hm... Steht IMHO alles in der PHP-Doku. Ich meine: Ich habe mir jetzt
genau das zweite Mal die PHP-Expat-Doku durchgelesen und habe bereits
den "Parser" geschrieben. Natürlich habe ich bereits Erfahrungen von
Perl's XML::Parser::Expat, aber ich weiss ehrlichgesagt nicht,
welchen Mehrwert bzgl. der Doku ich in einem Tipps&Tricks-Artikel
beitragen könnte.
Das mit dem Normalisieren wäre ein mögliches Thema für einen
Tipps&Tricks-Artikel, denn das steht IMHO nicht in der PHP-Doku und
hat auch ein konkreteres (nicht so allgemeines) Anwendungsgebiet.
Eigentlich wollte ich schon immer mal einen Artikel, sei es
Tipps&Tricks, oder Feature-Artikel schreiben, aber irgendwie fehlt
mir für das immer die Zeit (komischerweise im Forum nicht :-)). Ich
habe sogar schon einmal früher was angefangen, aber diese Artikel
brauchen immer viel, viel mehr Zeit, als man denkt.
Wie ging das noch gleich bei EAV?
"Ja, morgen, ja, morgen, da fängt ein neues Leben an. Und wenn nicht
morgen, dann übermorgen, oder vielleicht auch irgendwann..."
... irgendwann werde auch ich mal einen Feature-Artikel
schreiben... ;)
Viele Grüsse
Philipp