Quantcast
Channel: SCN : Blog List - ABAP Development
Viewing all articles
Browse latest Browse all 943

Simple PPTX export with template

$
0
0

The idea behind this trivial solution is the possibility to export text data from an SAP system into PowerPoint format using a given template. In my experience, multinational companies usually have a default PowerPoint template for presentations, which includes: the big company logo, strict rules for what should the header and the footer include, and so on. These are particularly important for status update presentations in different areas.

Although I had this requirement in the PPM sector, that is, Portfolio and Project Management, I chose to post this in ABAP development as it might be used in other areas as well.

template.png

As a prerequisite, this solution only works beginning with Office 2007 as it requires Office Open XML. Therefore, the iXML Library must be available on the SAP Application Server.

 

The following steps need to be performed in order to achieve a first test:

1. Create a demo template.

It is mandatory to set a custom property for each of the slides in the presentation, with:

name = "&&slide*&&"

type = "text"

value = "slide*"

(where * represents the slide number)

Advanced Properties.png

2. Create an ABAP/4 class with the following source-code or use the one attached bellow:

 

class ZCL_PPTX definition
  public
  create public .

public section.
*"* public components of class ZCL_PPTX
*"* do not include other source files here!!!

  constants MC_SCHEMA type STRING value 'http://schemas.openxmlformats.org/drawingml/2006/main'. "#EC NOTEXT

  methods CONSTRUCTOR
    importing
      !IM_V_CONTENT type XSTRING
    raising
      ZCX_PPTX .
  methods GENERATE
    importing
      !IM_T_TEXTS type ZXFILE_T_NAME_VALUE_PAIR
    returning
      value(RE_V_FILE) type XSTRING .
protected section.
*"* protected components of class ZCL_PPTX
*"* do not include other source files here!!!

  data MO_ZIP type ref to CL_ABAP_ZIP .
  data MO_IXML type ref to IF_IXML .
  data MO_IXML_DOCPROPS_CUSTOM type ref to IF_IXML_DOCUMENT .

  methods GET_FILE
    importing
      !IM_V_FILEPATH type STRING
    returning
      value(RE_O_DOCUMENT) type ref to IF_IXML_DOCUMENT .
  methods UPDATE_FILE
    importing
      !IM_V_FILEPATH type STRING
      !IM_O_DOCUMENT type ref to IF_IXML_DOCUMENT .
  methods ADD_FILE
    importing
      !IM_V_FILEPATH type STRING
      !IM_O_DOCUMENT type ref to IF_IXML_DOCUMENT .
  methods UPDATE_TEXTS
    importing
      !IM_T_TEXTS type ZXFILE_T_NAME_VALUE_PAIR
      !IM_O_IXML_NODE type ref to IF_IXML_NODE .
  methods GET_INDICATORS
    returning
      value(RE_T_INDICATORS) type ZXFILE_T_NAME_VALUE_PAIR .
private section.
*"* private components of class ZCL_PPTX
*"* do not include other source files here!!!
ENDCLASS.



CLASS ZCL_PPTX IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->ADD_FILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_FILEPATH                  TYPE        STRING
* | [--->] IM_O_DOCUMENT                  TYPE REF TO IF_IXML_DOCUMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD ADD_FILE.

  DATA :
    lo_ostream  TYPE REF TO if_ixml_ostream,
    lv_content  TYPE xstring.

* create output stream
  lo_ostream = mo_ixml->create_stream_factory( )->create_ostream_xstring( string = lv_content ).

* set encoding to UTF-8 (Unicode Transformation Format)
* 8-bit variable-width encoding maximizes compatibility with ASCII
  lo_ostream->set_encoding( encoding = mo_ixml->create_encoding( character_set = 'UTF-8' byte_order = 0 ) ).

* Set Pretty Print
  lo_ostream->set_pretty_print( abap_true ).

* render document
  mo_ixml->create_renderer( ostream = lo_ostream document = im_o_document )->render( ).

