Diesen Artikel habe ich ursprünglich im SAP Community Network veröffentlicht.

Es gibt verschiedene Möglichkeiten, mit XML-Daten in ABAP umzugehen, die alle mehr oder weniger gut dokumentiert sind. Wenn Sie beispielsweise einen abwärtskompatiblen ereignisbasierten Parsing-Ansatz benötigen, können Sie die iXML-Bibliothek mit ihrem integrierten Parser im SAX-Stil verwenden. (Beachten Sie, dass iXML das gesamte Dokument immer noch erstellt, sodass es eher einem DOM-Parser mit einer angehängten SAX-Ereignisausgabe ähnelt. Wenn Sie eine streng serielle Verarbeitungsfunktion suchen, sehen Sie sich stattdessen die relativ neue sXML-Bibliothek an.

Die iXML-Dokumentation hat einen, sagen wir, besonderen Schreibstil, und die Bibliothek hebt sich stolz vom restlichen ABAP-Ökosystem ab (z. B. durch die Verwendung von Null-basierten Indizes anstelle von Eins-basierten Listen an verschiedenen Stellen), aber alles in allem ist sie eine brauchbare und stabile Lösung. Das heißt, wenn Sie die erste Regel von iXML beachten: Size Does Matter. Betrachten Sie das folgende Beispiel:

REPORT ztest_ixml_sax_parser.  

CLASS lcl_test_ixml_sax_parser DEFINITION CREATE PRIVATE.  
  PUBLIC SECTION.  
    CLASS-METHODS run.  
ENDCLASS.  

CLASS lcl_test_ixml_sax_parser IMPLEMENTATION.  
  METHOD run.  
    CONSTANTS: co_line_length TYPE i VALUE 100.  
    TYPES: t_line   TYPE c LENGTH co_line_length,  
           tt_lines TYPE TABLE OF t_line.  
    DATA: lt_xml_data       TYPE tt_lines,  
          l_xml_size        TYPE i,  
          lr_ixml           TYPE REF TO if_ixml,  
          lr_stream_factory TYPE REF TO if_ixml_stream_factory,  
          lr_istream        TYPE REF TO if_ixml_istream,  
          lr_document       TYPE REF TO if_ixml_document,  
          lr_parser         TYPE REF TO if_ixml_parser,  
          lr_event          TYPE REF TO if_ixml_event,  
          l_num_errors      TYPE i,  
          lr_error          TYPE REF TO if_ixml_parse_error.  
    DATA: lr_ostream TYPE REF TO cl_demo_output_stream.  
    " prepare the output stream and display  
    lr_ostream = cl_demo_output_stream=>open( ).  
    SET HANDLER cl_demo_output_html=>handle_output FOR lr_ostream.  
    " prepare the data to be parsed  
    lt_xml_data = VALUE #( ( '<?xml version="1.0"?>' )  
                           ( '<foo name="bar">' )  
                           ( '  <baz number="1"/>' )  
                           ( '  <baz number="2"/>' )  
                           ( '  <baz number="4"/>' )  
                           ( '</foo>' ) ).  
    " determine the size of the table - since the lines have a fixed length, that should be easy  
    l_xml_size = co_line_length * lines( lt_xml_data ).  
    " initialize the iXML objects  
    lr_ixml = cl_ixml=>create( ).  
    lr_stream_factory = lr_ixml->create_stream_factory( ).  
    lr_istream = lr_stream_factory->create_istream_itable( table = lt_xml_data  
                                                           size  = l_xml_size ).  
    lr_document = lr_ixml->create_document( ).  
    lr_parser = lr_ixml->create_parser( stream_factory = lr_stream_factory  
                                        istream        = lr_istream  
                                        document       = lr_document ).  
    lr_parser->set_event_subscription( if_ixml_event=>co_event_attribute_post +  
                                       if_ixml_event=>co_event_element_pre +  
                                       if_ixml_event=>co_event_element_post ).  
    " the actual event handling loop.  
    lr_ostream->write_text(  
        iv_text   = 'iXML Parser Events'  
        iv_format = if_demo_output_formats=>heading  
        iv_level  = 1  
    ).  
    DO.  
      lr_event = lr_parser->parse_event( ).  
      IF lr_event IS INITIAL. " if either the end of the document is reached or an error occurred  
        EXIT.  
      ENDIF.  
      CASE lr_event->get_type( ).  
        WHEN if_ixml_event=>co_event_element_pre.  
          lr_ostream->write_text( |new element '{ lr_event->get_name( ) }'| ).  
        WHEN if_ixml_event=>co_event_attribute_post.  
          lr_ostream->write_text( |attribute '{ lr_event->get_name( ) }' = '{ lr_event->get_value( ) }'| ).  
        WHEN if_ixml_event=>co_event_element_post.  
          lr_ostream->write_text( |end of element '{ lr_event->get_name( ) }'| ).  
      ENDCASE.  
    ENDDO.  
    " error handling  
    l_num_errors = lr_parser->num_errors( ).  
    IF l_num_errors > 0.  
      lr_ostream->write_text(  
          iv_text   = 'iXML Parser Errors'  
          iv_format = if_demo_output_formats=>heading  
          iv_level  = 1  
      ).  
      DO l_num_errors TIMES.  
        lr_error = lr_parser->get_error( sy-index - 1 ). " because iXML is 0-based  
        lr_ostream->write_text( |{ lr_error->get_severity_text( ) } at offset { lr_error->get_offset( ) }: { lr_error->get_reason( ) }| ).  
      ENDDO.  
    ENDIF.  
    lr_ostream->close( ).  
  ENDMETHOD.  
