[ABAP] Desktop Office Integration (DOI): Eventhandler, leeres Dokument erzeugen, Zugriff auf Ole-Funktionen für Ole-Automation

* https://www.consolut.com/fileadmin/user_upload/SAP_PrintDoku/de/BCCIOFFI/BCCIOFFI.PDF
* https://www.saptechpro.com/post/SAPRDEMO_MS_DEMO
* https://www.saptechpro.com/post/SAPRDEMODOCUMENTCONTAINER2

* Eventhandler für Excel on_closed
CLASS lcl_events DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS: on_close FOR EVENT on_close_document OF i_oi_document_proxy
      IMPORTING
          document_proxy
          has_changed.
ENDCLASS.

CLASS lcl_events IMPLEMENTATION.
  METHOD on_close.
    MESSAGE 'Excel closed.' TYPE 'S' DISPLAY LIKE 'W'.
  ENDMETHOD.
ENDCLASS.

* Dummy-Screen für cl_gui_container=>default_screen deklarieren
SELECTION-SCREEN BEGIN OF SCREEN 100.
SELECTION-SCREEN END OF SCREEN 100.

START-OF-SELECTION.

  TRY.

* Daten mit DOI (Excel inplace) anzeigen
      DATA: o_oic TYPE REF TO i_oi_container_control.
      DATA: o_oid TYPE REF TO i_oi_document_proxy.
      DATA: o_err TYPE REF TO i_oi_error.
      DATA: it_errors TYPE STANDARD TABLE OF REF TO i_oi_error WITH NON-UNIQUE DEFAULT KEY.

      c_oi_container_control_creator=>get_container_control( IMPORTING
                                                               control = o_oic
                                                               error   = o_err ).

      APPEND o_err TO it_errors.

      IF abap_true = o_err->has_succeeded.
        o_oic->init_control( EXPORTING
                               inplace_enabled         = abap_false
                               no_flush                = abap_true
                               r3_application_name     = 'Excel Daten'
                               parent                  = cl_gui_container=>default_screen
                               register_on_close_event = abap_true
                             IMPORTING
                               error                   = o_err ).

        APPEND o_err TO it_errors.

        IF abap_true = o_err->has_succeeded.
          o_oic->get_document_proxy( EXPORTING
                                       document_type  = soi_doctype_excel_sheet " 'Excel.Sheet'
                                     IMPORTING
                                       document_proxy = o_oid
                                       error          = o_err ).
          APPEND o_err TO it_errors.

          IF abap_true = o_err->has_succeeded.
* neues, leeres Workbook einfügen
            o_oid->create_document( EXPORTING
                                      document_title = 'Excel Test'
                                      open_inplace   = abap_false
                                    IMPORTING
                                      error          = o_err ).

* Event-Handler für on_close Event
            SET HANDLER lcl_events=>on_close FOR o_oid.

* Abfrage nach dem Automation-Handle des Dokumentes
            DATA: lv_cntl_handle TYPE cntl_handle.
            o_oid->get_document_handle( IMPORTING
                                          handle  = lv_cntl_handle
                                          error   = o_err ).

            APPEND o_err TO it_errors.

            IF abap_true = o_err->has_succeeded.

* Ole-Funktionen Direkt aufrufen
              DATA: o_xl_ole TYPE ole2_object.
              DATA: o_cell_ole TYPE ole2_object.
              GET PROPERTY OF lv_cntl_handle-obj 'Application' = o_xl_ole.
              CALL METHOD OF o_xl_ole 'Cells' = o_cell_ole
                EXPORTING
                  #1 = 1
                  #2 = 1.
              SET PROPERTY OF o_cell_ole 'Value' = 'Test'.
              FREE OBJECT o_cell_ole.
              FREE OBJECT o_xl_ole.

* leere Toolbar ausblenden
              cl_abap_list_layout=>suppress_toolbar( ).

* cl_gui_container=>default_screen erzwingen
              CALL SCREEN 100.

* Schließen eines Dokuments in der Office-Anwendung
              o_oid->close_document( IMPORTING error = o_err ).

              APPEND o_err TO it_errors.

              IF o_err->has_succeeded = abap_true.

* Schließen eines Visual-Basic-Dokuments
                o_oid->close_activex_document( IMPORTING error = o_err ).

                APPEND o_err TO it_errors.

                IF o_err->has_succeeded = abap_true.

* Freigabe des für das Dokument reservierten Speichers
                  o_oid->release_document( IMPORTING error = o_err ).

                  APPEND o_err TO it_errors.

                  IF o_err->has_succeeded = abap_true.
                    FREE: o_oid.
                  ENDIF.
                ENDIF.
              ENDIF.
            ENDIF.

* baut das Control ab
            o_oic->destroy_control( IMPORTING error = o_err ).

            APPEND o_err TO it_errors.

            IF o_err->has_succeeded = abap_true.
              FREE: o_oic.
            ENDIF.

          ENDIF.
        ENDIF.
      ENDIF.

* Ausgabe Fehlerprotokoll
      DATA: lv_message_id TYPE sy-msgid.
      DATA: lv_message_number TYPE sy-msgno.
      DATA: lv_param1 TYPE sy-msgv1.
      DATA: lv_param2 TYPE sy-msgv2.
      DATA: lv_param3 TYPE sy-msgv3.
      DATA: lv_param4 TYPE sy-msgv4.

      LOOP AT it_errors ASSIGNING FIELD-SYMBOL(<e>).
        <e>->get_message( IMPORTING
                            message_id     = lv_message_id
                            message_number = lv_message_number
                            param1         = lv_param1
                            param2         = lv_param2
                            param3         = lv_param3
                            param4         = lv_param4
                        ).
        WRITE: / <e>->error_code, lv_message_id, lv_message_number, lv_param1, lv_param2, lv_param3, lv_param4.
      ENDLOOP.

    CATCH cx_root INTO DATA(e_txt).
      WRITE: / e_txt->get_text( ).
  ENDTRY.

[ABAP] Interne Tabelle als Excel-Datei (*.xlsx) speichern (cl_fdt_xl_spreadsheet)

