Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
antonsikidin
Participant
There are several ways to generate Microsoft Word docx documents using ABAP + UPDATES

All of them have a number of disadvantages:

  • some require a lot of manual actions and saving structures in the ABAP Dictionary

  • some require the Microsoft Office package installed on the user's computer for their work


The advantage of my development:

  • requires a minimum of manual actions

  • works on the server side (does not require the presence of Microsoft Office)


If you know easier way to generate Microsoft Word docx documents - please, notify me.

Metrics for measuring the simplicity (lower is better):

  • mouse click: single click / double click / select = 2 points

  • keyboard type: one word / tab / enter = 1 point

  • switch application / alt + tab = 3 points

  • + cost of change (add 2 fields + remove 2 fields + rename 2 fields)


Source, readme: https://github.com/AntonSikidin/zcl_docx
Video (watch in 1080p):



Instruction and video complement each other.

Installation Install package via abapGit https://docs.abapgit.org/guide-install.html






For example, the following document should be created:


At first toggle developer toolbar in Microsoft Word.
File -> Options -> Customize ribbon.


Go to developer tab, turn on “design mode”.


Select text that will be replaced.


Make tag.


Enter tag name.


 

Repeat for all variable.

For repeated rows or text fragment - select row or text fragment, make repeated control.


To enter properties of repeated control place cursor in the begin or end control.


Tag all variables and repeated part.

Save document. Go to transaction smw0 Select Binary data, enter.






Go to se38 Program ZDOCX_GET_TYPES.

Navigate to your template.


Run.

Program generate data types, based on your document structure.


Copy to your program.

Define variable.
Data
: gs_templ_data TYPE t_data
.

Fill structure with your data.

Then get document.
zcl_docx3=>get_document(
iv_w3objid = 'ZDOCX_EXAMLE' " name of our template, obligatory
* iv_on_desktop = 'X' " by default save document on desktop
* iv_folder = 'report' " in folder by default 'report'
* iv_path = '' " IF iv_path IS INITIAL save on desctop or sap_tmp folder
* iv_file_name = 'report.docx' " file name by default
* iv_no_execute = '' " if filled -- just get document no run office
* iv_protect = '' " if filled protect document from editing, but not protect from sequence
" ctrl+a, ctrl+c, ctrl+n, ctrl+v, edit
iv_data = gs_templ_data " root of our data, obligatory
* iv_no_save = '' " just get binary data not save on disk
).

Whole program “ZDOCX_EXAMPLE”:
*&---------------------------------------------------------------------*
*& Report ZDOCX_EXAMPLE
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zdocx_example.

*describe types

TYPES
: begin of t_TABLE3
, PERSON type string
, SALARY type string
, end of t_TABLE3

, tt_TABLE3 type table of t_TABLE3 with empty key


, begin of t_T2
, F1 type string
, F2 type string
, F3 type string
, end of t_T2

, tt_T2 type table of t_T2 with empty key


, begin of t_T1
, H1 type string
, T2 type tt_T2
, end of t_T1

, tt_T1 type table of t_T1 with empty key


, begin of t_LINE1
, FIELD1 type string
, FIELD2 type string
, FIELD3 type string
, FIELD4 type string
, end of t_LINE1

, tt_LINE1 type table of t_LINE1 with empty key


, begin of t_TAB1
, TITLE1 type string
, LINE1 type tt_LINE1
, end of t_TAB1

, tt_TAB1 type table of t_TAB1 with empty key


, begin of t_LINE2
, FIELD1 type string
, FIELD2 type string
, FIELD3 type string
, end of t_LINE2

, tt_LINE2 type table of t_LINE2 with empty key


, begin of t_TAB2
, TITLE2 type string
, LINE2 type tt_LINE2
, end of t_TAB2

, tt_TAB2 type table of t_TAB2 with empty key


, begin of t_data
, SH01 type string
, DATE type string
, TIME type string
, USER type string
, TABLE3 type tt_TABLE3
, T1 type tt_T1
, TAB1 type tt_TAB1
, TAB2 type tt_TAB2
, end of t_data

, tt_data type table of t_data with empty key


.



*some variable
DATA
: gs_templ_data TYPE t_data
, lv_index TYPE i
, lv_index2 TYPE i
, lv_index3 TYPE i
.


*fill data

