[SAP ABAP] Import and Export document Part 2

Working with Microsoft Office files

Every Windows system user has heard of the Microsoft Office package. Microsoft Word or Microsoft Excel formats are the most recognizable and characteristic extensions for Windows operating systems. The Office package is also very well integrated with SAP. We have the possibility to export the result to an Excel spreadsheet in many standard transactions. Exporting to Excel is also part of standard ABAP List Viewer (ALV) functionality. In this section, we dive deeper into SAP and Microsoft integration. Reading, writing, and editing examples will be covered. 

Reading data from Microsoft Excel

Let's assume some external system created a report in Excel format. We need to create an ABAP program that is capable of reading this file. Create a new program, ZMSA_R_CHAPTER4_5, and copy the report pattern from Appendix AAssessments.
We need to declare variables. The lv_filename will be used to store the filename and file path on our local PC. The variable lt_excel stores values in a special cell-addressing format:
DATA: lv_filename TYPE localfile VALUE 'c:/temp/testfile4_5.xlsx'.
DATA: lt_excel TYPE TABLE OF alsmex_tabline.
For reading Excel files, we have to use the function module alsm_excel_to_internal_table. You can get a sample Excel file from GitHub or create a file yourself in the temp folder. Use a pattern framework to get a function call structure. The call will look like this:
CALL FUNCTION 'ALSM_EXCEL_TO_INTERNAL_TABLE'
      EXPORTING
        filename =
        i_begin_col =
        i_begin_row =
        i_end_col =
        i_end_row =
      TABLES
        intern =
* EXCEPTIONS
* INCONSISTENT_PARAMETERS = 1
* UPLOAD_OLE = 2
* OTHERS = 3
              .
    IF sy-subrc <> 0.
* Implement suitable error handling here
    ENDIF.
Set i_end_col and i_end_row to 1000. These two parameters have a limitation, which can vary depending on your local system and server configuration. Also remember that indexation starts from 1, so the parameters i_begin_col and i_begin_row have to be set at least to 1. If you open  alms_excel_to_internal_table and analyze the code behind it, you will find two important things. This function is just wrapper to an Object Linking and Embedding (OLE) mechanism, which we will use in all other examples. It's also interesting that this function uses some strange tricks. The Excel file is opened in the background and all cells are selected. Then we use the Copy to clipboard mechanism, and the algorithm reads values from the clipboard and assigns the values of individual cells to the ABAP table. This has some limitations, for example, a cell will be copied with its display value and not the real value.
Add the display_data method from cl_demo_output:
cl_demo_output=>display_data( lt_excel ).

Saving a table to a Microsoft Excel file

The common business case for using OLE is to generate custom-format Excel files generated in an ABAP report. Please create a new ABAP program, ZMSA_R_CHAPTER4_6, and copy the report pattern from Appendix AAssessments. If you checked the alms_excel_to_internal_table function module, you should know how OLE works. It's important to include just before our class definition OLE type information:
INCLUDE ole2incl.






We need to declare the following variables: 
DATA: lv_filename TYPE localfile VALUE 'c:\temp\testfile4_6.xls'.
DATA: lo_excel TYPE ole2_object.
DATA: lo_workbook TYPE ole2_object.
DATA: lo_sheet TYPE ole2_object.                      
DATA: lo_cell TYPE ole2_object.
For some systems, to make this example work, instead of using (/) in the path name, you have to use (\) . This is only a problem if a path is hardcoded. Now we need to do some basic setup:
CREATE OBJECT lo_excel 'EXCEL.APPLICATION'.
SET PROPERTY OF lo_excel 'visible' = 1.

CALL METHOD OF lo_excel 'Workbooks' = lo_workbook.
CALL METHOD OF lo_workbook 'Add'.

CALL METHOD OF lo_excel 'Worksheets' = lo_sheet
                                 EXPORTING #1 = 1.
CALL METHOD OF lo_sheet 'Activate'.
SET PROPERTY OF lo_sheet 'Name' = 'TestSheet'.
This will initialize both lo_workbook and lo_sheetlo_workbook represents our entire workbook and lo_sheet is the equivalent of our single spreadsheet. You can also change the name using the 'NAME' property on the sheet object. If you run this program, Excel will be opened on your local desktop. If you change the 'visible' parameter to 0, the process will be run in the background.
Now we want to put some information into Excel. To do this, we will get a lo_cell object from lo_sheet and set the 'Value' property:
DO 10 TIMES.
    CALL METHOD OF lo_sheet 'Cells' = lo_cell EXPORTING #1 = sy-index  #2 = 1.
    SET PROPERTY OF lo_cell 'Value' = sy-index.
