
ABAP Cloud - XML erstellen
Wie kannst du eigentlich in ABAP Cloud ein XML außerhalb von Transformationen erstellen? In diesem Artikel bauen wir ein XML im Detail nach.
Inhaltsverzeichnis
In einem älteren Artikel hatten wir uns angeschaut, wie wir einen XML Stream lesen können. In diesem Artikel werden wir auf die Details bei der Erstellung eingehen und uns Schritt für Schritt die Struktur vom letzten Mal zusammenbauen.
Einleitung
Ab und zu müssen wir in ABAP auch XML Dateien und Streams verarbeiten, weil wir sie vielleicht aus der Dateiverarbeitung erhalten oder in der ABAP Cloud Welt als Payload an einen Service senden müssen, weil dieser kein JSON versteht. Neben den Transformationen bietet SAP auch verschiedene freigegebene ABAP APIs im System, mit dem wir das umsetzen können. Die Herausforderung bei Transformationen ist, dass sie zusätzlich noch einmal ein eigenes Skillset benötigen, um die Syntax dahinter zu erlernen.
In diesem Artikel wollen wir die Struktur aus dem letzten Artikel nachbauen. Die Struktur enthält verschiedene Bestandteile, wie einen eigenen Namespace, Attribute und Werte auf verschiedenen Ebenen.
Hinweis: Erklärung zu den einzelnen Bestandteilen des XML findest du in dem oben verlinkten Artikel, um den Aufbau des XML erst einmal besser zu verstehen.
Einstieg
Im ersten Schritt benötigen wir eine Konfiguration und einige Daten, um mit der Umsetzung beginnen zu können. Dabei verwenden wir den gleichen Typen aus dem letzten Artikel, den wir uns lokal in der Klasse anlegen.
TYPES: BEGIN OF people,
height TYPE c LENGTH 9,
name TYPE string,
END OF people.
TYPES peoples TYPE STANDARD TABLE OF people WITH EMPTY KEY.
TYPES: BEGIN OF file,
head_key TYPE c LENGTH 10,
head_description TYPE string,
title TYPE string,
description TYPE string,
desc_length TYPE i,
desc_space TYPE c LENGTH 20,
tags TYPE string,
peoples TYPE peoples,
END OF file.
Danach befüllen wir die Konfiguration und übergeben sie an die Generierungsfunktion, die uns dann das XML erstellt. Die Daten in unserem Beispiel würden damit wie folgt aussehen.
DATA(setting) = VALUE file( head_key = 'H1'
head_description = `My custom XML`
title = `First try`
description = `A description text`
desc_length = 25
desc_space = 'minimum'
tags = `Name, Data, Others`
peoples = VALUE #( ( height = '150cm' name = `Jason` )
( height = '155cm' name = `Pamela` )
( height = '190cm' name = `Ryan` ) ) ).
Umsetzung
In diesem Kapitel gehen wir auf das Mapping und die Umsetzung ein. Dabei verwenden wir die Klasse CL_SXML_STRING_WRITER um unser XML zu erzeugen. In den weiteren Abschnitten gehen wir dann auf die verschiedenen Besonderheiten ein.
Writer
Damit wir mit der Erstellung beginnen können, legen wir einen String Writer Objekt an, dieses benötigen wir nur lokal und legen es als lokale Variable an.
DATA(string_writer) = cl_sxml_string_writer=>create( ).
writer = CAST if_sxml_writer( string_writer ).
Der String Writer hat eine recht kleine Anzahl an Methoden und dient vor allem zur Erzeugung der Datei. Deshalb müssen wir einen CAST auf IF_SXML_WRITER durchführen. Die Instanz legen wir uns als Attribut in der Klasse ab, da wir aus den verschiedenen Methoden darauf zugreifen wollen. Grundsätzlich kannst du die Instanz auch von Methode zu Methode weitergeben.
Das Interface bietet den größten Teil der Methoden die wir für die Arbeit mit dem XML Objekt benötigen.
Struktur
Damit wir nun unsere Struktur abarbeiten können, bilden wir die verschiedenen Schritte als Methoden ab und rufen diese nacheinander auf. Damit zerlegen wir die große Aufgabe in kleine Bestandteile und können Knoten für Knoten das Mapping abarbeiten.
open_root( ).
add_title( ).
add_description( ).
add_people( ).
add_tags( ).
close_node( ).
Die Methode CLOSE_NODE legen wir zentral an, da wir diese zum Schließen eines Knotens mehrfach benötigen und so einfacher als Funktion wiederverwenden können. Die Methode erzeugt ein schließendes Element und schreibt es über den WRITER in das Objekt.
writer->write_node( writer->new_close_element( ) ).
Knoten mit Attributen
Unser Wurzelknoten besteht aus einem Namespace und zwei Attributen. Entsprechend lassen wir uns vom WRITER ein neues öffnendes Element anlegen und übergeben den Namen, den Namespace und das Präfix des Namespaces. Über das erzeugte Objekt können wir die Attribute mit ihren Werten setzen. Damit wir den Knoten übernehmen, rufen wir am Ende die Methode WRITE_NODE auf und übergeben unser Objekt.
DATA(open_node) = writer->new_open_element( name = `myroot`
nsuri = namespace_swh
prefix = prefix_swh ).
open_node->set_attribute( name = `key`
value = run_setting-title ).
open_node->set_attribute( name = `description`
value = run_setting-description ).
writer->write_node( open_node ).
Führen wir die bisherige Logik (Root Knoten und Schließen) aus, dann erhalten wir aktuell das folgende Ergebnis in Form unseres ersten Knotens.
<swh:myroot key="First try" description="A description text" xmlns:swh="http://software-heroes/swh"/>
Der Namespace wurde in den Knoten übernommen und auch alle Attribute finden wir wieder. Damit können wir beginnen die weiteren Knoten in der gleichen Form zu erzeugen.
Knoten mit Wert
Wie sieht es nun aus, wenn wir einen Wert schreiben wollen? Der Wert befindet sich zwischen den beiden Tags. In diesem Beispiel erzeugen wir das TITLE Tag und übernehmen den Knoten ins Modell. Im nächsten Schritt erzeugen wir über NEW_VALUE einen Knoten für den Wert, übernehmen den Wert mit SET_VALUE und fügen diesen ebenfalls hinzu. Zum Abschluss schließen wir den aktuellen Knoten.
DATA(open_node) = writer->new_open_element( name = `title`
nsuri = namespace_swh
prefix = prefix_swh ).
writer->write_node( open_node ).
DATA(value_node) = writer->new_value( ).
value_node->set_value( run_setting-title ).
writer->write_node( value_node ).
close_node( ).
Abschluss
Wenn wir alle Informationen und Knoten erzeugt haben, dann können wir über das Original-Objekt die Methode GET_OUTPUT aufrufen, die uns das XML als Binary zurückgibt. Über die XCO Klasse wandeln wir den Inhalt nun in einen lesbaren String um. Bevor wir das Ergebnis zurückgeben, um es zu prüfen, fügen wir noch das XML Tag am Anfang hinzu, dieses wird leider nicht mit erzeugt.
DATA(binary) = string_writer->get_output( ).
DATA(content) = xco_cp=>xstring( binary )->as_string( xco_cp_character=>code_page->utf_8 )->value.
RETURN |<?xml version="1.0" encoding="UTF-8"?>{ content }|.
Schauen wir uns das Ergebnis an, dann solltest du nun die folgende Ausgabe haben. In diesem Fall haben wir uns das Ergebnis im Browser rendern lassen, da die Klasse den String unformatiert zurückgibt.
Optionen
Im letzten Schritt hatten wir den Header manuell an den Stream hinzugefügt, davon sind vor allem ältere Releases betroffen. Ab dem Release 2502 ist es nun möglich, den Header zu erzeugen, ob mit oder ohne Encoding. Dazu stehen die beiden Konstanten zur Verfügung:
- IF_SXML_WRITER=>CO_OPT_VAL_FULL generiert den Kompletten Header (<?xml version="1.0" encoding="utf-8"?>)
- IF_SXML_WRITER=>CO_OPT_VAL_WITHOUT_ENCODING generiert die Ausgabe ohne das entsprechende Encoding (IF_SXML_WRITER=>CO_OPT_VAL_FULL)
In unserem Beispiel können wir neben dem Header auch weitere Optionen für die Ausgabe definieren. Setzen wir zum Beispiel Zeilenumbrüche und eine Einrückung, dann können wir das Ergebnis auch in die Konsole ausgeben.
writer->set_option( option = if_sxml_writer=>co_opt_xml_header
value = if_sxml_writer=>co_opt_val_full ).
writer->set_option( option = if_sxml_writer=>co_opt_linebreaks
value = abap_true ).
writer->set_option( option = if_sxml_writer=>co_opt_indent
value = abap_true ).
In der ABAP Konsole würde dann das finale Ergebnis wie folgt aussehen.
Komplettes Beispiel
Wir sind in diesem Artikel nicht auf alle Einzelheiten der Konvertierung eingegangen. Die vollständige Klasse findest du allerdings hier, wenn du es bei dir im System testen willst oder noch weitere Informationen zur Klasse suchst.
CLASS zcl_bs_demo_xml_create DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
TYPES: BEGIN OF people,
height TYPE c LENGTH 9,
name TYPE string,
END OF people.
TYPES peoples TYPE STANDARD TABLE OF people WITH EMPTY KEY.
TYPES: BEGIN OF file,
head_key TYPE c LENGTH 10,
head_description TYPE string,
title TYPE string,
description TYPE string,
desc_length TYPE i,
desc_space TYPE c LENGTH 20,
tags TYPE string,
peoples TYPE peoples,
END OF file.
CONSTANTS namespace_swh TYPE string VALUE `http://software-heroes/swh`.
CONSTANTS prefix_swh TYPE string VALUE `swh`.
DATA run_setting TYPE file.
DATA writer TYPE REF TO if_sxml_writer.
METHODS create_xml_content
IMPORTING run_setting TYPE file
RETURNING VALUE(result) TYPE string.
METHODS add_person
IMPORTING !person TYPE zcl_bs_demo_xml_create=>people.
METHODS close_node.
METHODS open_root.
METHODS add_title.
METHODS add_description.
METHODS add_people.
METHODS add_tags.
METHODS set_writer_options.
ENDCLASS.
CLASS zcl_bs_demo_xml_create IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(setting) = VALUE file( head_key = 'H1'
head_description = `My custom XML`
title = `First try`
description = `A description text`
desc_length = 25
desc_space = 'minimum'
tags = `Name, Data, Others`
peoples = VALUE #( ( height = '150cm' name = `Jason` )
( height = '155cm' name = `Pamela` )
( height = '190cm' name = `Ryan` ) ) ).
out->write( create_xml_content( setting ) ).
ENDMETHOD.
METHOD create_xml_content.
me->run_setting = run_setting.
DATA(string_writer) = cl_sxml_string_writer=>create( ).
writer = CAST if_sxml_writer( string_writer ).
set_writer_options( ).
open_root( ).
add_title( ).
add_description( ).
add_people( ).
add_tags( ).
close_node( ).
DATA(binary) = string_writer->get_output( ).
DATA(content) = xco_cp=>xstring( binary )->as_string( xco_cp_character=>code_page->utf_8 )->value.
" RETURN |<?xml version="1.0" encoding="UTF-8"?>{ content }|.
RETURN content.
ENDMETHOD.
METHOD set_writer_options.
writer->set_option( option = if_sxml_writer=>co_opt_xml_header
value = if_sxml_writer=>co_opt_val_full ).
writer->set_option( option = if_sxml_writer=>co_opt_linebreaks
value = abap_true ).
writer->set_option( option = if_sxml_writer=>co_opt_indent
value = abap_true ).
ENDMETHOD.
METHOD close_node.
writer->write_node( writer->new_close_element( ) ).
ENDMETHOD.
METHOD open_root.
DATA(open_node) = writer->new_open_element( name = `myroot`
nsuri = namespace_swh
prefix = prefix_swh ).
open_node->set_attribute( name = `key`
value = run_setting-title ).
open_node->set_attribute( name = `description`
value = run_setting-description ).
writer->write_node( open_node ).
ENDMETHOD.
METHOD add_title.
DATA(open_node) = writer->new_open_element( name = `title`
nsuri = namespace_swh
prefix = prefix_swh ).
writer->write_node( open_node ).
DATA(value_node) = writer->new_value( ).
value_node->set_value( run_setting-title ).
writer->write_node( value_node ).
close_node( ).
ENDMETHOD.
METHOD add_description.
DATA(open_node) = writer->new_open_element( name = `description`
nsuri = namespace_swh
prefix = prefix_swh ).
open_node->set_attribute( name = `length`
value = CONV #( run_setting-desc_length ) ).
open_node->set_attribute( name = `space`
value = CONV #( run_setting-desc_space ) ).
writer->write_node( open_node ).
DATA(value_node) = writer->new_value( ).
value_node->set_value( run_setting-description ).
writer->write_node( value_node ).
close_node( ).
ENDMETHOD.
METHOD add_people.
DATA(open_node) = writer->new_open_element( `table` ).
writer->write_node( open_node ).
LOOP AT run_setting-peoples INTO DATA(person).
add_person( person ).
ENDLOOP.
close_node( ).
ENDMETHOD.
METHOD add_tags.
DATA(open_node) = writer->new_open_element( name = `tags`
nsuri = namespace_swh
prefix = prefix_swh ).
writer->write_node( open_node ).
DATA(value_node) = writer->new_value( ).
value_node->set_value( run_setting-tags ).
writer->write_node( value_node ).
close_node( ).
ENDMETHOD.
METHOD add_person.
DATA(open_node) = writer->new_open_element( `item` ).
open_node->set_attribute( name = `height`
value = CONV #( person-height ) ).
writer->write_node( open_node ).
DATA(value_node) = writer->new_value( ).
value_node->set_value( person-name ).
writer->write_node( value_node ).
close_node( ).
ENDMETHOD.
ENDCLASS.
Fazit
Aktuell gibt es viele Dinge in der ABAP Entwicklung zu lernen, bist du neu, wird es dir vielleicht schwer fallen alle Dinge auf einmal zu lernen. Du kannst dich deshalb entscheiden, dir die Klasse anzuschauen oder mit dem Thema Transformation vertraut zu machen.
Weitere Informationen:
SAP Help - sXML Rendering