gs_templ_data-DATE = |{ sy-datum date = environment }|.
gs_templ_data-TIME = |{ sy-uzeit TIME = ENVIRONMENT }|.
gs_templ_data-USER = sy-uname.


*1. Lurch Schpellchek: 1200 usd
*2. Russell Sprout: 1300 usd
*3. Fergus Douchebag: 3000 usd
*4. Bartholomew Shoe: 100 usd

APPEND INITIAL LINE TO gs_templ_data-table3 ASSIGNING FIELD-SYMBOL(<ls_3>).
<ls_3>-person = 'Lurch Schpellchek'.
<ls_3>-salary = '1200'.

APPEND INITIAL LINE TO gs_templ_data-table3 ASSIGNING <ls_3>.
<ls_3>-person = 'Russell Sprout'.
<ls_3>-salary = '1300'.

APPEND INITIAL LINE TO gs_templ_data-table3 ASSIGNING <ls_3>.
<ls_3>-person = 'Fergus Douchebag'.
<ls_3>-salary = '3000'.

APPEND INITIAL LINE TO gs_templ_data-table3 ASSIGNING <ls_3>.
<ls_3>-person = 'Bartholomew Shoe'.
<ls_3>-salary = '100'.



gs_templ_data-sh01 = 'test aaa'.

DO 3 TIMES.
lv_index = sy-index.

APPEND INITIAL LINE TO gs_templ_data-tab1 ASSIGNING FIELD-SYMBOL(<ls_tab1>).
<ls_tab1>-title1 = |table 1 subtable { lv_index }|.


DO 3 TIMES.
lv_index2 = sy-index.
APPEND INITIAL LINE TO <ls_tab1>-line1 ASSIGNING FIELD-SYMBOL(<ls_line1>).

DO 4 TIMES.
lv_index3 = sy-index.

ASSIGN COMPONENT lv_index3 OF STRUCTURE <ls_line1> TO FIELD-SYMBOL(<fs_any>).

<fs_any> = |Line { lv_index2 } field { lv_index3 }|.

ENDDO.


ENDDO.


ENDDO.


DO 3 TIMES.
lv_index = sy-index.

APPEND INITIAL LINE TO gs_templ_data-tab2 ASSIGNING FIELD-SYMBOL(<ls_tab2>).
<ls_tab2>-title2 = |Table 2 subtable { lv_index }|.


DO 3 TIMES.
lv_index2 = sy-index.
APPEND INITIAL LINE TO <ls_tab2>-line2 ASSIGNING FIELD-SYMBOL(<ls_line2>).

DO 3 TIMES.
lv_index3 = sy-index.

ASSIGN COMPONENT lv_index3 OF STRUCTURE <ls_line2> TO <fs_any>.

<fs_any> = |Line { lv_index2 } field { lv_index3 }|.

ENDDO.


ENDDO.


ENDDO.

gs_templ_data = VALUE #( BASE gs_templ_data
t1 = VALUE #(
( h1 = |1| t2 = VALUE #(
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
)
)
( h1 = |2| t2 = VALUE #(
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
)
)
( h1 = |3| t2 = VALUE #(
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
( f1 = 'F1' f2 = 'F2' f3 = 'f3' )
)
)
)
).


*get document

DATA
: lv_document TYPE xstring " variable to hold generated document, can be omitted
.

*first case: send document as attachment

lv_document = zcl_docx3=>get_document(
iv_w3objid = 'ZDOCX_EXAMLE'
iv_data = gs_templ_data
iv_no_save = 'X' ).

PERFORM send_document_as_attachment USING lv_document.



*second case: save on desctop and open document

zcl_docx3=>get_document(
iv_w3objid = 'ZDOCX_EXAMLE' " name of our template, obligatory
* iv_on_desktop = 'X' " by default save document on desktop
* iv_folder = 'report' " in folder by default 'report'
* iv_path = '' " IF iv_path IS INITIAL save on desctop or sap_tmp folder
* iv_file_name = 'report.docx' " file name by default
* iv_no_execute = '' " if filled -- just get document no run office
* iv_protect = '' " if filled protect document from editing, but not protect from sequence
" ctrl+a, ctrl+c, ctrl+n, ctrl+v, edit
iv_data = gs_templ_data " root of our data, obligatory
* iv_no_save = '' " just get binary data not save on disk
).