ENDDO.
We have two exporting parameters here; the first is for the row and the second is for a column. We will put the value of the current index in individual cells. The last thing to do is save the file and close the Excel OLE objects:
    CALL METHOD OF lo_sheet 'SaveAs'
      EXPORTING
        #1 = lv_filename
        #2 = 1.

    SET PROPERTY OF lo_excel 'visible' = 0.

    CALL METHOD OF lo_sheet 'CLOSE'
      EXPORTING
        #1 = 'YES'.

    CALL METHOD OF lo_excel 'QUIT'.

    FREE OBJECT: lo_excel,
                 lo_sheet.
If everything went okay, you should have a new Excel file in the C:/temp directory. Go there and verify that the content is correct:

Working with Microsoft Word

In the case of Microsoft Word, we will focus only on writing values to the file due to the fact that reading from a Microsoft Word document is not especially interesting or useful. Word documents can have infinite numbers of layouts. It's much easier to work with structured Excel, where we always expect rows and columns, than with Word, where the table can have any format.
Create a new program, ZMSA_R_CHAPTER4_7, and copy the report pattern from Appendix AAssessments. Add an obligatory INCLUDE statement to the OLE type-pool just before the class definition: 
INCLUDE ole2incl.
Declare a few variables in the main method:
DATA: lo_word TYPE ole2_object.
DATA: lo_doc TYPE ole2_object.

DATA: lo_selection TYPE ole2_object.
DATA: lo_font TYPE ole2_object.
DATA: lo_paragraph TYPE ole2_object.
In the next step, we will initialize and get the handler of all needed objects:
CREATE OBJECT lo_word 'Word.Application'.

CALL METHOD OF lo_word 'Documents' = lo_doc.
CALL METHOD OF lo_doc 'Add'.

GET PROPERTY OF lo_word 'Selection' = lo_selection.
GET PROPERTY OF lo_selection 'ParagraphFormat' = lo_paragraph.
GET PROPERTY OF lo_selection 'Font' = lo_font.
You can also prepare your font and paragraph format, and therefore specify the final layout of the document's content:
SET PROPERTY OF lo_word 'Visible' = 1.
SET PROPERTY OF lo_font 'Size' = 22.
SET PROPERTY OF lo_font 'Bold' = 1.
SET PROPERTY OF lo_paragraph 'Alignment' = 1. " Centered

To write something, you will use the 'TypeText' method on the lo_selecton object. The exporting parameters represent the text you want to put into Word document:
CALL METHOD OF lo_selection 'TypeText'
    EXPORTING
        #1 = 'First Word Report of Airlines with OLE'.

CALL METHOD OF lo_selection 'TypeParagraph'.
To make this example more reasonable, we will select data from a SAP IDES standard data flight model. Let's say we'd like to list every single airline from the current system. We need an additional variable and a new select statement:
 DATA: lt_carrname TYPE TABLE OF s_carrname.
 DATA: lv_carrname TYPE s_carrname.
 SELECT carrname FROM scarr INTO TABLE lt_carrname.
The list of the airlines should be written with a smaller font than the header; because of that, we will change a few properties:
SET PROPERTY OF lo_font 'Size' = 10.
SET PROPERTY OF lo_font 'Bold' = 0.
SET PROPERTY OF lo_paragraph 'Alignment' = 0.    
To write the airline list into Word, we will again use the 'TypeText' method of the lo_selection class:
LOOP AT lt_carrname INTO lv_carrname.

    CALL METHOD OF lo_selection 'TypeText'
        EXPORTING
            #1 = lv_carrname.

    CALL METHOD OF lo_selection 'TypeParagraph'.
ENDLOOP.
If everything went well, you should be able to see the Word document with the airlines report in it:

Using DOI to integrate Microsoft Office applications into ABAP reports

DOI is an ABAP object interface that can be used to work with Office applications using the OLE2 interface. You can use it to edit the Office format, but it can also be used to integrate Office applications into ABAP reports. This could be bi-directional integration, meaning the ABAP report can react to Office events and Office can react based on ABAP logic. DOI significantly extends the possibilities presented in previous chapters carried out using OLE classes.
Using DOI is also much more complicated than using OLE objects, so we will focus on only one example of integrating a Word document into the ABAP program. Forget for a moment about our standard program template and create an empty new program, ZMSA_R_CHAPTER4_8. ABAP and the structure of ABAP programs are not flexible when it comes to programs based on classic Dynpro. Using the object, in this case, is not so easy, and in the end, some fragments would have to be made non-objected anyway. First, create a few variables. We also need to include the soi as a TYPE-POOLS:
TYPE-POOLS: soi.

