Thursday 18 January 2018

OpenXML in word processing – Custom XML part – mapping structured data

I discussed custom XML part and content control and how to work with it to achieve data binding in word document. Custom xml file was structured in very simple way. However there might be requirement to map and create some parts of document dynamically. For example we do not know how many records our table can contain after processing.

In this blog I will show a way how we can fill table dynamically with data.

To get this done I’ve debugged standard demo program ROOXML_DOCX_FORM_MIX_FT_TEST01 in which class CL_DOCX_FORM with method MERGE_DATA does the job of dynamic creation of records in table. It is using in runtime generated XSLT transformation to match binding tags in docx document with given tags and data from custom xml file. As this standard functionality is provided only as demo we will copy it into z class with some small changes.

Copy class CL_DOCX_FORM into ZCL_DOCX_FORM class. Then you have to correct 3 errors stopping you from activation of newly created Z-class. You find them quickly and you just have to rewrite “cl_docx_form” to “zcl_docx_form”. Then go to method MERGE_DATA where you have to do 2 small changes:

1. Find and rewrite following code where transformation name is created:

CONCATENATE 'XSLT' ls_xsltattr-xsltdesc INTO ls_xsltattr-xsltdesc.

to for example this:

CONCATENATE 'Z' ls_xsltattr-xsltdesc INTO ls_xsltattr-xsltdesc.

Reason for this change is that you are allowed to create transformation only under Z or customer namespace and not XSLT namespace.

2. Find FM: XSLT_MAINTENANCE for deleting generated transformation and insert input parameter i_gen_flag with value abap_true

* Delete the transformation since it is not needed any more.
  CALL FUNCTION 'XSLT_MAINTENANCE'
    EXPORTING
      i_operation               = 'DELETE'                  "#EC NOTEXT
      i_xslt_attributes         = ls_xsltattr
      i_gen_flag                = abap_true
      i_suppress_corr_insert    = abap_true
      i_suppress_tree_placement = abap_true
    EXCEPTIONS
      OTHERS                    = 1.

Reason for this change is to avoid pop-up window in runtime (in case it is used in webUI for example).

Z class ZCL_DOCX_FORM is now ready for use.

Let’s now prepare custom xml file and map it in word document. Here is an example of my custom xml file.

<?xml version="1.0" encoding="utf-8"?>
<data xmlns="http://www.sap.com/SAPForm/0.5">
<TABLE>
<DATA>
<NAME>%NAME%</NAME>
<LAST_NAME>%LAST_NAME%</LAST_NAME>
<DATE>%DATE%</DATE> 
</DATA>
</TABLE>
</data>

Make sure that namespace of data tag is exactly “”http://www.sap.com/SAPForm/0.5″”. This validation is used in mentioned method merge_data. Our table is represented by node TABLE and every line in that table is represented by tag <DATA>…</DATA>. Tags inside <DATA> section are simply columns in tables.

Let’s create new docx file called Table.docx. Insert into document simple table with header line Name, Last Name and Birth date and one empty line. Now mark that empty line and press button Repeating Section Content Control. Your word document should looks like this:

SAP ABAP Development, SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP XML, SAP ABAP Structured Data

Now attach custom xml file created in previous step and map its attributes to columns. Result should looks like this:

SAP ABAP Development, SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP XML, SAP ABAP Structured Data

Now save word document as Table.docx.

We have now prepared everyhting to run following report to demonstrate dynamic table creation.

*&---------------------------------------------------------------------*
*& Report  ZCUSTOMXML_SAP
*&
*&---------------------------------------------------------------------*
*& Report demonstrates creation of custom xml file, then reading
*& document and using generated XSLT tramsformation to merge these files
*& Pavol Olejar 23.4.2017
*&---------------------------------------------------------------------*
REPORT zcustomxml_sap.

TYPES: BEGIN OF t_table,
         name       TYPE string,
         last_name  TYPE string,
         birth_date TYPE string,
       END OF t_table.