FORM send_document_as_attachment USING iv_doc TYPE xstring.

* implement sending here
MESSAGE 'Doc sended' TYPE 'S'.
ENDFORM.

 

UPD #1: How to interpret \n (new string) as a new line? No way


If you want several line use Repeating Section Сontent Сontrol.


 

Insert field inside table control


 

and make something like:


If you want all lines be in single line - make field inside field. Twise use


 

and make this:


Program:
REPORT  z_multiline_test.


TYPES
: begin of t_TABLE1
, FIELD1 type string
, end of t_TABLE1

, tt_TABLE1 type table of t_TABLE1 with default key

, begin of t_data
, TABLE1 type tt_TABLE1
, end of t_data

, tt_data type table of t_data with default key
.

DATA
: gs_templ_data TYPE t_data
.

FIELD-SYMBOLS
: <fs_line> TYPE t_TABLE1
.

APPEND INITIAL LINE TO gs_templ_data-TABLE1 ASSIGNING <fs_line>.
<fs_line>-FIELD1 = 'asd'.

APPEND INITIAL LINE TO gs_templ_data-TABLE1 ASSIGNING <fs_line>.
<fs_line>-FIELD1 = 'fffff'.

APPEND INITIAL LINE TO gs_templ_data-TABLE1 ASSIGNING <fs_line>.
<fs_line>-FIELD1 = 'hhhhh'.


zcl_docx3=>get_document(
iv_w3objid = 'ZDOCX_EXAMLE_LINE' " name of our template
* iv_template = '' " you can feed template as xstring instead of load from smw0
* iv_on_desktop = 'X' " by default save document on desktop
* iv_folder = 'report' " in folder by default 'report'
* iv_path = '' " IF iv_path IS INITIAL save on desctop or sap_tmp folder
iv_file_name = 'multiline.docx' " file name by default
* iv_no_execute = '' " if filled -- just get document no run office
* iv_protect = '' " if filled protect document from editing, but not protect from sequence
" ctrl+a, ctrl+c, ctrl+n, ctrl+v, edit
iv_data = gs_templ_data " root of our data, obligatory
* iv_no_save = '' " just get binary data not save on disk
).

 

Result:



UPD #2: Types in ABAP Dictionary and Images


Now this development:

  •  works with saved types of your template in ABAP Dictionary, so you can implement “best practice” and “golden rules” of ABAP-programming in your company

  • can dynamically add images (previous version handles only images that were added to your template during creation)


FYI: the last version of abapGit ignores language differences, so you shouldn't have any problems with the original language of ABAP objects.

TYPES. Don’t know why you need manually recreate the types of your template in ABAP Dictionary from generated code instead of Ctrl+C 🡪 Ctrl+V, but if you want - you can do it. But it leads to some limitations - you can't use the table name and structure name that already exist in ABAP Dictionary as tag_name.

For example, if table1, table2, person etc. already existed  in ABAP Dictionary - you can't use it (check it with SE11 or Table DD02L). 


Whatever, when you try to use existing tables or structures as tag_name - program ‘ZDOCX_GET_TYPES’ informs you.


IMAGES. Example - program ‘ZDOCX_IMAGE_TEST’. You can upload bmp, png, jpg. First of all - insert Picture Content Control and name it. 


Next, all generated types for images should be changed on TYPE zst_docx_image. And in zst_docx_image structure, fill in the image  field XSTRING of your picture.
 <fs_line>-img-image = lv_img1. 

Image size of the generated doc has the same size as the template placeholder. But you can specify it using fields ‘cx’ and ‘cy’ (in centimeters). Example - images in line 2, 4, 6, 8 in ‘ZDOCX_IMAGE_TEST’.
    <fs_line>-img-cx = 8.
<fs_line>-img-cy = 8.

Hope these updates will be useful.

--------------------------------------------------------------

Conclusion:

  • Using this development in your daily work you will save your working time.

  • Freed up time you can spend on legacy code refactoring.

  • After reading this post you can think of a way to generate reports better than this.

  • Your boss will notice your progress.

  • You can ask for a bigger salary.

  • Your hair will become clean and silky.

  • Your dog will stop do bad things.

  • You can play the piano again even if you didn’t.

  • People will populate Mars and there will be apple orchards on Mars.

66 Comments