* Mit Hilfe der Klasse cl_fdt_xl_spreadsheet können über XML-Transformationen
* Daten aus SAP in XLSX-Dateien geschrieben werden
TRY.
* Testdaten lesen
    SELECT * FROM t001 INTO TABLE @DATA(it_t001).

    IF sy-subrc = 0.
* Header erzeugen
      DATA: it_columns TYPE if_fdt_doc_spreadsheet=>t_column.
      DATA: lv_head TYPE t001.
      DATA(o_desc) = CAST cl_abap_structdescr( cl_abap_structdescr=>describe_by_data( lv_head ) ).

      LOOP AT o_desc->get_components( ) ASSIGNING FIELD-SYMBOL(<c>).
        IF <c> IS ASSIGNED.
          IF <c>-type->kind = cl_abap_typedescr=>kind_elem.
            APPEND VALUE #( id           = sy-tabix
                            name         = <c>-name
                            display_name = <c>-name
                            is_result    = abap_true
                            type         = <c>-type ) TO it_columns.
          ENDIF.
        ENDIF.
      ENDLOOP.

* itab + header -> XML -> xstring
* Achtung: Speicherintensiv und rel. langsam! Es sollten keine großen Datenmengen verarbeitet werden.
      DATA(lv_bin_data) = cl_fdt_xl_spreadsheet=>if_fdt_doc_spreadsheet~create_document( columns      = it_columns " optional
                                                                                         itab         = REF #( it_t001 )
                                                                                         iv_call_type = if_fdt_doc_spreadsheet=>gc_call_dec_table ).
      IF xstrlen( lv_bin_data ) > 0.
        DATA: lv_action TYPE i.
        DATA: lv_filename TYPE string.
        DATA: lv_fullpath TYPE string.
        DATA: lv_path TYPE string.

* Save-Dialog
        cl_gui_frontend_services=>file_save_dialog( EXPORTING
                                                      default_file_name = 'Excel.xlsx'
                                                      default_extension = 'xlsx'
                                                      file_filter       = |Excel-Datei (*.xlsx)\|*.xlsx\|{ cl_gui_frontend_services=>filetype_all }|
                                                    CHANGING
                                                      filename          = lv_filename
                                                      path              = lv_path
                                                      fullpath          = lv_fullpath
                                                      user_action       = lv_action ).

        IF lv_action EQ cl_gui_frontend_services=>action_ok.
* XSTRING -> SOLIX (RAW)
          DATA(it_raw_data) = cl_bcs_convert=>xstring_to_solix( EXPORTING iv_xstring = lv_bin_data ).

* Datei lokal speichern
          cl_gui_frontend_services=>gui_download( EXPORTING
                                                    filename     = lv_fullpath
                                                    filetype     = 'BIN'
                                                    bin_filesize = xstrlen( lv_bin_data )
                                                  CHANGING
                                                    data_tab     = it_raw_data ).


        ENDIF.
      ENDIF.
    ENDIF.
  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'I'.
ENDTRY.

[ABAP] XML-Strings parsen

Variante 1 (IF_IXML_ELEMENT, IF_IXML_NODE_COLLECTION)

* XML-Daten
DATA(lv_xml) = |<object>| &&
               |  <str name="text">abcd</str>| &&
               |  <bool name="flag">true</bool>| &&
               |  <member name="number"><num>111</num></member>| &&
               |  <member name="content"><null /></member>| &&
               |  <member name="attr"></member>| &&
               |</object>|.

* XML-Interface
DATA(o_ixml) = cl_ixml=>create( ).
* XML-Doc
DATA(o_doc) = o_ixml->create_document( ).
* Stream-Factory
DATA(o_sf) = o_ixml->create_stream_factory( ).
* Stream
DATA(o_stream) = o_sf->create_istream_string( string = lv_xml ).

* Parser-Objekt erzeugen
DATA(o_parser) = o_ixml->create_parser( document       = o_doc
                                        istream        = o_stream
                                        stream_factory = o_sf ).

* XML parsen
IF o_parser->parse( ) = 0.
* Root-Node
  DATA(o_root) = o_doc->get_root_element( ).

* Alle Nodes mit Namen 'member'
  DATA(o_nodes_member) = o_root->get_elements_by_tag_name( name = 'member' ).
* Iterator für die gefundenen Nodes
  DATA(o_node_iterator_members) = o_nodes_member->create_iterator( ).
* ersten Iterator-Wert holen
  DATA(o_nodes_temp) = o_node_iterator_members->get_next( ).

* Iterator durchgehen
  WHILE NOT o_nodes_temp IS INITIAL.
* Wert der Node ausgeben
    WRITE: / o_nodes_temp->get_value( ).

* Attribute
    DATA(o_note_temp_attr) = o_nodes_temp->get_attributes( ).
    DATA(o_node_temp_item) = o_note_temp_attr->get_named_item( 'name' ).
    IF o_node_temp_item IS BOUND.
      WRITE: / o_node_temp_item->get_value( ).
    ENDIF.

* Children
    DATA(o_node_temp_children) = o_nodes_temp->get_children( ).
    WRITE: / 'Childrens:', o_node_temp_children->get_length( ).

* nächster Iterator-Wert
    o_nodes_temp = o_node_iterator_members->get_next( ).
  ENDWHILE.
ELSE.
* Fehlerauswertung
  DO o_parser->num_errors( ) TIMES.
    DATA(o_err) = o_parser->get_error( index = sy-index - 1 ).
    IF o_err IS BOUND.
      WRITE: / o_err->get_column( ), o_err->get_line( ), o_err->get_reason( ).
    ENDIF.
  ENDDO.
ENDIF.

Variante 2 (find_from_name)

* XML-Daten
DATA(lv_xml) = |<object>| &&
               |  <str name="text">abcd</str>| &&
               |  <bool name="flag">true</bool>| &&
               |  <member name="number"><num>111</num></member>| &&
               |  <member name="content"><null /></member>| &&
               |  <member name="attr"></member>| &&
               |</object>|.

* XML-Interface
DATA(o_ixml) = cl_ixml=>create( ).
* XML-Doc
DATA(o_doc) = o_ixml->create_document( ).
* Stream-Factory
DATA(o_sf) = o_ixml->create_stream_factory( ).
* Stream
DATA(o_stream) = o_sf->create_istream_string( string = lv_xml ).

