Wednesday, 7 February 2018

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

In my first blog I wrote short introduction about OpenXML in word processing and how we can work with word documents in ABAP. In this blog I will describe custom xml part of word document and give some examples how we can use it in ABAP. For example how custom data can be used in on demand printing requests to provide customer data in document with content control.

Custom XML parts were introduced in Microsoft word 2007 along with Open XML formats. Custom part of microsoft word document is used to store custom data and it is not suprising that data format is XML. In connection to Custom XML we will use also Content Control functionality of Microsoft Word. Custom Control is set of individual objects to control and customize content of the document. One of its purpose is to map data from different sources (in our example from custom XML) to exact place in word document. So let’s go with example.

In first part I will show you how to manually create and modify custom xml part. In second part I will provide simple ABAP code which does same.

1. Maintaining custom XML manually

Purpose of this step is to understand custom xml and content control in word. So open our Test.docx document (or create a new one) in Microsoft Word 2007 or above.  As prerequisite it is necessary to install add-in called “Custom XML Part Editor”. Go to Developer Tab–>Add-in and search it and add. After successfull installation you should see new button XML Mapping Pane.

SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP Guides

As second step prepare simple custom xml file with some dummy data in XML format. I use following example.

<?xml version="1.0" encoding="utf-8"?>
<data xmlns="custom">      
<NAME>%Name%</NAME>
<LAST_NAME>%Last Name%</LAST_NAME>
<DATE>%Date%</DATE>   
</data>

Press button XML Mapping Pane to add new custom part. On right side select Add new part… and navigate to our custom.xml file. Aftre adding it now select “custom” part and you should see something like this.

SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP Guides

Now place cursor on exact spot in document where you want to map data and by right click on attributes (Name, Last Name, Date) select Insert Content Control–>Plain text. Rewrite values of content controls attributes and save document. I added Name and Last name into our Test.docx document.

SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP Guides

Let’s now check Main document xml part and Custom xml part which was added into document and now will be visible in document structure. For this I use OOXML tool in chrome.

SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP Guides

Our custom XML part has path customXml–>item1.xml and looks like this.

SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP Guides

Main part has path word–>document.xml. As it got quick big I will provide only short sample of this xml. please note tag <w:dataBinding> which binds data from custom XML to document.

SAP ABAP Tutorials and Materials, SAP ABAP Certifications, SAP ABAP Guides

2. Maintainong custom XML in ABAP

In second part I will provide ABAP code to do same as in first step. Aftre 1st run of program we read word document, create new custom xml part, attach this part to document and save it into same file. Now it is time to manually map data to places where we want them to appear – in same way how I showed in 1st part. Now with second run of report we change custom xml with data we want.

*&---------------------------------------------------------------------*
*& Report  ZCUSTOMXML
*&
*&---------------------------------------------------------------------*
*& Report demonstrates reading word document, creating custom xml part
*& to attach it to document.
*& Pavol Olejar 23.4.2017
*&---------------------------------------------------------------------*
REPORT zcustomxml.

DATA: lv_length     TYPE i,
      lt_data_tab   TYPE STANDARD TABLE OF x255,
      lv_docx       TYPE xstring,
      lv_string     TYPE string,
      lr_docx       TYPE REF TO cl_docx_document,
      lr_main       TYPE REF TO cl_docx_maindocumentpart,
      lr_custom_col TYPE REF TO cl_openxml_partcollection,
      lr_custom     TYPE REF TO cl_oxml_customxmlpart,
      lv_it         TYPE i,
      lv_custom_xml TYPE xstring,
      lv_date       TYPE string.

* 1ST STEP - READ FILE AND FIND CUSTOM XML
*--------------------
CALL METHOD cl_gui_frontend_services=>gui_upload
  EXPORTING
    filename   = 'C:\Test.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.
lr_main = lr_docx->get_maindocumentpart( ).
lr_custom_col = lr_main->get_customxmlparts( ).
* We can have more than 1 custom xml files
* so let's find correct one and if exists
DATA(lv_size) = lr_custom_col->get_count( ).
DATA: lv_flag TYPE flag VALUE abap_false.
DO lv_size TIMES.
  lv_it = sy-index - 1.
  lr_custom ?= lr_custom_col->get_part( iv_index = lv_it ).
  DATA(lv_xml) = lr_custom->get_data( ).
  CALL FUNCTION 'CRM_IC_XML_XSTRING2STRING'
    EXPORTING
      inxstring = lv_xml
    IMPORTING
      outstring = lv_string.

  FIND FIRST OCCURRENCE OF 'xmlns="custom"' IN lv_string.
  IF sy-subrc EQ 0.
* our custom xml was found
    lv_flag = abap_true.
    EXIT.
  ENDIF.
ENDDO.

* 2ND STEP - CREATE OR CHANGE CUSTOM XML
*---------------------------------------
CASE lv_flag.
  WHEN abap_false. "our custom xml is not there yet so we create it
    lr_custom = lr_main->add_customxmlpart( ).

    CONCATENATE '<?xml version="1.0" encoding="utf-8"?>'
              '<data xmlns="custom">'
              '<NAME>%Name%</NAME>'
              '<LAST_NAME>%Last Name%</LAST_NAME>'
              '<DATE>%Date%</DATE>'
              '</data>'
    INTO DATA(lv_custom).

    CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
      EXPORTING
        text   = lv_custom
      IMPORTING
        buffer = lv_custom_xml.
  WHEN abap_true.  "we provide data for document
* lr_custom and lv_xml are correctly filled from loop
* Change data in custom XML
    CALL FUNCTION 'CRM_IC_XML_XSTRING2STRING'
      EXPORTING
        inxstring = lv_xml
      IMPORTING
        outstring = lv_string.
    REPLACE FIRST OCCURRENCE OF '%Name%' IN lv_string
    WITH 'James'.
    REPLACE FIRST OCCURRENCE OF '%Last Name%' IN lv_string
    WITH 'Bond'.
    CONCATENATE sy-datum+6(2) '.' sy-datum+4(2) '.' sy-datum+0(4)
    INTO lv_date.
    REPLACE FIRST OCCURRENCE OF '%Date%' IN lv_string
    WITH lv_date.

    CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
      EXPORTING
        text   = lv_string
      IMPORTING
        buffer = lv_custom_xml.

ENDCASE.
* 3RD STEP - SAVE IT
*---------------------------------------
lr_custom->feed_data( iv_data = lv_custom_xml ).
lv_docx = lr_docx->get_package_data( ).
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:\Test.docx'
    filetype          = 'BIN'
    confirm_overwrite = 'X'
  CHANGING
    data_tab          = lt_data_tab.

1 comment:

  1. Thank you for your post. This is excellent information. It is amazing and wonderful to visit your site.
    sas value added reseller

    ReplyDelete