Thomas: Umlaute bei SAX

Hallo.

Wenn ich ein XML-File mit Umlauten parse, reißt SAX Tags auseinander:

<titel>Entität</titel>

Das ergibt Entit und ät. Das XML-File hat als Encoding UTF-8 (ist auch als UTF-8 gespeichert), und SAX wird mit xml_parser_create('UTF-8'); eingerichtet.

Wer weiß, woran das liegt?

  1. Moin!

    Wenn ich ein XML-File mit Umlauten parse, reißt SAX Tags auseinander:

    <titel>Entität</titel>

    Das ergibt Entit und ät.

    Wo?

    Das XML-File hat als Encoding UTF-8 (ist auch als UTF-8 gespeichert), und SAX wird mit xml_parser_create('UTF-8'); eingerichtet.

    Wer weiß, woran das liegt?

    Mehr Code! Mehr Infos! Dann gibts auch mehr Hilfe.

    - Sven Rautenberg

    --
    "Love your nation - respect the others."
    1. Hi.

      Wo?

      $titel = array();

      $xml = xml_parser_create('UTF-8');

      xml_parser_set_option ($xml, XML_OPTION_CASE_FOLDING, false);
      xml_set_element_handler($xml, 'start_handler', 'end_handler');
      xml_set_character_data_handler($xml, 'character_handler');

      function start_handler($xml, $tag, $attributes)
      {
      $GLOBALS['tag'] = $tag;
      }

      function character_handler($xml, $data)
      {
      if ($GLOBALS['tag'] != 'titel') return;
      $GLOBALS['titel'][] = $data;
      }

      Mehr Code! Mehr Infos! Dann gibts auch mehr Hilfe.

      Reicht das als Kurzbeschreibung?

      1. Moin!

        function character_handler($xml, $data)
        {
        if ($GLOBALS['tag'] != 'titel') return;
        $GLOBALS['titel'][] = $data;

        An dieser Stelle legst du ein potentiell mehrelementiges Array an, welches Teile des Inhalts des titel-Elements enthält. Nämlich für jedes geparste Bruchstück, was SAX ausspuckt, eines.

        Das entspricht vollkommen der Funktionsweise von SAX.

        }

        Abgesehen davon halte ich deine Vorgehensweise, über $GLOBALS auf globale Variablen zuzugreifen, für extrem böse!

        Außerdem: Dein Code enthält nirgendwo den Ausgabeteil. In dem wird vermutlich der Grund für den zerhauenen Umlaut drinstehen.

        - Sven Rautenberg

        --
        "Love your nation - respect the others."
        1. Hi.

          Moin!

          function character_handler($xml, $data)
          {
          if ($GLOBALS['tag'] != 'titel') return;
          $GLOBALS['titel'][] = $data;

          An dieser Stelle legst du ein potentiell mehrelementiges Array an, welches Teile des Inhalts des titel-Elements enthält. Nämlich für jedes geparste Bruchstück, was SAX ausspuckt, eines.

          Das entspricht vollkommen der Funktionsweise von SAX.

          »»

          Ja, so soll es auch sein. Jeder Textknoten von den titel-Tags soll einen eigenen Eintrag in dem Array $titel haben. Aber es wird bei jedem Umlaut eine neues Ereignis für character_handler ausgelöst, was keinen Sinn macht. In Deinen Worten drei statt zwei Bruchstücken:

          <titel>Entität</titel>
          <titel>Tabelle</titel>

          soll logischerweise zwei Ereignisse auslösen, es sind aber drei. Dabei wird das Array natürlich zu groß, statt zwei Einträgen drei.

          }

          Abgesehen davon halte ich deine Vorgehensweise, über $GLOBALS auf globale Variablen zuzugreifen, für extrem böse!

          Klar. Es geht hier aber nur um die Funktion von SAX.

          Außerdem: Dein Code enthält nirgendwo den Ausgabeteil. In dem wird vermutlich der Grund für den zerhauenen Umlaut drinstehen.

          Den habe ich, weil er nichts zur Sache tut, weggelassen. Aber daran liegt es garantiert nicht:

          foreach($titel as $titel)
            echo $titel;

          1. »»

            foreach($titel as $titel)
              echo $titel;

            Das ist natürlich Quatsch:

            foreach ($titel as $eintrag)
             echo $eintrag;

          2. Außerdem: Dein Code enthält nirgendwo den Ausgabeteil. In dem wird vermutlich der Grund für den zerhauenen Umlaut drinstehen.
            Den habe ich, weil er nichts zur Sache tut, weggelassen. Aber daran liegt es garantiert nicht:
            foreach($titel as $titel)
              echo $titel;

            In welcher Zeichenkodierung erfolgt die Ausgabe?

            Siechfred

            --
            Hinter den Kulissen passiert viel mehr, als man denkt, aber meistens nicht das, was man denkt.
            1. Hi.

              In welcher Zeichenkodierung erfolgt die Ausgabe?

              Siechfred

              Erst mal in noch gar keiner. Es geht erst mal nur um die zuvielen Einträge im Array $titel.

          3. Moin!

            function character_handler($xml, $data)
            {
            if ($GLOBALS['tag'] != 'titel') return;
            $GLOBALS['titel'][] = $data;

            An dieser Stelle legst du ein potentiell mehrelementiges Array an, welches Teile des Inhalts des titel-Elements enthält. Nämlich für jedes geparste Bruchstück, was SAX ausspuckt, eines.

            Das entspricht vollkommen der Funktionsweise von SAX.
            »»

            Ja, so soll es auch sein. Jeder Textknoten von den titel-Tags soll einen eigenen Eintrag in dem Array $titel haben. Aber es wird bei jedem Umlaut eine neues Ereignis für character_handler ausgelöst, was keinen Sinn macht. In Deinen Worten drei statt zwei Bruchstücken:

            <titel>Entität</titel>
            <titel>Tabelle</titel>

            soll logischerweise zwei Ereignisse auslösen, es sind aber drei. Dabei wird das Array natürlich zu groß, statt zwei Einträgen drei.

            Deine Beobachtung entspricht aber vollkommen der dokumentierten Funktionsweise von SAX. Es handelt sich hierbei um einen eventgesteuerten XML-Parser, d.h. du fütterst in ihn ein Fragment von XML hinein, und er parst das Stück und ruft intern passend zu den gefundenen Teilen die entsprechenden Funktionen auf, welche dann die gewünschten Maßnahmen ergreifen, um das XML-Fragment in geeigneten anderen Datenstrukturen zu speichern.

            Dein Irrtum liegt darin, dass du glaubst, für den Textblock "Entität" würde dein Eventhandler für "character data" garantiert nur ein einziges Mal aufgerufen. Diese Annahme ist falsch. SAX könnte durchaus deinen Eventhandler für jeden Buchstaben einzeln aufrufen! Das muß dein Code berücksichtigen, er muß sich also merken, ob das Elementende "</titel>" schon gefunden wurde - solange das nicht der Fall ist, muß deine Funktion für Character Data alle gefundenen Zeichen sammeln und in einen String speichern.

            Diese Vorgehensweise ist übrigens in der PHP-Doku auch beschrieben.

            Außerdem: Dein Code enthält nirgendwo den Ausgabeteil. In dem wird vermutlich der Grund für den zerhauenen Umlaut drinstehen.

            Den habe ich, weil er nichts zur Sache tut, weggelassen. Aber daran liegt es garantiert nicht:

            foreach($titel as $titel)
              echo $titel;

            Ohne Angaben zum Encoding wird der Browser "irgendwas" benutzten - in deinem Fall wohl ISO-8859-1.

            Das hat in der Tat nichts mit der Zweiteiligkeit deines Parsergebnisses zu tun, aber mit der falschen Ausgabe des Umlauts schon.

            - Sven Rautenberg

            --
            "Love your nation - respect the others."
            1. Hi.

              Deine Beobachtung entspricht aber vollkommen der dokumentierten Funktionsweise von SAX. Es handelt sich hierbei um einen eventgesteuerten XML-Parser, d.h. du fütterst in ihn ein Fragment von XML hinein, und er parst das Stück und ruft intern passend zu den gefundenen Teilen die entsprechenden Funktionen auf, welche dann die gewünschten Maßnahmen ergreifen, um das XML-Fragment in geeigneten anderen Datenstrukturen zu speichern.

              Dein Irrtum liegt darin, dass du glaubst, für den Textblock "Entität" würde dein Eventhandler für "character data" garantiert nur ein einziges Mal aufgerufen. Diese Annahme ist falsch. SAX könnte durchaus deinen Eventhandler für jeden Buchstaben einzeln aufrufen!

              Was gibt das für einen Sinn? Wenn keine Umlaute vorkommen, erzeugt ein Textknoten doch auch nur ein Ereignis.

              »»Das muß dein Code berücksichtigen, er muß sich also merken, ob das Elementende "</titel>" schon gefunden wurde - solange das nicht der Fall ist, muß deine Funktion für Character Data alle gefundenen Zeichen sammeln und in einen String speichern.
              »»

              Wenn es nicht anders geht, dann halt so.

              Diese Vorgehensweise ist übrigens in der PHP-Doku auch beschrieben.

              Link?

              Ich habe nur das gefunden:

              xml_set_character_data_handler()

              Character data is roughly all the non-markup contents of XML documents, including whitespace between tags. Note that the XML parser does not add or remove any whitespace, it is up to the application (you) to decide whether whitespace is significant.

              Ohne Angaben zum Encoding wird der Browser "irgendwas" benutzten - in deinem Fall wohl ISO-8859-1.

              Das hat in der Tat nichts mit der Zweiteiligkeit deines Parsergebnisses zu tun, aber mit der falschen Ausgabe des Umlauts schon.

              Mag sein. Aber der Umlaut wird schon falsch im Array gespeichert. Das habe ich mit strlen() geprüft, was für den Umlaut zwei Zeichen statt eines ergibt. Oder wird ein UTF-8-Umlaut mit zwei Bytes gespeichert?

              1. Mag sein. Aber der Umlaut wird schon falsch im Array gespeichert. Das habe ich mit strlen() geprüft, was für den Umlaut zwei Zeichen statt eines ergibt. Oder wird ein UTF-8-Umlaut mit zwei Bytes gespeichert?

                Durch Einfügen von header('Content-Type: text/html; charset=utf-8'); wird immerhin der Umlaut richtig ausgegeben, hast in dem Punkt recht. Bleibt nur die Zerstückelung.

              2. Moin!

                Dein Irrtum liegt darin, dass du glaubst, für den Textblock "Entität" würde dein Eventhandler für "character data" garantiert nur ein einziges Mal aufgerufen. Diese Annahme ist falsch. SAX könnte durchaus deinen Eventhandler für jeden Buchstaben einzeln aufrufen!

                Was gibt das für einen Sinn? Wenn keine Umlaute vorkommen, erzeugt ein Textknoten doch auch nur ein Ereignis.

                Ob das Vorgehen von SAX dir sinnvoll erscheint oder nicht, hat ja nichts damit zu tun, ob es den Entwicklern sinnvoll erschien. :) Mir erscheint es jedenfalls sehr sinnvoll, wenn man sich folgendes überlegt: SAX ist ein nichtspeichernder Parser, d.h. es wird kein XML-Dokumentenbaum aufgebaut, der im Speicher gehalten werden muß, sondern es werden alle Datenbruchstücke, die in SAX hineingegeben werden, direkt durch eventgesteuerte Aktionen sofort wieder rausgegeben. Denn es ist ja durchaus erlaubt und möglich, eine riesige XML-Datei in mundgerechte Stückchen á 1024 Byte in SAX einzuspeisen - bei solch einer Vorgehensweise ist nie garantiert, dass ein Textknoten immer vollständig übergeben wird, d.h. er kann auch in zwei Arbeitsgängen übergeben werden. SAX merkt sich nur, in welchem Zustand es nach dem Aufruf war, d.h. welche Tags geöffnet waren, und ob der Datenstrom ggf. mitten in einem Tag fortgesetzt wird, usw.

                Das bedeutet aber, dass der auswertende Code eben damit rechnen muß, dass ein Textknoten in mehreren Schüben an den entsprechenden Eventhandler übergeben wird. Und offenbar passiert genau das auch ohne Unterbrechung des Datenstromes an den Stellen, wo sowas wie "Sonderzeichen" vorkommen.

                Diese Vorgehensweise ist übrigens in der PHP-Doku auch beschrieben.

                Link?

                Ich habe nur das gefunden:

                xml_set_character_data_handler()

                Genau dort. :) http://de.php.net/manual/en/function.xml-set-character-data-handler.php

                Character data is roughly all the non-markup contents of XML documents, including whitespace between tags. Note that the XML parser does not add or remove any whitespace, it is up to the application (you) to decide whether whitespace is significant.

                Character data handler is called for every piece of a text in the XML document. It can be called multiple times inside each fragment (e.g. for non-ASCII strings).

                "multiple times inside each fragment"...

                Ohne Angaben zum Encoding wird der Browser "irgendwas" benutzten - in deinem Fall wohl ISO-8859-1.

                Das hat in der Tat nichts mit der Zweiteiligkeit deines Parsergebnisses zu tun, aber mit der falschen Ausgabe des Umlauts schon.

                Mag sein. Aber der Umlaut wird schon falsch im Array gespeichert. Das habe ich mit strlen() geprüft, was für den Umlaut zwei Zeichen statt eines ergibt. Oder wird ein UTF-8-Umlaut mit zwei Bytes gespeichert?

                Ja natürlich. UTF-8 verwendet zwischen einem und vier Bytes pro Zeichen. Da PHP das Unicode in den normalen Stringfunktionen nicht "versteht", sondern nur byteorientiert arbeitet, gibt strlen() halt einen Wert zurück, den du nicht "erwartest", der aber hinsichtlich des Speicherbedarfs korrekt ist.

                Die korrekte Stringlänge in ZEICHEN erhälst du, wenn du die Multibyte-Stringfunktionen verwendest. Die analoge Funktion zur herkömmlichen PHP-Stringfunktion erhälst du grundsätzlich, indem du "mb_" voranstellst, und damit dann in der Doku suchst. Für strlen z.B.: http://de.php.net/manual/en/function.mb-strlen.php.

                - Sven Rautenberg

                --
                "Love your nation - respect the others."
                1. Hi.

                  Was gibt das für einen Sinn? Wenn keine Umlaute vorkommen, erzeugt ein Textknoten doch auch nur ein Ereignis.

                  Ob das Vorgehen von SAX dir sinnvoll erscheint oder nicht, hat ja nichts damit zu tun, ob es den Entwicklern sinnvoll erschien. :)

                  Nein, natürlich nicht. Ich befasse mich noch nicht lange mit SAX. War vielleicht eine nicht richtig durchdachte Frage :)

                  Mir erscheint es jedenfalls sehr sinnvoll, wenn man sich folgendes überlegt: SAX ist ein nichtspeichernder Parser, d.h. es wird kein XML-Dokumentenbaum aufgebaut, der im Speicher gehalten werden muß, sondern es werden alle Datenbruchstücke, die in SAX hineingegeben werden, direkt durch eventgesteuerte Aktionen sofort wieder rausgegeben.

                  Das habe ich bereits als Vorteil gegüber DomDocument gekannt. So gesehen ist natürlich auch eine Zerstückelung eines Textknotens sinnvoll, wenn der Speicherbedarf niedrig bleiben soll, der Textknoten aber sehr groß ist. Das sehe ich ein.

                  Denn es ist ja durchaus erlaubt und möglich, eine riesige XML-Datei in mundgerechte Stückchen á 1024 Byte in SAX einzuspeisen - bei solch einer Vorgehensweise ist nie garantiert, dass ein Textknoten immer vollständig übergeben wird, d.h. er kann auch in zwei Arbeitsgängen übergeben werden. SAX merkt sich nur, in welchem Zustand es nach dem Aufruf war, d.h. welche Tags geöffnet waren, und ob der Datenstrom ggf. mitten in einem Tag fortgesetzt wird, usw.

                  »»

                  Ist das (1024 Byte) die Obergrenze, was maximal in einem Event als Textmenge zurückgegeben wird, oder hast Du die Zahl einfach mal in den Raum geworfen?

                  Das bedeutet aber, dass der auswertende Code eben damit rechnen muß, dass ein Textknoten in mehreren Schüben an den entsprechenden Eventhandler übergeben wird. Und offenbar passiert genau das auch ohne Unterbrechung des Datenstromes an den Stellen, wo sowas wie "Sonderzeichen" vorkommen.

                  »»

                  Ich habe es jetzt so gemacht, dass ich den Textknoten zunächst sammel und beim Schließen des Tags im Array speichere. Funzt perfekt. Das war eine große Hilfe von Dir.

                  Aber den Sinn einer Zerstückelung bei Sonderzeichen sehe ich immer noch nicht.

                  Diese Vorgehensweise ist übrigens in der PHP-Doku auch beschrieben.

                  Link?

                  Ich habe nur das gefunden:

                  xml_set_character_data_handler()

                  Genau dort. :) http://de.php.net/manual/en/function.xml-set-character-data-handler.php

                  Character data is roughly all the non-markup contents of XML documents, including whitespace between tags. Note that the XML parser does not add or remove any whitespace, it is up to the application (you) to decide whether whitespace is significant.

                  Character data handler is called for every piece of a text in the XML document. It can be called multiple times inside each fragment (e.g. for non-ASCII strings).

                  »»

                  Das sehe ich nicht. Ich habe die Offline-Version des PHP-Manuals. Vielleicht ist meine Version veraltet?

                  "multiple times inside each fragment"...

                  Ja natürlich. UTF-8 verwendet zwischen einem und vier Bytes pro Zeichen. Da PHP das Unicode in den normalen Stringfunktionen nicht "versteht", sondern nur byteorientiert arbeitet, gibt strlen() halt einen Wert zurück, den du nicht "erwartest", der aber hinsichtlich des Speicherbedarfs korrekt ist.

                  »»
                  Ok, das leuchtet mir ein.

                  Die korrekte Stringlänge in ZEICHEN erhälst du, wenn du die Multibyte-Stringfunktionen verwendest. Die analoge Funktion zur herkömmlichen PHP-Stringfunktion erhälst du grundsätzlich, indem du "mb_" voranstellst, und damit dann in der Doku suchst. Für strlen z.B.: http://de.php.net/manual/en/function.mb-strlen.php.

                  Wieder was gelernt. Von der Funktion habe ich noch nie gehört. Vielen Dank!

                  1. Moin!

                    Ist das (1024 Byte) die Obergrenze, was maximal in einem Event als Textmenge zurückgegeben wird, oder hast Du die Zahl einfach mal in den Raum geworfen?

                    Nein, das ist eine willkürlich gewählte Zahl. Sieht man oft in Beispielen, die fgets() etc. verwenden. Genauso häufig wird (in Beispielen) aber die gesamte Datei in einem String eingelesen.

                    Was jeweils sinnvoller ist, ist in erster Instanz Geschmackssache, in zweiter Instanz wird das von der verfügbaren Speichergröße in Relation zur erwarteten Dateigröße bestimmt (megabytegroße Dateien auf diese Weise einzulesen ist keine so gute Idee, wenn das Skript dabei ins Memory-Limit kommt).

                    Aber den Sinn einer Zerstückelung bei Sonderzeichen sehe ich immer noch nicht.

                    Keine Ahnung, was SAX an der Stelle intern macht. Ich vermute mal, dass an dieser Stelle Prüfroutinen anspringen, die das Mehr-Byte-Zeichen auf Zulässigkeit checken. Alles bis dahin wird schon mal dem Eventhandler übergeben, dann erst wird geschaut, ob ein gültiges UTF-8-Zeichen vorliegt.

                    xml_set_character_data_handler()

                    Genau dort. :) http://de.php.net/manual/en/function.xml-set-character-data-handler.php

                    Das sehe ich nicht. Ich habe die Offline-Version des PHP-Manuals. Vielleicht ist meine Version veraltet?

                    Dein Text stammt aus der Hauptseite von SAX. Mein Text aus der Beschreibung von xml_set_character_data_handler().

                    Es ist immer eine gute Idee, die Online-Version zu checken und eine Offline-Version möglichst aktuell zu halten.

                    - Sven Rautenberg

                    --
                    "Love your nation - respect the others."
          4. Hello,

            foreach($titel as $titel)
              echo $titel;

            Ob die lokale Verdeckung hier wirklich sicher funktioniert?
            Sowas wäre mir zu unsicher!

            foreach($titel as $element)
             {
               echo $element;
             }

            ist da sicherer.

            Harzliche Grüße vom Berg
            http://bergpost.annerschbarrich.de

            Tom

            --
            Fortschritt entsteht nur durch die Auseinandersetzung der Kreativen
            Nur selber lernen macht schlau
            Ein Jammer ist auch, dass die Dummen so selbstsicher und die Klugen voller Zweifel sind. Das sollte uns häufiger zweifeln lassen :-)