* Parser-Objekt erzeugen
DATA(o_parser) = o_ixml->create_parser( document       = o_doc
                                        istream        = o_stream
                                        stream_factory = o_sf ).

* XML parsen
IF o_parser->parse( ) = 0.
* <object>
  DATA(o_root) = o_doc->find_from_name( 'object' ).

  IF o_root IS BOUND.
* <str ...>
    DATA(o_str) = o_root->get_first_child( ).
    IF o_str IS BOUND.
* Wert der Node ausgeben
      WRITE: / o_str->get_value( ).

* Attribut 'name'
      DATA(o_attr) = o_str->get_attributes( ).
      DATA(o_name) = o_attr->get_named_item( 'name' ).
      IF o_name IS BOUND.
        WRITE: / o_name->get_value( ).
      ENDIF.
    ENDIF.
  ENDIF.
ELSE.
* Fehlerauswertung
  DO o_parser->num_errors( ) TIMES.
    DATA(o_err) = o_parser->get_error( index = sy-index - 1 ).
    IF o_err IS BOUND.
      WRITE: / o_err->get_column( ), o_err->get_line( ), o_err->get_reason( ).
    ENDIF.
  ENDDO.
ENDIF.

[ABAP] PDF-Dokument per ADS (Adobe Document Service) in XML konvertieren

* https://www.berater-wiki.de/XML_aus_PDF_extrahieren_mit_Testprogramm_FP_PDF_TEST_03
* http://sapfirst.blogspot.com/2014/05/inbound-abap-proxy-with-pdf-attachment.html
* Paket:     SAFP
* Programme: FP_PDF_TEST_01 (PDF erzeugen)
*            FP_PDF_TEST_03 (Datenextraktion)
* XFD (XML Form Data): https://www.berater-wiki.de/XFD-Datei_(Values_of_PDF_form_fields_in_XML)
* XFT (XML Form Template)
* XDC (XML Printer Definitions)

TRY.
    DATA: lv_rc TYPE i.
    DATA: it_files TYPE filetable.
    DATA: lv_action TYPE i.

* FileOpen-Dialog aufrufen
    cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                  file_filter    = |pdf (*.pdf)\|*.pdf\|{ cl_gui_frontend_services=>filetype_all }|
                                                CHANGING
                                                  file_table  = it_files
                                                  rc          = lv_rc
                                                  user_action = lv_action ).

    IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn mind. zwei Dateien ausgewählt worden sind
      IF lines( it_files ) > 0.
        DATA(lv_filesize) = CONV w3param-cont_len( '0' ).
        DATA(it_bin_data) = VALUE w3mimetabtype( ).

* Datei auf Appl. Server hochladen (binary)
        cl_gui_frontend_services=>gui_upload( EXPORTING
                                                filename   = |{ it_files[ 1 ]-filename }|
                                                filetype   = 'BIN'
                                              IMPORTING
                                                filelength = lv_filesize
                                              CHANGING
                                                data_tab   = it_bin_data ).

* solix -> xstring
        DATA(lv_bin_data) = cl_bcs_convert=>solix_to_xstring( it_solix = it_bin_data ).

* Form Processor erzeugen
        DATA(o_form_processor) = cl_fp=>get_reference( ).
* PDF-Objekt erzeugen
        DATA(o_pdf) = o_form_processor->create_pdf_object( ).
* Binärdaten an PDF-Objekt übergeben
        o_pdf->set_document( pdfdata = lv_bin_data ).
* PDF-Daten extrahieren
        o_pdf->set_extractdata( ).
* ADS-Server rufen
        o_pdf->execute( ).
* extrahierte Daten holen (xstring)
        o_pdf->get_data( IMPORTING formdata = DATA(lv_pdf_formdata) ).

* xstring (binary) -> XML-string (UTF-8)
        DATA(lv_pdf_xml_str) = ||.
        DATA(o_conv) = cl_abap_conv_in_ce=>create( input    = lv_pdf_formdata
                                                   encoding = 'UTF-8' ).
        o_conv->read( IMPORTING data = lv_pdf_xml_str ).

* XML-Zugriff
        DATA(o_ixml) = cl_ixml=>create( ).

        DATA(o_sf) = o_ixml->create_stream_factory( ).
        DATA(o_doc) = o_ixml->create_document( ).
        DATA(o_stream) = o_sf->create_istream_string( lv_pdf_xml_str ).

* XML-Parser
        DATA(o_parser) = o_ixml->create_parser( stream_factory = o_sf
                                                istream        = o_stream
                                                document       = o_doc ).

* XML parsen
        IF o_parser->parse( ) = 0.

          DATA(o_root) = o_doc->get_root_element( ).

          ...

        ELSE.
* Fehlerauswertung
          DO o_parser->num_errors( ) TIMES.
            DATA(o_err) = o_parser->get_error( index = sy-index - 1 ).
            IF o_err IS BOUND.
              WRITE: / o_err->get_column( ), o_err->get_line( ), o_err->get_reason( ).
            ENDIF.
          ENDDO.
        ENDIF.

      ENDIF.
    ENDIF.
  CATCH cx_root INTO DATA(e_txt).
    WRITE: / e_txt->get_text( ).
ENDTRY.

[ABAP] HTML-Email versenden

DATA: lv_recipient TYPE adr6-smtp_addr.
DATA: lv_subject TYPE string.

lv_recipient = 'email@email.com'.
lv_subject = 'Testemail'.

DATA(lv_html_text) = VALUE string( ).

lv_html_text = |<BODY> | &&
               |<P><B>Test</B></BR>| &&
               |</BODY>|.

DATA(it_body_txt) = cl_document_bcs=>string_to_soli( ip_string = lv_html_text ).

TRY.
* it_body_txt beinhaltet die Zeilen des Body-Textes
* die Zeilen sind jeweils char255 Zeichen lang
* eine Zeile (line) ist vom Typ SOLI

* Document (Mail) erzeugen
* Email-Subject (i_subject) darf hier max. 50 Zeichen lang sein (Subject-Darstellung in der SOST)
* die "richtige" Übergabe des Email-Subjects erfolgt weiter unten in der Funktion set_message_subject( )
    DATA(o_document) = cl_document_bcs=>create_document( i_type    = 'HTM'
                                                         i_text    = it_body_txt
                                                         i_subject = CONV so_obj_des( lv_subject ) ).

