Purpose
Generate a Word document

Solution
We use a Word template document stored on the server and perform the conversion from the template document to the new document using ABAP means. For table generation, our template file will contain VBA code and we therefore need a .docm file. To have only individual variables, the process is simpler and we can work with a .docx file.

Overall, the process is sophisticated, as it uses techniques in Word/VBA, ABAP and HTML/JavaScript. Once established, however, you can use any Word templates without having to change your code.

This is what the solution looks like in our S10 demo system:



The individual steps are now described using the example from our S10 demo system.  The tip is divided into the following sections:
  • How to include single variables in the template document (Word)
  • How to include tables in the template (Word incl. VBA)
  • How to generate the Word document from the template (ABAP)
  • How to trigger the generation of the document and the download in the user interface (HTML incl. JavaScript)

How to include single variables in the template document (Word)

Our template file is a rudimentary customer data sheet with the customer address and the contacts, relationships and partner functions:





The document created from the template for the specific customer Nr. 1000354 looks like this:




We will first focus on the individual fields. To insert them into your template, first create a "custom document property" for each field. The "Custom properties" are somewhat hidden in Word; you need to click File->Info->Properties->Advanced Properties:





In the "Advanced Properties", choose the last tab, "Custom". Now enter all the ABAP class attributes that you require for your document. Enter the ABAP field name xxxxx as "Name" and &[XXXXX] as "Value". The property type is "Text":




The ABAP class in our example is "Customer":



With the S10 notation "xxxxx@text" you receive the text for a coded field according to the S10 rules, e.g. the country name "Canada" instead of the country code "CA".

In the Word document, add the fields you need via Quick Parts -> Field -> DocProperty:






If you press the key combination Alt + F9 in Microsoft Word, the field codes are shown (or hidden again) so that you can check whether the property names have been entered correctly:




In this view you might see a MERGEFORMAT option




which is inserted by Word when you tick the "Preserve formatting" checkbox in the docproperty insertion dialog:

 

For our purpose it is not relevant whether MERGEFORMAT is specified or not.

Do not change the font style within a variable as here:



Otherwise the variable will not be replaced:




As long as you assign the style to the whole string, there is no problem. Here, for example, is the ABC classification in red:



Result:





Do not forget to hide the Word field codes before saving the template file. Otherwise, the user will see the field codes instead of the content, as Word saves the status of the field code view in the document:




Finally, a point concerning sub-objects. Let's assume that we also want to display the customer's terms of payment, which are located in ABAP in a sub-object "myknvv" of the "customer" object:







It would be obvious to specify the payment terms in Word as a custom property with the name "myknvv.zterm" or, for the text, "myknvv.zterm@text". This would be correct from the point of view of the S10 Framework, but unfortunately Word does not allow a dot in the name of a custom property.  However, we can use the S10 Framework to help ourselves by defining "zterm" as an attribute of "customer" and specifying a build method that gets it from the "myknvv" sub-object.

Here are the necessary steps to also display the payment conditions and the shipping conditions from the MYKNVV sub-object:


1. Add the two custom properties (Word):



2. Add the placeholders with a suitable label (Word):





3. Add the ABAP class attributes in the "customer" class (ABAP):




4. Add a build-method for the data from MYKNVV (ABAP):






5. Check the result





If you have to work with documents in several languages, it may be useful to also use the SAP standard labels for the fields from the SAP Data Dictionary. The S10 Framework provides these automatically with the "...&label" notation:



The result is (almost) as before:



However, if the user is logged in in French, the document is created with French label texts:





How to include tables in the template (Word incl. VBA)

Please note that to add tables, both the Word template document and the generated document must be .docm files (.docx with macros). If this is not an option for security reasons, you cannot use the table generation procedure via VBA described here. There are other methods, such as using a JavaScript framework for document creation, which will be described in a separate tip.

We use the following procedure to generate the table: The table data is included in the document as raw data in CSV format. When the document is opened in Word, a VBA function contained in the document runs to create the Word table from the raw data. For  transporting the table data we cannot use the document properties as with the individual variables, as Word has a maximum length of 255 characters for each custom property value.  Instead, we use a bookmark for each table:







For example, if we want to display a table "partners" from our ABAP program, we write the string GENERATE_TABLE_PARTNERS in the Word document, select it with the mouse and then convert it into a bookmark using the bookmark name "table_partners":




The tables "contacts", "relationships" and "partners" are defined in our ABAP program. The character string "GENERATE_TABLE_PARTNERS" could be any character string that is very unlikely to occur in the document. It is case-sensitive.

In the Word template, use the menu items Developer -> Visual Basic to add the function calls for generating your tables:



The VBA code for creating the table essentially consists of using the VBA function "Range.ConvertToTable()" which converts data from CSV format into a Word table. There is also a subroutine Base64Decoding() in the VBA, which we need because we transport the table data in Base64 format to avoid conflicts with reserved XML characters in the Word document.

After executing "Range.ConvertToTable()" you can optionally assign styles and colors etc. to the generated table object. We will not go into the programming of the VBA part in detail, but you will find a link to all the documents used here below. If you are happy with the simple tables as shown here, you can use the routines as they are.


How to generate the Word document from the template (ABAP)

In ABAP, we first read the Word template into a variable of type 'xstring'. This part differs depending on where the template is stored, e.g. in the file system of the SAP server, as an attachment to the customer or in the SAP MIME repository. For the MIME repository, it may look as follows:

* read Word template file from SAP MIME repository
    data:
     template_path type string 
         value 'SAP/BC/BSP/S10/CIS/TEMPLATES/TEMPLATE.C218.EN.DOCM'.

* content
    data: xcontent_template type xstring.

* interface to SAP MIME repository
    data: mime_api    type ref to cl_mime_repository_api.
    mime_api ?= cl_mime_repository_api=>if_mr_api~get_api( ).

* read from MIME repository
    call method mime_api->if_mr_api~get
      exporting
        i_url     = template_path
      importing
        e_content = xcontent_template
      exceptions
        others    = 1.

We then use the SAP standard class "cl_docx_document" to extract the custom property list and the actual content from the Word document:

    data:
      docx           type ref to cl_docx_document,
      main           type ref to cl_docx_maindocumentpart,
      custom         type ref to cl_oxml_custompropertiespart,
      xstringvar     type xstring,
      xstringmain    type xstring,
      xcontent       type xstring,
      contentmain    type string,
      contentvar     type string.

    docx = cl_docx_document=>load_document( xcontent_template ).

* custom properties
    custom       = docx->get_custompropertiespart( ).  
    xstringvar = custom->get_data( ).    
    contentvar = cl_abap_codepage=>convert_from( xstringvar ).

* content  
    main =  docx->get_maindocumentpart( ). 
    xstringmain = main->get_data( ).   
    contentmain = cl_abap_codepage=>convert_from( xstringmain ).

Unfortunately, SAP's "cl_docx_document" class does not have a method for replacing variables, so we have to use our own method:

* replace variables
    replace_variables_docx(
     exporting
       baseobject  = detailcustomer
       contentvar = contentvar
       contentmain =  contentmain
     importing
       newcontentvar = newcontentvar
       newcontentmain = newcontentmain ).

The ABAP source code of the method "replace_variables_docx" can be found below as a link.

So much for replacing the individual variables. To generate the tables, we call up a method from our "customer" class:

*   generate tables
    newcontentmain = detailcustomer->change_word_content( contentmain ).

* generate WORD table content in CSV format
  method change_word_content.

    newcontent = content.

* table contacts
    if newcontent cs 'GENERATE_TABLE_CONTACTS'.
      replace 'GENERATE_TABLE_CONTACTS'  in newcontent 
        with 'csvbase64' && s10getuservalue( 'csv_table_contacts' ).
    endif.

* table relations
    if newcontent cs 'GENERATE_TABLE_RELATIONS'.
      replace 'GENERATE_TABLE_RELATIONS'  in newcontent 
        with 'csvbase64' && s10getuservalue( 'csv_table_relations' ).
    endif.

* table partners
    if newcontent cs 'GENERATE_TABLE_PARTNERS'.
      replace 'GENERATE_TABLE_PARTNERS'  in newcontent 
        with 'csvbase64' && s10getuservalue( 'csv_table_partners' ).
    endif.

  endmethod. 

For the attributes, e.g. "csv_table_contacts", the build methods are defined in the "customer" class:

  method build_csv_table_contacts.

   csv_table_contacts =  s10buildcsv(
       foldername = 'contacts'
       columns = 'lastname,firstname,function,telm_numbr'
       withheaders = 'X' ).

    call method cl_http_utility=>if_http_utility~encode_base64
      exporting
        unencoded = csv_table_contacts
      receiving
        encoded   = csv_table_contacts.


  endmethod.


As you can see, it is easy to create the CSV format using the "s10buildcsv" method of the S10 framework.

When using the option withheader = "X", please note that this only works if the corresponding attributes have a reference to the SAP Data Dictionary. If you do not have a suitable data dictionary reference field for an attribute and still want to use the header option, you can overwrite the s10columnheader() method of /s10/any in your class and assign your own text as a column header.



How to trigger the generation of the document and the download in the user interface (HTML incl. JavaScript)

On the HTML page, we show the user a download icon with which he can initiate the generation and download of the Word document:



<!-- Data sheet -->
<div class="infoblock" style="padding: 4px;">
  <label class='label'>Data sheet (Word)</label>
  <br />
  <img src="../../../icons/download.png" style="width: 24px;"  
    onclick="S10Apply('worddoc_mycustomer')" 
    title="Download customer data sheet"/>
</div>


The ABAP method "worddoc_mycustomer" creates the Word document as described above and fills two class attributes, the document name and the document content in base64 format, which we insert into the HTML page as hidden elements:

 <!-- word document handling -->
<input type="hidden" class="output" 
     name="wordfilename" id="wordfilename" 
     onchange='display_word_document();' />
<input type="hidden" class="output" 
      name="wordbase64content" id="wordbase64content" />

The customer number and a timestamp are part of the document name so that the "onchange" JavaScript function "display_word_document" is called.

 <script>
  function display_word_document() {

   var filename = document.getElementById('wordfilename').value;
   var base64content = document.getElementById('wordbase64content').value;
   var datauri = 
      "data:application/vnd.openxmlformats-officedocument"
        + ".wordprocessingml.document;base64," 
         + base64content;

   var link = document.createElement('a');
   link.setAttribute('href', datauri);
   link.setAttribute('target', '_blank');
   link.setAttribute("download", filename);
   link.click();
  };
</script>

In this function "display_word_document" we dynamically create a link node in the HTML document, insert the Word document as inline document (data uri) in base64 format and click on the link. The user now receives the generated Word document as a download.


Download the Word template and the ABAP methods:
s10framework.wordgeneration.zip

Components
S10 Framework