DATA: lt_table        TYPE TABLE OF t_table,
      ls_table        TYPE t_table,
      lv_length       TYPE i,
      lt_data_tab     TYPE STANDARD TABLE OF x255,
      lv_docx         TYPE xstring,
      lr_docx         TYPE REF TO cl_docx_document,
      lr_main         TYPE REF TO cl_docx_maindocumentpart,
      lr_custom       TYPE REF TO cl_oxml_customxmlpart,
      lv_table_string TYPE string,
      lv_custom_xml   TYPE xstring.
* 1ST PART - PREPARE DATA FOR MAPPING
ls_table-name = 'James'.
ls_table-last_name = 'Bond'.
ls_table-birth_date = '13.04.1968'.
APPEND ls_table TO lt_table.

ls_table-name = 'Pavol'.
ls_table-last_name = 'Olejar'.
ls_table-birth_date = '27.01.1985'.
APPEND ls_table TO lt_table.

LOOP AT lt_table INTO ls_table.
  CONCATENATE lv_table_string
              '<DATA>'
              '<NAME>' ls_table-name '</NAME>'
              '<LAST_NAME>' ls_table-last_name '</LAST_NAME>'
              '<DATE>' ls_table-birth_date '</DATE>'
              '</DATA>'
         INTO lv_table_string.
ENDLOOP.

CONCATENATE '<?xml version="1.0" encoding="utf-8"?>'
          '<data xmlns="http://www.sap.com/SAPForm/0.5">'
          '<TABLE>'
          lv_table_string
          '</TABLE>'
          '</data>'
INTO DATA(lv_custom).
* 2ND STEP - CREATE CUSTOM XML
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
  EXPORTING
    text   = lv_custom
  IMPORTING
    buffer = lv_custom_xml.
* 3RD STEP - READ WORD DOCUMENT
CALL METHOD cl_gui_frontend_services=>gui_upload
  EXPORTING
    filename   = 'C:\Table.docx'
    filetype   = 'BIN'
  IMPORTING
    filelength = lv_length
  CHANGING
    data_tab   = lt_data_tab.

CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
  EXPORTING
    input_length = lv_length
  IMPORTING
    buffer       = lv_docx
  TABLES
    binary_tab   = lt_data_tab.

CALL METHOD cl_docx_document=>load_document
  EXPORTING
    iv_data = lv_docx
  RECEIVING
    rr_doc  = lr_docx.

CHECK lr_docx IS BOUND.
lv_docx = lr_docx->get_package_data( ).

* 4TH STEP - MERGE CUSTOM XML FILE AND DOC
TRY.
    CALL METHOD zcl_docx_form=>merge_data
      EXPORTING
        im_formtemplate_data = lv_docx
        im_customxml_data    = lv_custom_xml
        im_delete_sdt_tags   = 'Y'
      RECEIVING
        re_merged_data       = lv_docx.
  CATCH cx_root.
ENDTRY.
* 5TH STEp - SAVE RESULT
lv_length  = xstrlen( lv_docx ).
CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
  EXPORTING
    buffer     = lv_docx
  TABLES
    binary_tab = lt_data_tab.

CALL METHOD cl_gui_frontend_services=>gui_download
  EXPORTING
    bin_filesize      = lv_length
    filename          = 'C:\Table.docx'
    filetype          = 'BIN'
    confirm_overwrite = 'X'
  CHANGING
    data_tab          = lt_data_tab.

Result word document looks like this:

SAP ABAP Development, SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP XML, SAP ABAP Structured Data

I reccomend to use MERGE_DATA method only if you need to generate tables dynamically. When you have word documents with tens of pages then process for generation of transformation followed by usage of that transformation can be time consumig. 

Another tip is to check input parameters of this method to influence content control tags behavior – for example to keep them or not.

Note: When using pictures in documents I have experienced problems when extrenal list tag with xpath reference in curly brackets was present. Here is an example.

<a:extLst>
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
<a14:useLocalDpi
      xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/>
</a:ext>
</a:extLst>

Method MERGE_DATA cannot parse this successfully. As solution I suggest to replace one curly brackets with two curly brackets and then run MERGE_DATA method. After that you can change it back. Other option is manually remove this tag from document.

FROM
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">

TO
<a:ext uri="{{28A0092B-C50C-407E-A947-70E740481C1C}}">

No comments:

Post a Comment