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.
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)
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