DATA: lo_container TYPE REF TO cl_gui_custom_container.
DATA: lo_control TYPE REF TO i_oi_container_control.
DATA: lo_proxy TYPE REF TO i_oi_document_proxy.

DATA: lv_okcode TYPE syst_ucomm.
In the next step, we need to create a new screen. This screen will encapsulate our integrated Word application and ABAP-based buttons for interacting with our Word document. To create the screen, right-click on your report name in the navigation bar and choose Create|Screen:

A small popup will appear where you can set a new screen number. You can choose whatever four-digit number you like. When you extend standard SAP reports, it should be in the range of 9000+, but in the case of custom development, it can be anything, such as 0100. Be aware that if you change this number you will also have to remember to change it in other parts of this example:
After clicking the green check mark, you will jump to a new screen where all basic configurations can be set. For this example, only a short description is obligatory:

In the next step, we will create a layout for our screen. To jump into the Screen Editor, you can hit Ctrl + F7 on the keyboard or click on the Layout button on the toolbar. On your new screen, create three objects (one custom container and two buttons). Use the icons on the left to create everything:
It's very important to include the names and function codes (FctCode) as in this example. For BTN_OPEN, use OPEN and for BTN_CLOSE use CLOSE:
Custom containers also need to have reference names, but FctCode is not needed. You can verify names either in the layout object preferences panel or in the Element list tab when you close the Screen Editor. You should also enter an element name for the OK command object:

Go to the Flow logic tab and remove everything:
Put the following code there. It will be needed later to handle screen-processing events:
PROCESS BEFORE OUTPUT.
 MODULE INIT.

PROCESS AFTER INPUT.
 MODULE EXIT AT EXIT-COMMAND.
 MODULE USER_COMMAND_0100.
Activate the screen and go back to your main application. Include some code to handle the three new modules we just created in the Flow logic tab:
MODULE INIT OUTPUT.

ENDMODULE.


MODULE EXIT INPUT.

ENDMODULE.


MODULE user_command_0100 INPUT.

ENDMODULE.
The INIT module will be started just before screen 0100 starts. EXIT and user_command_0100 modules are called every time the user does something on the screen. Now we need to call our screen just at the beginning of program execution. This should be placed just after the data declaration:
SET SCREEN 100.
If you run your program, you should already be able to see your screen:
Nothing will work yet, and two very important parts are missing. We need to create a GUI status or GUI Titles. Right-click on the report name and select Create | Gui Titles:
Choose a custom name and select Title Code:
Create GUI Status:

In the GUI Status popup, fill in a description and number for GUI Status:
After clicking the green check mark, you will be forwarded to a GUI Status setup window. In our case, we need to set up only the STOP code:
Activate this and go back to the main program code. First, we will start by handling the INIT function code. Put the following source code in the INIT module:
SET PF-STATUS '0100'.
SET TITLEBAR '0100'.
This will set the earlier-created GUI Status and GUI Titlebar. Move one and add the following calls:
 CALL METHOD c_oi_container_control_creator=>get_container_control
    IMPORTING
      control = lo_control.

  CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.
This will initialize the lo_control object. The method show_message can help you in case of any troubleshooting. Now add the following code:
  CREATE OBJECT lo_container
    EXPORTING
      container_name = 'CUSTOMCONTAINER'.
  CALL METHOD lo_container->set_visible EXPORTING visible = abap_false.


  CALL METHOD lo_control->init_control
    EXPORTING
      r3_application_name = 'R/3 Basis'
      inplace_enabled = abap_true
      inplace_scroll_documents = abap_true
      parent = lo_container
      register_on_close_event = abap_true
      register_on_custom_event = abap_true
      no_flush = abap_false.

  CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.
Finally, initialize the document proxy object:
  CALL METHOD lo_control->get_document_proxy
    EXPORTING
      document_type = 'Word.Document.8'
      no_flush = abap_false
    IMPORTING
      document_proxy = lo_proxy.

  CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.
One last thing is missing in the INIT module. We need to create mechanisms that will avoid creating and initializing objects after every user action. For this, we will create a global variable. We can also create a second global variable that will be useful for us later:
DATA: lv_init TYPE boolean.
DATA: lv_closed TYPE i.
Use this variable in the INIT module. The first line of the following coding should be put at the beginning of the module and the second at the end:
  CHECK lv_init = abap_false.
  lv_init = abap_true.