ENDCLASS.  

START-OF-SELECTION.  
  lcl_test_ixml_sax_parser=>run( ). 

Sie können dieses Programm in Ihr System kopieren und ausführen, es richtet keinen Schaden an: Es erstellt einfach ein einfaches XML-Dokument (in einer echten Anwendung würden Sie dieses aus einer Datei, einer Datenbank, einer Netzwerkquelle – was auch immer – erhalten), konstruiert einen Eingabestrom darum herum, übergibt es an einen Parser und führt eine Parse-Evaluate-Print-Schleife aus, bis entweder das Ende der Ausgabe erreicht ist oder etwas Schlimmes passiert.

Wenn Ihr System ein Nicht-Unicode-System (NUC) ist (Sie können dies einfach unter „System –> Status“ überprüfen), läuft das Programm einwandfrei und erzeugt eine Ausgabe, die der folgenden Abbildung ähnelt:

Wenn Ihr System ein Unicode-System (UC) ist, verhält sich das Programm nicht ganz so wie sonst – Sie erhalten eine eher nichtssagende Fehlermeldung (error at offset 0: unexpected symbol; expected ‘<’, ‘).

Es ist sicherlich nicht hilfreich, dass der Parser beim Zusammenstellen der Fehlermeldung keinen Offset (oder eine Zeilen- und Spaltennummer) zurückgibt Fehlermeldung. Die vor den Fehlermeldungen protokollierten Ereignisse geben jedoch einen Hinweis: Der Fehler tritt immer auf, nachdem die Hälfte der Tabellenzeilen verarbeitet wurde. Sie können dies leicht überprüfen, indem Sie die Anzahl der baz-Elemente in dem obigen Beispiel ändern. Da ich bereits erwähnt habe, dass dieses Problem nur auf UC-Systemen auftritt, ist es nun einfach zu erkennen, was hier schiefgelaufen ist:

Die iXML-Stream-Factory erwartet, dass die Größe in der Anzahl der Bytes und nicht in der Anzahl der Zeichen angegeben wird. Der Code funktioniert, solange ein Zeichen durch ein einzelnes Byte dargestellt wird, aber in UC-Systemen ist das nicht der Fall. Die Lösung – oder vielleicht eine der Lösungen – ist relativ einfach:

    " determine the size of the table for both UC and NUC systems  
    l_xml_size = co_line_length * lines( lt_xml_data ) * cl_abap_char_utilities=>charsize.

Diese Falle ist ziemlich hinterhältig, da sie von den Standard-Unicode-Prüfungen nicht erkannt wird und die Fehlermeldung so irreführend wie nur möglich ist. Ob Sie die Meldung überhaupt sehen, hängt außerdem von der tatsächlichen Implementierung des Parsing-Programms ab. Wenn der ursprüngliche Entwickler dachte, dass die Fehlerbehandlung von denjenigen implementiert werden könnte, die nach ihm kommen – nun, das ist ein weiter Weg nach unten…