* Sendrequest erzeugen
    DATA(o_send_request) = cl_bcs=>create_persistent( ).
* Email-Subject festlegen, ip_subject ist vom Typ String
    o_send_request->set_message_subject( ip_subject = lv_subject ).
* Die Mail an den Sendrequest hängen
    o_send_request->set_document( o_document ).

* SAP-User als Sender
    DATA(o_sender) = cl_sapuser_bcs=>create( sy-uname ).
* Email-Adresse als Sender
*        DATA(o_sender) = cl_cam_address_bcs=>create_internet_address( i_address_string = 'email@email.com' ).
    o_send_request->set_sender( o_sender ).

* Empfänger -> beachte: u.U. sind Email-Adressen mit Umlauten nicht zulässig -> Exception
    DATA(o_recipient) = cl_cam_address_bcs=>create_internet_address( lv_recipient ).
    o_send_request->add_recipient( i_recipient = o_recipient
                                   i_express = abap_true
                                 " i_copy      = abap_true " CC
                                 " i_blind_copy = abap_true " BCC
                                 ).

* Sofort senden
    o_send_request->set_send_immediately( abap_true ).

* Dokument senden
    IF o_send_request->send( i_with_error_screen = abap_true ) = abap_true.
      WRITE: / 'Email gesendet.'.
    ELSE.
      WRITE: / 'Fehler beim Email-Versand.'.
    ENDIF.

* Persistenzdienste des Objektes starten, sonst wird ggf. die Mail nicht gesendet
    COMMIT WORK.

  CATCH cx_root INTO DATA(e_text).
    WRITE: / e_text->get_text( ).
ENDTRY.

[ABAP] Interne Tabellen im Format Office Open XML (SpreadsheetML) speichern

Beispiel für die Nutzung des Office Open XML (SpreadsheetML) Formates zur Datenausgabe von Nutzdaten aus internen Tabellen.

Links

Wikipedia – Office Open XML
Wikipedia – SpreadsheetML
SAP ABAP Doku
Beispiel 1
Beispiel 2

Beispiel – Speicherung Daten beliebiger interner Tabellen in eine von Microsoft Office Excel lesbare XML-Datei

* Dateien können ab Microsoft Office Excel 2002 / 2003 geöffnet werden
DATA: it_spfli TYPE STANDARD TABLE OF spfli.
DATA: gv_company TYPE string VALUE 'codezentrale.de'.
DATA: gv_user TYPE string VALUE 'Testuser'.
DATA: gv_sheetname TYPE string VALUE 'SPFLI'.

INITIALIZATION.
* Daten holen
  SELECT * FROM spfli INTO TABLE @it_spfli.

START-OF-SELECTION.

  DATA: o_cell TYPE REF TO if_ixml_element.
  DATA: o_data TYPE REF TO if_ixml_element.

* Tabellenstruktur
  DATA(o_table_descr) = CAST cl_abap_tabledescr( cl_abap_tabledescr=>describe_by_data( it_spfli ) ).

* Struktur einer Tabellenzeile
  DATA(o_struct_descr) = CAST cl_abap_structdescr( o_table_descr->get_table_line_type( ) ).

* Komponenten (Spalten) einer Zeile
  DATA(it_comp_tab) = o_struct_descr->get_components( ).

* Anzahl Spalten
  DATA(lv_colcount) = lines( it_comp_tab ).

* ixml Factory
  DATA(o_ixml) = cl_ixml=>create( ).

* Encoding UTF-8
  DATA(o_encoding) = o_ixml->create_encoding( character_set = 'UTF-8'
                                              byte_order = if_ixml_encoding=>co_none ).

* DOM Object Model
  DATA(o_doc) = o_ixml->create_document( ).

* Processing Instructions setzen
* damit die XML-Datei gleich vom Windows als Excel-XML erkannt und korrekt geöffnet wird
  DATA(o_pi) = o_doc->create_pi_parsed( name = 'mso-application' ).
  o_pi->set_attribute( name = 'progid' value = 'Excel.Sheet' ).
  o_doc->append_child( o_pi ).

* Workbook
  DATA(o_workbook) = o_doc->create_simple_element( name = 'Workbook' parent = o_doc ).
  o_workbook->set_attribute( name = 'xmlns' value ='urn:schemas-microsoft-com:office:spreadsheet' ).
  o_workbook->set_attribute_node( o_doc->create_namespace_decl( name = 'o' prefix = 'xmlns' uri = 'urn:schemas-microsoft-com:office:office') ).
  o_workbook->set_attribute_node( o_doc->create_namespace_decl( name = 'x' prefix = 'xmlns' uri = 'urn:schemas-microsoft-com:office:excel' ) ).
  o_workbook->set_attribute_node( o_doc->create_namespace_decl( name = 'ss' prefix = 'xmlns' uri = 'urn:schemas-microsoft-com:office:spreadsheet') ).
  o_workbook->set_attribute_node( o_doc->create_namespace_decl( name = 'html' prefix = 'xmlns' uri = 'http://www.w3.org/TR/REC-html40') ).

* DocumentProperties
* Zeitstempel erzeugen
  DATA: lv_tsl TYPE timestampl.
  GET TIME STAMP FIELD lv_tsl.
  DATA(lv_created) = |{ substring( val = |{ lv_tsl TIMESTAMP = ISO }| off = 0 len = 19 ) }Z|.

  DATA(o_prop) = o_doc->create_simple_element( name = 'DocumentProperties' parent = o_workbook ).
  o_prop->set_attribute( name = 'xmlns' value ='urn:schemas-microsoft-com:office:office' ).
  o_doc->create_simple_element( name = 'Author' value = gv_user parent = o_prop ).
  o_doc->create_simple_element( name = 'LastAuthor' value = gv_user parent = o_prop ).
  o_doc->create_simple_element( name = 'Created' value = lv_created parent = o_prop ).
  o_doc->create_simple_element( name = 'Company' value = gv_company parent = o_prop ).
  o_doc->create_simple_element( name = 'Version' value = '14.00' parent = o_prop ).