So far, so good. Now we need to add buttons logic. We have two actions, OPEN and CLOSECLOSE will also save a document into a database table. Put the following code in the USER_COMMAND_0100 module:
  CASE lv_okcode.

    WHEN 'OPEN'.
      CALL METHOD lo_proxy->is_destroyed
        IMPORTING
          ret_value = lv_closed.

      CHECK NOT lv_closed IS INITIAL.

      CALL METHOD lo_container->set_visible
        EXPORTING
          visible = abap_true.

      CALL METHOD lo_proxy->create_document
        EXPORTING
          open_inplace = abap_true
          document_title = 'DOI Test Document' 
          no_flush = abap_false.

      CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.

WHEN'CLOSE'.

  ENDCASE.
  CLEAR: lv_okcode.
The first method checks if a document proxy exists; we don't want to create it again in such a case. We will also make our container visible. The final step, the create_document method, creates an actual document. The parameter open_inplace determines whether the Word application should be opened embedded in the GUI or as a new window. If you execute your program now and click on the Open button, you will see the Word application:

From a functional perspective, our program is still missing two things—saving and reopening the document. First, create three additional variables:
DATA: lv_changed TYPE i.

TYPES: ty_row TYPE x LENGTH 2048.
DATA: lt_doc_table TYPE STANDARD TABLE OF ty_row.
DATA: lv_doc_size TYPE i.
Now add the following code into the USER_COMMAND_0100 module; this module concerns the CLOSE command in the CASE statement:
      CALL METHOD lo_proxy->is_destroyed
        IMPORTING
          ret_value = lv_closed.

      IF lv_closed IS INITIAL.
        CALL METHOD lo_proxy->close_document
          EXPORTING
            do_save = abap_true
          IMPORTING
            has_changed = lv_changed.

        CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.

        IF NOT lv_changed IS INITIAL.
          CALL METHOD lo_proxy->save_document_to_table
            CHANGING
              document_table = lt_doc_table
              document_size = lv_doc_size.
          CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.
        ENDIF.

        CALL METHOD lo_proxy->release_document.
        CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.
      ENDIF.

      CALL METHOD lo_container->set_visible EXPORTING visible = abap_false.
The is_destoryed method checks whether our document exists or not. We cannot save or release a document if it doesn't exist. save_document_to_table saves document contents into a local ABAP table. The call of the method set_visible hides the container. We can already open and close the document, but now we need to take care of reopening it. We need to delete everything from the open part of the USER_COMMAND_0100 module and paste the following:
      CALL METHOD lo_proxy->is_destroyed
        IMPORTING
          ret_value = lv_closed.

      CHECK NOT lv_closed IS INITIAL.
      CALL METHOD lo_container->set_visible
        EXPORTING
          visible = abap_true.

      IF lv_doc_size > 0.

        CALL METHOD lo_proxy->open_document_from_table
          EXPORTING
            document_table = lt_doc_table
            document_size = lv_doc_size
            document_title = 'DOI Test Document'
            open_inplace = abap_true.
      ELSE.


        CALL METHOD lo_proxy->create_document
          EXPORTING
            open_inplace = abap_true
            document_title = 'DOI Test Document'
            no_flush = abap_false.

      ENDIF.
      CALL METHOD c_oi_errors=>show_message EXPORTING type = 'E'.


Some parts of the code are the same as earlier. The new part concerns a condition statement, where we check whether the size of a closed document is not initial. If not, we will open the previously—saved document. Please test it by clicking Open, writing something, closing the document, and clicking Open again:

The last formality is to create a mechanism to turn off the program. Copy and paste the following code into the EXIT module. This will release all memory, close all objects, and exit the program:
  CASE lv_okcode.
    WHEN 'STOP'.
      IF NOT lo_proxy IS INITIAL.
        CALL METHOD lo_proxy->close_document.
        FREE lo_proxy.
      ENDIF.
      IF NOT lo_control IS INITIAL.
        CALL METHOD lo_control->destroy_control.
        FREE lo_control.
      ENDIF.
      LEAVE PROGRAM.
  ENDCASE.
In this section, we looked at the basic possibilities of DOI. The examples presented here should give you a basic idea of how to work with DOI. Of course, this mechanism opens up a wide range of different possibilities and it can handle a lot more sophisticated business requirements. 

Comments

Popular posts from this blog

[SAP ABAP] Data declaration

[SAP ABAP] BAPI

[SAP HR] Payroll Schema