* add file
  mo_zip->add( name = im_v_filepath content = lv_content ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_PPTX->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_CONTENT                  TYPE        XSTRING
* | [!CX!] ZCX_PPTX
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD constructor.

  IF im_v_content IS INITIAL.
    TRY.
      RAISE EXCEPTION TYPE zcx_pptx.
    ENDTRY.
  ENDIF.

* get iXML library instance
  mo_ixml = cl_ixml=>create( ).

* load OpenXML document
  CREATE OBJECT mo_zip.
  mo_zip->load(
    EXPORTING
      zip            = im_v_content
    EXCEPTIONS
      zip_parse_error = 1
      OTHERS          = 2 ).

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  mo_ixml_docprops_custom = get_file( im_v_filepath = 'docProps/custom.xml' ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_PPTX->GENERATE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_T_TEXTS                    TYPE        ZXFILE_T_NAME_VALUE_PAIR
* | [<-()] RE_V_FILE                      TYPE        XSTRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD generate.

  CLEAR : re_v_file.

  DATA :
    lo_ixml_document      TYPE REF TO if_ixml_document,
    lo_ixml_document_rels TYPE REF TO if_ixml_document,
    lt_indicators        TYPE zxfile_t_name_value_pair,
    lt_files              TYPE cl_abap_zip=>t_files,
    lv_dummy              TYPE string,                      "#EC NEEDED
    lv_slide              TYPE string,
    lv_value              TYPE string,
    lv_filename          TYPE string,
    lv_filename_rels      TYPE string.

  lt_indicators[] = get_indicators( ).
  lt_files[]      = mo_zip->files[].

  FIELD-SYMBOLS : <fs_file> TYPE cl_abap_zip=>t_file.
  LOOP AT lt_files[] ASSIGNING <fs_file>
                    WHERE name CP 'ppt/slides/slide*.xml'.
    CLEAR:
      lv_dummy,
      lv_slide,
      lv_value,
      lv_filename,
      lv_filename_rels.

    SPLIT <fs_file>-name AT 'ppt/slides/' INTO lv_dummy lv_slide.
    SPLIT lv_slide      AT '.'          INTO lv_value lv_dummy.

    FIELD-SYMBOLS : <fs_indicator> TYPE zxfile_s_name_value_pair.
    READ TABLE lt_indicators[] ASSIGNING <fs_indicator>
                              WITH KEY value = lv_value.
    IF NOT sy-subrc IS INITIAL.
*    file not relevant, process next
      CONTINUE.
    ENDIF.

*  get .xml file
    lo_ixml_document = get_file( im_v_filepath = <fs_file>-name ).

*  get .rels file
    CONCATENATE 'ppt/slides/_rels/' lv_value '.xml.rels' INTO lv_filename_rels.
    lo_ixml_document_rels = get_file( im_v_filepath = lv_filename_rels ).

*  check indicators
    IF NOT <fs_indicator>-name IS INITIAL.
      update_texts( EXPORTING im_t_texts    = im_t_texts[]
                              im_o_ixml_node = lo_ixml_document ).

    ENDIF.

*  trigger update
    lv_filename = <fs_file>-name.
    update_file( im_v_filepath = lv_filename      im_o_document = lo_ixml_document ).
    update_file( im_v_filepath = lv_filename_rels im_o_document = lo_ixml_document_rels ).
  ENDLOOP.

  re_v_file = mo_zip->save( ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->GET_FILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_FILEPATH                  TYPE        STRING
* | [<-()] RE_O_DOCUMENT                  TYPE REF TO IF_IXML_DOCUMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_file.

  DATA :
    lo_stream_factory TYPE REF TO if_ixml_stream_factory,
    lo_istream        TYPE REF TO if_ixml_istream,
    lv_content        TYPE xstring.

  mo_zip->get( EXPORTING  name                    = im_v_filepath
              IMPORTING  content                = lv_content
              EXCEPTIONS zip_index_error        = 1
                          zip_decompression_error = 2
                          OTHERS                  = 3 ).

  IF NOT sy-subrc IS INITIAL.
    RETURN.
  ENDIF.

* create the document
  re_o_document = mo_ixml->create_document( ).

* create the stream factory
  lo_stream_factory = mo_ixml->create_stream_factory( ).

* create the input stream
  lo_istream = lo_stream_factory->create_istream_xstring( lv_content ).

* parse document
  IF NOT mo_ixml->create_parser( document      = re_o_document
                                istream        = lo_istream
                                stream_factory = lo_stream_factory )->parse( ) is INITIAL.
    CLEAR : re_o_document.
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->GET_INDICATORS
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RE_T_INDICATORS                TYPE        ZXFILE_T_NAME_VALUE_PAIR
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD GET_INDICATORS.

  REFRESH : re_t_indicators[].

  DATA :
    lo_ixml_iterator  TYPE REF TO if_ixml_node_iterator,
    lo_ixml_element    TYPE REF TO if_ixml_element,
    ls_indicator      TYPE zxfile_s_name_value_pair.

* get the corresponding entries in the custom .xml to control the generation
  lo_ixml_iterator  = mo_ixml_docprops_custom->get_elements_by_tag_name( name = 'property' )->create_iterator( ).

* get the first element
  lo_ixml_element ?= lo_ixml_iterator->get_next( ).
  WHILE lo_ixml_element IS BOUND.
    CLEAR ls_indicator.
*  get name
    ls_indicator-name = condense( lo_ixml_element->get_attribute( name = 'name' ) ).

    IF ls_indicator-name CP '&&*&&'.
*    get value
      lo_ixml_element    = lo_ixml_element->find_from_name( name = 'lpwstr' namespace = 'vt' ).
      ls_indicator-value = condense( val = lo_ixml_element->get_value( ) ).

*    insert indicator
      INSERT ls_indicator INTO TABLE re_t_indicators[].
    ENDIF.

*  get next element
    lo_ixml_element ?= lo_ixml_iterator->get_next( ).
  ENDWHILE.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->UPDATE_FILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_FILEPATH                  TYPE        STRING
* | [--->] IM_O_DOCUMENT                  TYPE REF TO IF_IXML_DOCUMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD UPDATE_FILE.

  mo_zip->delete( EXPORTING  name            = im_v_filepath
                  EXCEPTIONS zip_index_error = 1
                            OTHERS          = 2 ).

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  IF im_o_document IS BOUND.
    add_file( im_v_filepath = im_v_filepath im_o_document = im_o_document ).
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->UPDATE_TEXTS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_T_TEXTS                    TYPE        ZXFILE_T_NAME_VALUE_PAIR
* | [--->] IM_O_IXML_NODE                TYPE REF TO IF_IXML_NODE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD UPDATE_TEXTS.

  DATA :
    lo_classdescr    TYPE REF TO cl_abap_classdescr,
    lo_exception    TYPE REF TO cx_sy_ref_is_initial,
    lo_ixml_texts    TYPE REF TO if_ixml_node_collection,
    lo_ixml_iterator TYPE REF TO if_ixml_node_iterator,
    lo_ixml_document TYPE REF TO if_ixml_document,
    lo_ixml_element  TYPE REF TO if_ixml_element,
    lo_ixml_node    TYPE REF TO if_ixml_node,
    lv_message      TYPE string,
    lv_text          TYPE string,
    lv_result        TYPE string.

  TRY.
*    determine all texts of the corresponding node
      lo_classdescr ?= cl_abap_classdescr=>describe_by_object_ref( im_o_ixml_node ).
      READ TABLE lo_classdescr->interfaces WITH KEY name = 'IF_IXML_DOCUMENT'
                                          TRANSPORTING NO FIELDS.
      IF sy-subrc IS INITIAL.
*      document
        lo_ixml_document ?= im_o_ixml_node.
        lo_ixml_texts    = lo_ixml_document->get_elements_by_tag_name_ns(
                              name = 't'
                              uri  = mc_schema ).

      ELSE.
        READ TABLE lo_classdescr->interfaces WITH KEY name = 'IF_IXML_ELEMENT'
                                            TRANSPORTING NO FIELDS.
        IF sy-subrc IS INITIAL.
*        element
          lo_ixml_element ?= im_o_ixml_node.
          lo_ixml_texts    = lo_ixml_element->get_elements_by_tag_name_ns(
                              name = 't'
                              uri  = mc_schema ).
        ELSE.
*        current object not supported
          RETURN.
        ENDIF.
      ENDIF.

*    get iterator
      lo_ixml_iterator = lo_ixml_texts->create_iterator( ).

*    get first node
      lo_ixml_node = lo_ixml_iterator->get_next( ).

      WHILE lo_ixml_node IS BOUND.
*      update slide
        lv_text = lo_ixml_node->get_value( ).

*      replace the corresponding text
        FIELD-SYMBOLS : <fs_text> TYPE zxfile_s_name_value_pair.
        LOOP AT im_t_texts[] ASSIGNING <fs_text>.
*        update component
          CONCATENATE '&&' <fs_text>-name '&&' INTO lv_result.
          REPLACE lv_result IN lv_text WITH <fs_text>-value.
        ENDLOOP.

*      set updated text
        lo_ixml_node->set_value( lv_text ).

*      get next node
        lo_ixml_node = lo_ixml_iterator->get_next( ).
      ENDWHILE.
    CATCH cx_sy_ref_is_initial INTO lo_exception.
      IF lo_exception->is_resumable EQ abap_false.
        lv_message = lo_exception->get_text( ).
        MESSAGE lv_message TYPE 'X'.
      ENDIF.
  ENDTRY.

ENDMETHOD.
ENDCLASS.

 

3. Create a demo report with the following source-code or use the one attached bellow:

 

REPORT zdemo_pptx.

DATA :
  gv_data TYPE xstring.

PARAMETERS :
  p_file TYPE localfile.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
  PERFORM open.

START-OF-SELECTION.
  PERFORM upload.
  PERFORM generate.
  PERFORM download.
END-OF-SELECTION.

FORM upload.
  DATA :
    lt_file      TYPE solix_tab,
    lv_filename  TYPE string,
    lv_filelength TYPE i.

  lv_filename = p_file.

  CALL METHOD cl_gui_frontend_services=>gui_upload
    EXPORTING
      filename                = lv_filename
      filetype                = 'BIN'
    IMPORTING
      filelength              = lv_filelength
    CHANGING
      data_tab                = lt_file
    EXCEPTIONS
      file_open_error        = 1
      file_read_error        = 2
      no_batch                = 3
      gui_refuse_filetransfer = 4
      invalid_type            = 5
      no_authority            = 6
      unknown_error          = 7
      bad_data_format        = 8
      header_not_allowed      = 9
      separator_not_allowed  = 10
      header_too_long        = 11
      unknown_dp_error        = 12
      access_denied          = 13
      dp_out_of_memory        = 14
      disk_full              = 15
      dp_timeout              = 16
      not_supported_by_gui    = 17
      error_no_gui            = 18
      OTHERS                  = 19.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
    EXPORTING
      input_length = lv_filelength
    IMPORTING
      buffer      = gv_data
    TABLES
      binary_tab  = lt_file
    EXCEPTIONS
      failed      = 1
      OTHERS      = 2.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.
ENDFORM.

FORM generate.
  DATA :
    lo_pptx  TYPE REF TO zcl_pptx,
    lt_texts TYPE zxfile_t_name_value_pair,
    ls_text  TYPE zxfile_s_name_value_pair.

  TRY.
      CREATE OBJECT lo_pptx
        EXPORTING
          im_v_content = gv_data.
    CATCH zcx_pptx.
      MESSAGE text-001 TYPE 'E'.
  ENDTRY.

  ls_text-name  = 'TITLE'.
  ls_text-value = 'Automatically generated
PowerPoint'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = '
SUBTITLE'.
  ls_text-value = '
using very little coding'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = 'NAME'.
  ls_text-value = '
Your name (Ex. John Dr. Smith)'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = 'DEPARTMENT'.
  ls_text-value = '
Your department name or abbreviation'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = 'DATE'.
  ls_text-value =
sy-datum.
  APPEND ls_text TO lt_texts. CLEAR ls_text.


  CLEAR gv_data.
  gv_data = lo_pptx->generate( lt_texts ).
ENDFORM.

FORM download.
  DATA :
    lt_file      TYPE solix_tab,
    lv_filelength TYPE i,
    lv_filename  TYPE string,
    lv_fullpath  TYPE string,
    lv_path      TYPE string.

  CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
    EXPORTING
      buffer        = gv_data
    IMPORTING
      output_length = lv_filelength
    TABLES
      binary_tab    = lt_file.

  CALL METHOD cl_gui_frontend_services=>file_save_dialog
    CHANGING
      filename                  = lv_filename
      path                      = lv_path
      fullpath                  = lv_fullpath
    EXCEPTIONS
      cntl_error                = 1
      error_no_gui              = 2
      not_supported_by_gui      = 3
      invalid_default_file_name = 4
      OTHERS                    = 5.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  TRANSLATE lv_fullpath TO UPPER CASE.
  IF lv_fullpath NS '.PPTX'.
    CONCATENATE lv_fullpath '.PPTX' INTO lv_fullpath.
  ENDIF.

  CALL METHOD cl_gui_frontend_services=>gui_download
    EXPORTING
      bin_filesize            = lv_filelength
      filename                = lv_fullpath
      filetype                = 'BIN'
    CHANGING
      data_tab                = lt_file
    EXCEPTIONS
      file_write_error        = 1
      no_batch                = 2
      gui_refuse_filetransfer = 3
      invalid_type            = 4
      no_authority            = 5
      unknown_error          = 6
      header_not_allowed      = 7
      separator_not_allowed  = 8
      filesize_not_allowed    = 9
      header_too_long        = 10
      dp_error_create        = 11
      dp_error_send          = 12
      dp_error_write          = 13
      unknown_dp_error        = 14
      access_denied          = 15
      dp_out_of_memory        = 16
      disk_full              = 17
      dp_timeout              = 18
      file_not_found          = 19
      dataprovider_exception  = 20
      control_flush_error    = 21
      not_supported_by_gui    = 22
      error_no_gui            = 23
      OTHERS                  = 24.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.
ENDFORM.

FORM open.
  CALL FUNCTION 'F4_FILENAME'
    EXPORTING
      program_name  = syst-cprog
      dynpro_number = syst-dynnr
      field_name    = space
    IMPORTING
      file_name    = p_file.
ENDFORM.

In the future, based on new requirements, I plan to extend this class to also include tables, new slides, and images. But if you get ahead of me, please feel free to add your code on SCN and give me a hint!

 

Tudor


Viewing all articles
Browse latest Browse all 943

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>