* OfficeDocumentSettings
  DATA(o_set) = o_doc->create_simple_element( name = 'OfficeDocumentSettings' parent = o_workbook ).
  o_set->set_attribute( name = 'xmlns' value ='urn:schemas-microsoft-com:office:office' ).
  o_doc->create_simple_element( name = 'AllowPNG' parent = o_set ).

* ExcelWorkbook
  DATA(o_excelwb) = o_doc->create_simple_element( name = 'ExcelWorkbook' parent = o_workbook ).
  o_excelwb->set_attribute( name = 'xmlns' value ='urn:schemas-microsoft-com:office:excel' ).
  o_doc->create_simple_element( name = 'ProtectStructure' value = 'False' parent = o_excelwb ).
  o_doc->create_simple_element( name = 'ProtectWindows' value = 'False' parent = o_excelwb ).

* Styles
  DATA(o_styles) = o_doc->create_simple_element( name = 'Styles' parent = o_workbook ).

* "Default" Style
  DATA(o_style_default) = o_doc->create_simple_element( name = 'Style' parent = o_styles ).
  o_style_default->set_attribute_ns( name = 'ID' prefix = 'ss' value = 'Default' ).
  o_style_default->set_attribute_ns( name = 'Name' prefix = 'ss' value = 'Normal' ).
  DATA(o_align) = o_doc->create_simple_element( name = 'Alignment' parent = o_style_default ).
  o_align->set_attribute_ns( name = 'Vertical' prefix = 'ss' value = 'Bottom' ).
  DATA(o_borders) = o_doc->create_simple_element( name = 'Borders' parent = o_style_default ).
  DATA(o_font) = o_doc->create_simple_element( name = 'Font' parent = o_style_default ).
  o_font->set_attribute_ns( name = 'FontName' prefix = 'ss' value = 'Arial' ).
  o_font->set_attribute_ns( name = 'Family' prefix = 'x' value = 'Swiss' ).
  o_font->set_attribute_ns( name = 'Size' prefix = 'ss' value = '11' ).
  o_font->set_attribute_ns( name = 'Color' prefix = 'ss' value = '#000000' ).
  DATA(o_interior) = o_doc->create_simple_element( name = 'Interior' parent = o_style_default ).
  DATA(o_numberformat) = o_doc->create_simple_element( name = 'NumberFormat' parent = o_style_default ).
  DATA(o_protection) = o_doc->create_simple_element( name = 'Protection' parent = o_style_default ).

* Bold Style "s62" für die Überschrift
  DATA(o_style_bold) = o_doc->create_simple_element( name = 'Style' parent = o_styles ).
  o_style_bold->set_attribute_ns( name = 'ID' prefix = 'ss' value = 's62' ).
  DATA(o_font_bold) = o_doc->create_simple_element( name = 'Font' parent = o_style_bold ).
  o_font_bold->set_attribute_ns( name = 'FontName' prefix = 'ss' value = 'Arial' ).
  o_font_bold->set_attribute_ns( name = 'Family' prefix = 'x' value = 'Swiss' ).
  o_font_bold->set_attribute_ns( name = 'Size' prefix = 'ss' value = '11' ).
  o_font_bold->set_attribute_ns( name = 'Color' prefix = 'ss' value = '#000000' ).
  o_font_bold->set_attribute_ns( name = 'Bold' prefix = 'ss' value = '1' ).

* Worksheet "SPFLI" einfügen
  DATA(o_sheet1) = o_doc->create_simple_element( name = 'Worksheet' parent = o_workbook ).
  o_sheet1->set_attribute_ns( name = 'Name' prefix = 'ss' value = gv_sheetname ).

* WorksheetOptions
  DATA(o_ws_opt) = o_doc->create_simple_element( name = 'WorksheetOptions' parent = o_sheet1 ).
  o_ws_opt->set_attribute( name = 'xmlns' value = 'urn:schemas-microsoft-com:office:excel' ).

  o_doc->create_simple_element( name = 'Selected' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'ProtectObjects' value = 'False' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'ProtectScenarios' value = 'False' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'SplitHorizontal' value = '1' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'TopRowBottomPane' value = '1' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'FreezePanes' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'FrozenNoSplit' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'Unsynced' parent = o_ws_opt ).
  o_doc->create_simple_element( name = 'ActivePane' value = '2' parent = o_ws_opt ).

* Page Setup
  DATA(o_ps) = o_doc->create_simple_element( name = 'PageSetup' parent = o_ws_opt ).
  DATA(o_ps_header) = o_doc->create_simple_element( name = 'Header' parent = o_ps ).
  o_ps_header->set_attribute_ns( name = 'Margin' prefix = 'x' value = '0.3' ).
  DATA(o_ps_footer) = o_doc->create_simple_element( name = 'Footer' parent = o_ps ).
  o_ps_footer->set_attribute_ns( name = 'Margin' prefix = 'x' value = '0.3' ).
  DATA(o_ps_margins) = o_doc->create_simple_element( name = 'PageMargins' parent = o_ps ).
  o_ps_margins->set_attribute_ns( name = 'Bottom' prefix = 'x' value = '0.7' ).
  o_ps_margins->set_attribute_ns( name = 'Left' prefix = 'x' value = '0.7' ).
  o_ps_margins->set_attribute_ns( name = 'Right' prefix = 'x' value = '0.7' ).
  o_ps_margins->set_attribute_ns( name = 'Top' prefix = 'x' value = '0.7' ).

* Splitter für Header / Data definieren
  o_doc->create_simple_element( name = 'x:Selected' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:FreezePanes' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:FrozenNoSplit' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:Unsynced' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:ProtectObjects' value = 'False' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:ProtectScenarios' value = 'False' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:SplitHorizontal' value = '1' parent = o_ps ).
  o_doc->create_simple_element( name = 'x:TopRowBottomPane' value = '1' parent = o_ps ).

* Named Range für Filter definieren
  DATA(o_names) = o_doc->create_simple_element( name = 'Names' parent = o_sheet1 ).
  DATA(o_named_range) = o_doc->create_simple_element( name = 'NamedRange' parent = o_names ).
  o_named_range->set_attribute_ns( name = 'Name' prefix = 'ss' value = '_FilterDatabase' ).
  o_named_range->set_attribute_ns( name = 'RefersTo' prefix = 'ss' value = |=Sheet1!R1C1:R1C{ lv_colcount }| ). " Spalte 1-x als Range "_FilterDatabase" definieren
  o_named_range->set_attribute_ns( name = 'Hidden' prefix = 'ss' value = '1' ).

