Purpose Generate a Word document |
Solution
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)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 |