* Autofilter aktivieren
  DATA(o_autofilter) = o_doc->create_simple_element( name = 'AutoFilter' parent = o_sheet1 ).
  o_autofilter->set_attribute_ns( name = 'Range' prefix = 'x' value = |R1C1:R1C{ lv_colcount }| ).              " Spalte 1-x als Filter-Range definieren
  o_autofilter->set_attribute_ns( name = 'xmlns' value = 'urn:schemas-microsoft-com:office:excel' ).

* Table für Header und Daten
  DATA(o_table) = o_doc->create_simple_element( name = 'Table' parent = o_sheet1 ).
  o_table->set_attribute_ns( name = 'FullColumns' prefix = 'x' value = '1' ).
  o_table->set_attribute_ns( name = 'FullRows' prefix = 'x' value = '1' ).
  o_table->set_attribute_ns( name = 'DefaultColumnWidth' prefix = 'x' value = '66' ).
  o_table->set_attribute_ns( name = 'DefaultRowHeight' prefix = 'x' value = '14.25' ).
  o_table->set_attribute_ns( name = 'DefaultColumnWidth' prefix = 'ss' value = '66' ).
  o_table->set_attribute_ns( name = 'DefaultRowHeight' prefix = 'ss' value = '14.25' ).

* Header (Tabellenüberschriften) einfügen
  DATA(o_hrow) = o_doc->create_simple_element( name = 'Row' parent = o_table ).

  LOOP AT it_comp_tab ASSIGNING FIELD-SYMBOL(<fs_comp>).
    o_cell = o_doc->create_simple_element( name = 'Cell' parent = o_hrow ).
    o_cell->set_attribute_ns( name = 'StyleID' prefix = 'ss' value = 's62' ). " Style Bold
* nur elementare Datentypen zulassen
    IF <fs_comp>-type->kind = cl_abap_typedescr=>kind_elem.
      o_doc->create_simple_element( name = 'Data' value = <fs_comp>-name parent = o_cell )->set_attribute_ns( name = 'Type' prefix = 'ss' value = 'String' ).
    ELSE.
* alle anderen Spalten mit Strukturen, INCLUDES usw. einfach als DEEP kennzeichnen, Inhalt bleibt leer
      o_doc->create_simple_element( name = 'Data' value = 'DEEP' parent = o_cell )->set_attribute_ns( name = 'Type' prefix = 'ss' value = 'String' ).
    ENDIF.
* Named Range "_FilterDatabase" für Filter setzen
    o_doc->create_simple_element( name = 'NamedCell' parent = o_cell )->set_attribute_ns( name = 'Name' prefix = 'ss' value = '_FilterDatabase' ).
  ENDLOOP.

* Daten einfügen
  LOOP AT it_spfli ASSIGNING FIELD-SYMBOL(<fs_line>).
    DATA(o_row_data) = o_doc->create_simple_element( name = 'Row' parent = o_table ).

    o_struct_descr ?= cl_abap_structdescr=>describe_by_data( <fs_line> ).

    LOOP AT o_struct_descr->get_components( ) ASSIGNING FIELD-SYMBOL(<fs_comp2>).

      o_cell = o_doc->create_simple_element( name = 'Cell' parent = o_row_data ).

* nur elementare Datentypen zulassen, andernfalls gibts Exceptions bei der String-Konvertierung
* die Spalten mit kennzeichnung "DEEP" bleiben in der Ausgabe somit leer
      IF <fs_comp2>-type->kind = cl_abap_typedescr=>kind_elem.
        ASSIGN COMPONENT <fs_comp2>-name OF STRUCTURE <fs_line> TO FIELD-SYMBOL(<fs_cell>).

        IF <fs_cell> IS ASSIGNED.
          DATA: lv_data_type TYPE string.
          DATA(lv_typekind) = cl_abap_elemdescr=>get_data_type_kind( <fs_cell> ).

          CASE lv_typekind.
            WHEN cl_abap_typedescr=>typekind_time OR cl_abap_typedescr=>typekind_date OR cl_abap_typedescr=>typekind_string OR cl_abap_typedescr=>typekind_char.
              lv_data_type = 'String'.
            WHEN cl_abap_typedescr=>typekind_num OR cl_abap_typedescr=>typekind_packed OR cl_abap_typedescr=>typekind_int OR cl_abap_typedescr=>typekind_float.
              lv_data_type = 'Number'.
            WHEN OTHERS.
              lv_data_type = 'String'.
          ENDCASE.

          o_data = o_doc->create_simple_element( name = 'Data' value = |{ <fs_cell> }| parent = o_cell ).
          o_data->set_attribute_ns( name = 'Type' prefix = 'ss' value = lv_data_type ).
        ENDIF.
      ENDIF.

    ENDLOOP.
  ENDLOOP.

* XML-Dokument rendern
  DATA(o_sf) = o_ixml->create_stream_factory( ).

  DATA: lv_xml TYPE string.

  DATA(o_stream) = o_sf->create_ostream_cstring( lv_xml ).
  o_stream->set_encoding( encoding = o_encoding ).
  o_stream->set_pretty_print( pretty_print = abap_true ).

  DATA(o_render) = o_ixml->create_renderer( ostream  = o_stream
                                            document = o_doc ).

* XML-String in lv_xml generieren
  DATA(lv_rc) = o_render->render( ).

* Dateigröße in Bytes
  DATA(lv_size) = o_stream->get_num_written_raw( ).

* Stream schließen
  o_stream->close( ).

  IF lv_rc = 0 AND lv_size > 0.
* XML-String in XML-Document wandeln
    DATA(o_xml_doc) = NEW cl_xml_document( ).
    o_xml_doc->parse_string( lv_xml ).

* Ausgabe-Datei, "\" müssen quotiert werden
    DATA(lv_fullpath) = |c:\\temp\\test.xml|.

* XML-Document als Datei speichern
    o_xml_doc->export_to_file( CONV #( lv_fullpath ) ).

* lokale Datei im Excel aufrufen
* parameter muss wegen möglicher Leerzeichen im Pfad mit "" quotiert werden
    cl_gui_frontend_services=>execute( application = 'excel.exe'
                                       parameter = |"{ lv_fullpath }"| ).
  ELSE.
    WRITE: / 'Fehler beim Erzeugen der XML-Datei.'.
  ENDIF.

[ABAP] XML-Daten in eine Datei (*.xml) schreiben

Variante 1 (lokal über XML-String)

* ixml Factory
  DATA(o_ixml) = cl_ixml=>create( ).

* Encoding UTF-8
  DATA(o_encoding) = o_ixml->create_encoding( character_set = 'UTF-8'
                                              byte_order = if_ixml_encoding=>co_none ).

* DOM Object Model
  DATA(o_doc) = o_ixml->create_document( ).

  ...

* XML-Dokument rendern
  DATA(o_sf) = o_ixml->create_stream_factory( ).

* XML-String
  DATA: lv_xml TYPE string.

* Stream für Ausgabe in XML-String
  DATA(o_stream) = o_sf->create_ostream_cstring( lv_xml ).
  o_stream->set_encoding( encoding = o_encoding ).
  o_stream->set_pretty_print( pretty_print = abap_true ).

* Render-Objekt
  DATA(o_render) = o_ixml->create_renderer( ostream  = o_stream
                                            document = o_doc ).

* XML-String in lv_xml generieren
  DATA(lv_rc) = o_render->render( ).

* Dateigröße in Bytes
  DATA(lv_size) = o_stream->get_num_written_raw( ).
  
* Stream schließen
  o_stream->close( ).

* Wenn alles ok
  IF lv_rc = 0 AND lv_size > 0.
  * XML-String in XML-Document wandeln
    DATA(o_xml_doc) = NEW cl_xml_document( ).
    o_xml_doc->parse_string( lv_xml ).

  * XML-Document als Datei speichern
    o_xml_doc->export_to_file( 'c:\temp\test.xml' ).
  ENDIF.

Variante 2 (lokal über XML-iTab)

* ixml Factory
  DATA(o_ixml) = cl_ixml=>create( ).

* Encoding UTF-8
  DATA(o_encoding) = o_ixml->create_encoding( character_set = 'UTF-8'
                                              byte_order = if_ixml_encoding=>co_none ).

* DOM Object Model
  DATA(o_doc) = o_ixml->create_document( ).

  ...

* XML-Dokument rendern
  DATA(o_sf) = o_ixml->create_stream_factory( ).

* XML-iTab
  DATA: it_xml_out TYPE STANDARD TABLE OF char255 WITH DEFAULT KEY.

* Encoding UTF-8
  DATA(o_encoding) = o_ixml->create_encoding( character_set = 'UTF-8'
                                              byte_order = if_ixml_encoding=>co_none ).

* Stream für Ausgabe in XML-iTab
  DATA(o_stream) = o_sf->create_ostream_itable( table = it_xml_out ).
  o_stream->set_encoding( encoding = o_encoding ).

* Render-Objekt
  DATA(o_render) = o_ixml->create_renderer( ostream  = o_stream
                                            document = o_doc ).

  o_render->set_normalizing( is_normalizing = space ).

* XML-String in it_xml_out generieren
  DATA(lv_rc) = o_render->render( ).

* Dateigröße in Bytes
  DATA(lv_size) = o_stream->get_num_written_raw( ).
  
* Stream schließen
  o_stream->close( ).

* Wenn alles ok
  IF lv_rc = 0 AND lv_size > 0.
* Tabelle als Binär-Daten herunterladen
    cl_gui_frontend_services=>gui_download( EXPORTING
                                              filename = 'c:\temp\test.xml'
                                              filetype = 'BIN'
                                              bin_filesize = lv_size
                                            CHANGING
                                              data_tab = it_xml_out ).
  ENDIF.

Variante 3 (Applikationsserver)

* ixml Factory
  DATA(o_ixml) = cl_ixml=>create( ).

* DOM Object Model
  DATA(o_doc) = o_ixml->create_document( ).
  
  ...
  
* XML-Dokument rendern
  DATA(o_sf) = o_ixml->create_stream_factory( ).

* Encoding UTF-8
  DATA(o_encoding) = o_ixml->create_encoding( character_set = 'UTF-8'
                                              byte_order = if_ixml_encoding=>co_none ).

* Stream für Ausgabe in Datei '/tmp/test/test.xml' auf dem Applikationsserver
  DATA(o_stream) = o_sf->create_ostream_uri( system_id = '/tmp/test/test.xml' ).
  o_stream->set_encoding( encoding = o_encoding ).
  o_stream->set_pretty_print( pretty_print = abap_true ).

* Render-Objekt
  DATA(o_render) = o_ixml->create_renderer( ostream  = o_stream
                                            document = o_doc ).

* XML-String in Datei auf Applikationsserver generieren
  DATA(lv_rc) = o_render->render( ).

* Dateigröße in Bytes
  DATA(lv_size) = o_stream->get_num_written_raw( ).
  
* Stream schließen
  o_stream->close( ).

* Wenn alles ok
  IF lv_rc = 0 AND lv_size > 0.
    ...
  ENDIF.

[ABAP] XML-Dokument aufbauen / erstellen und rendern

TRY.
* Interface
    DATA(o_ixml) = cl_ixml=>create( ).

* Document
    DATA(o_doc) = o_ixml->create_document( ).

* Root definieren
    DATA(o_root) = o_doc->create_simple_element_ns( prefix = 'asx'
                                                    name = 'list'
                                                    parent = o_doc ).

* zwei Root-Element-Attribute hinzufügen
    o_root->set_attribute_ns( name =  'asx'
                              prefix = 'xmlns'
                              value = 'http://www.google.com/xml' ).

    o_root->set_attribute_ns( name =  'version'
                              value = '1.0' ).

* Node1 unterhalb Root definieren
    DATA(o_node1) = o_doc->create_simple_element_ns( prefix = 'asx'
                                                     name = 'names'
                                                     parent = o_root ).

* Node2 zur Node1 hinzufügen
    DATA(o_node2) = o_doc->create_simple_element_ns( name = 'name'
                                                     value = 'ABC'
                                                     parent = o_node1  ).

* XML-String generieren
    DATA: xml TYPE string.

    DATA(o_stream) = o_ixml->create_stream_factory( )->create_ostream_cstring( xml ).
    o_ixml->create_renderer( document = o_doc
                             ostream = o_stream )->render( ).

* Anzeige im Browser
    cl_abap_browser=>show_xml( xml_string = xml
                               title      = 'Test XML'
                               size       = cl_abap_browser=>large ).

  CATCH cx_root INTO DATA(e_text).
    WRITE: / e_text->get_text( ).
ENDTRY.

[ABAP] Email mit PDF-Anhang versenden

* https://www.sapnuts.com/tutorials/Sending-email-with-attachment-in-SAP-ABAP/singlepage.html
* http://www.sapwiki.cl/wiki/index.php?title=SAP_ABAP_CORREO
* http://www.berater-wiki.de/index.php?title=PDF_per_Mail/Fax_verschicken
* https://wiki.scn.sap.com/wiki/display/ABAP/Sending+Mails+-+Home+Page?original_fqdn=wiki.sdn.sap.com#SendingMails-HomePage-CL_BCS
* http://www.apentia-forum.de/viewtopic.php?f=2&t=74925&start=0

PARAMETERS: p_email TYPE adr6-smtp_addr OBLIGATORY DEFAULT 'email@email.com'.     " Email-Adr. des Empfängers
PARAMETERS: p_subj TYPE string DEFAULT 'Testmail.'.                               " Email-Subject
PARAMETERS: p_bdytxt TYPE soli DEFAULT 'Testtext.'.                               " eine Mailtext-Zeile
PARAMETERS: p_pdf TYPE file_table-filename OBLIGATORY DEFAULT 'C:\Temp\Test.pdf'. " PDF-Anhang

* wenn die F4-Hilfe für den Dateinamen aufgerufen wird
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_pdf.
  DATA: lv_rc TYPE i.
  DATA: it_files TYPE filetable.
  DATA: lv_action TYPE i.

* File-Tabelle leeren, da hier noch alte Einträge von vorherigen
* Aufrufen drin stehen können
  CLEAR: it_files.
* FileOpen-Dialog aufrufen
  TRY.
      cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                    file_filter = |PDF (*.pdf)\|*.pdf\|{ cl_gui_frontend_services=>filetype_all }|
                                                  CHANGING
                                                    file_table  = it_files     " ausgewählte Dateien
                                                    rc          = lv_rc        " Anzahl ausgewählte Dateien, -1 bei Fehler
                                                    user_action = lv_action ).

      IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn Datei ausgewählt wurde
        IF lines( it_files ) > 0.
* ersten Tabelleneintrag lesen
          p_pdf = it_files[ 1 ]-filename.
        ENDIF.
      ENDIF.
    CATCH cx_root INTO DATA(e_text).
      MESSAGE e_text->get_text( ) TYPE 'S'.
  ENDTRY.

START-OF-SELECTION.
  DATA: lv_pdf_size TYPE i.
  DATA: it_pdf_data_tab TYPE solix_tab.

  TRY.
* PDF-Datei vom lokalen Rechner auf den Appl.-Server hochladen
      cl_gui_frontend_services=>gui_upload( EXPORTING
                                              filename   = |{ p_pdf }|
                                              filetype   = 'BIN'
                                            IMPORTING
                                              filelength = lv_pdf_size
                                            CHANGING
                                              data_tab   = it_pdf_data_tab ).

      IF sy-subrc = 0.
* it_body_txt beinhaltet die Zeilen des Body-Textes
* die Zeilen sind jeweils char255 Zeichen lang
* eine Zeile (line) ist vom Typ SOLI
        DATA(it_body_txt) = VALUE soli_tab( ( line = p_bdytxt ) ).

* Document (Mail) erzeugen
* Email-Subject (i_subject) darf hier max. 50 Zeichen lang sein (Subject-Darstellung in der SOST)
* die "richtige" Übergabe des Email-Subjects erfolgt weiter unten in der Funktion set_message_subject( )
        DATA(o_document) = cl_document_bcs=>create_document( i_type    = 'RAW'
                                                             i_text    = it_body_txt
                                                             i_subject = CONV so_obj_des( p_subj ) ).

* Attachement hinzufügen -> Typen siehe Tabelle TSOTD
* siehe auch: http://www.apentia-forum.de/viewtopic.php?f=2&t=74925&start=0
        o_document->add_attachment( i_attachment_type    = 'EXT'
                                    i_attachment_subject = |{ p_pdf }|
                                    i_att_content_hex    = it_pdf_data_tab ).

* Sendrequest erzeugen
        DATA(o_send_request) = cl_bcs=>create_persistent( ).
* Email-Subject festlegen, ip_subject ist vom Typ String
        o_send_request->set_message_subject( ip_subject = p_subj ).
* Die Mail an den Sendrequest hängen
        o_send_request->set_document( o_document ).

* Sender
* SAP-User als Sender
        DATA(o_sender) = cl_sapuser_bcs=>create( sy-uname ).
* Email-Adresse als Sender
*        DATA(o_sender) = cl_cam_address_bcs=>create_internet_address( i_address_string = 'email@email.com' ).
        o_send_request->set_sender( o_sender ).

* Empfänger -> beachte: u.U. sind Email-Adressen mit Umlauten nicht zulässig -> Exception
        DATA(o_recipient) = cl_cam_address_bcs=>create_internet_address( p_email ).
        o_send_request->add_recipient( i_recipient = o_recipient
                                       i_express = abap_true
                                     " i_copy      = abap_true " CC
                                     " i_blind_copy = abap_true " BCC
                                     ).

* Sofort senden
        o_send_request->set_send_immediately( abap_true ).

* Dokument senden
        IF o_send_request->send( i_with_error_screen = abap_true ) = abap_true.
          WRITE: / 'Email gesendet.'.
        ELSE.
          WRITE: / 'Fehler beim Email-Versand.'.
        ENDIF.

* Persistenzdienste des Objektes starten, sonst wird ggf. die Mail nicht gesendet
        COMMIT WORK.

      ENDIF.
    CATCH cx_root INTO DATA(e_text).
      WRITE: / e_text->get_text( ).
  ENDTRY.