Creating word document from template using wordml defined in ECMA-376
Here is an example of creating word documents from templates stored in web repository (SMW0) using wordprocessingml.
A very simple form can be done in around an hour of time.
USE CASE
Prepare the template that needs to be filled with data.
Write the program that will fill the document.
data: LO_FORM type ref to Z_MWL_FORM.
data: L_TEMPLATE type STRING value ‘block’.
data: L_INDEX type I value 1.
data: L_NUM type I.
create object LO_FORM
exporting
I_TEMPLATE = ‘Z_TEST’.
LO_FORM->REPLICATE(
I_TEMPLATE_ID = L_TEMPLATE
I_COPY_NUM = 2
).
LO_FORM->PREP_SEQ_ACCESS( ).
if LO_FORM->FIND_VARIABLE( ‘name’ ) eq ABAP_TRUE.
LO_FORM->SET_VALUE( ‘Zhambyl’).
endif.
if LO_FORM->FIND_VARIABLE( ‘age’ ) eq ABAP_TRUE.
LO_FORM->SET_VALUE( ’25’ ).
endif.
if LO_FORM->FIND_VARIABLE( ‘gender’ ) eq ABAP_TRUE.
LO_FORM->SET_VALUE( ‘male’ ).
endif.
do 2 times.
if LO_FORM->FIND_BLOCK( L_INDEX ) eq ABAP_TRUE.
if LO_FORM->FIND_VARIABLE( ‘var1’ ) eq ABAP_TRUE.
LO_FORM->SET_VALUE( ‘1’ ).
endif.
if LO_FORM->FIND_VARIABLE( ‘var2’ ) eq ABAP_TRUE.
LO_FORM->SET_VALUE( ‘2’ ).
endif.
if LO_FORM->FIND_VARIABLE( ‘var3’ ) eq ABAP_TRUE.
LO_FORM->SET_VALUE( ‘3’ ).
endif.
endif.
add 1 to L_INDEX.
enddo.
LO_FORM->FINISH_SEQ_ACCESS( ).
LO_FORM->CLEAN( ).
LO_FORM->DISPLAY( ).
Open the filled document on client machine.
Implemantaion details
First we need to create a helper class that is responsible for operations related to downloading and uploading file data.
class Z_MWL_FILE definition
public
create public .
public section.
data EXTENSION type STRING .
data TEMPDIR type STRING .
data BSTRING type XSTRING.
methods: DOWNLOAD ” download file from web repository
importing
VALUE(I_TEMPLATE) type STRING
returning
VALUE(R_SUBRC) like SY-SUBRC.
methods GET_BSTRING ” returns xstring representation of file
returning
VALUE(R_STRING) type XSTRING.
methods GET_TEMP_DIR ” chose file storage location
returning
VALUE(R_PATH) type string.
methods SAVE_ON_FRONTEND ” upload file to client
importing
VALUE(I_STRING) type XSTRING
returning
VALUE(R_SUBRC) like SY-SUBRC.
protected section.
private section.
ENDCLASS.
CLASS Z_MWL_FILE IMPLEMENTATION.
method DOWNLOAD.
data: LS_KEY type WWWDATATAB.
data: LS_MIME type W3MIME.
data: LT_MIME type standard table of W3MIME.
field-symbols <LFS_DATA> type ANY.
LS_KEY-RELID = ‘MI’.
LS_KEY-OBJID = I_TEMPLATE.
call function ‘WWWDATA_IMPORT’
exporting
KEY = LS_KEY
tables
MIME = LT_MIME
exceptions
WRONG_OBJECT_TYPE = 1
IMPORT_ERROR = 2
others = 3.
if SY-SUBRC eq 0.
loop at LT_MIME into LS_MIME.
assign LS_MIME to <LFS_DATA> casting type (‘X’).
if <LFS_DATA> is assigned.
concatenate BSTRING <LFS_DATA> into BSTRING in byte mode.
unassign <LFS_DATA>.
endif.
endloop.
else.
R_SUBRC = SY-SUBRC.
endif.
endmethod. “DOWNLOAD
method GET_BSTRING.
R_STRING = BSTRING.
endmethod. “GET_BSTRING
method GET_TEMP_DIR.
data: L_WTITLE type STRING.
data: L_NAME type STRING.
data: L_FPATH type STRING.
L_WTITLE = ‘CHOSE FILE STORAGE LOCATION’.
CL_GUI_FRONTEND_SERVICES=>FILE_SAVE_DIALOG(
exporting
WINDOW_TITLE = L_WTITLE
DEFAULT_EXTENSION = ‘docx’
FILE_FILTER = ‘docx’
changing
FILENAME = L_NAME
PATH = TEMPDIR
FULLPATH = L_FPATH ).
CL_GUI_CFW=>FLUSH( ).
R_PATH = L_FPATH.
endmethod. “GET_TEMP_DIR
method SAVE_ON_FRONTEND.
data: LV_FILE_TAB type standard table of SOLISTI1,
LV_BYTECOUNT type I.
data: L_FPATH type STRING.
call function ‘SCMS_XSTRING_TO_BINARY’
exporting
BUFFER = I_STRING
importing
OUTPUT_LENGTH = LV_BYTECOUNT
tables
BINARY_TAB = LV_FILE_TAB.
“Save the file
L_FPATH = GET_TEMP_DIR( ).
if L_FPATH is not initial.
CL_GUI_FRONTEND_SERVICES=>GUI_DOWNLOAD(
exporting
BIN_FILESIZE = LV_BYTECOUNT
FILENAME = L_FPATH
FILETYPE = ‘BIN’
changing
DATA_TAB = LV_FILE_TAB
).
if SY-SUBRC ne 0.
R_SUBRC = SY-SUBRC.
endif.
else.
R_SUBRC = 2.
endif.
endmethod. “SAVE_ON_FRONTEND
ENDCLASS.
Secondly we create main class that is used to manipulate the word document.
This class relies on classes found in packages: S_OOXML_CORE, SXML and XSLT transformations.
class Z_MWL_FORM definition
public
create public .
public section.
data: MAIN_PART type XSTRING .
data: DOCUMENT type XSTRING .
data: INTRM_PART type XSTRING .
data: FINAL_DOC type XSTRING.
methods CONSTRUCTOR importing I_TEMPLATE type STRING. ” Finds the main part of word document from zip pakcage container and
” stores it. I_TEMPLATE is the logical name of the file in smw0
methods DISPLAY. ” This method packages updated main part and uploads it to front-end. Dont mind the name.
methods REPLICATE ” Replicates marked block of text using transformations and substitues standard markups for custom ones
importing I_TEMPLATE_ID type STRING
I_COPY_NUM type I.
methods: FIND_VARIABLE ” Finds tag named variable using sxml. I_var is a value for name attribute of this tag.
importing I_VAR type STRING
returning VALUE(RV_FOUND) type ABAP_BOOL.
methods: FIND_BLOCK importing I_BLOCK type I ” Finds tag named block using sxml. I_block is a value for number attribute of this tag.
returning VALUE(RV_FOUND) type ABAP_BOOL. ” Block contains several variables that can be copyed with different block numbers
methods: SET_VALUE importing I_VAL type STRING. ” Replaces value of placeholder variable
methods: PREP_SEQ_ACCESS. ” Converts xstring to Xml objects and prepares them for sequencial access
methods: FINISH_SEQ_ACCESS. ” Converts from sXML back to xstring representation
methods CLEAN. ” Clear’s all the custom mark up from main part of word document
protected section.
data: O_FILE type ref to ZCL_ZK_MWL_FILE.
data: O_DOC type ref to CL_DOCX_DOCUMENT.
data: O_DOCUMENTPART type ref to CL_DOCX_MAINDOCUMENTPART.
data: O_SREADER type ref to IF_SXML_READER.
data: O_SWRITER type ref to IF_SXML_WRITER.
data: O_SNODE type ref to IF_SXML_NODE.
data: O_SVALUE_NODE type ref to IF_SXML_VALUE_NODE.
private section.
ENDCLASS.
CLASS Z_MWL_FORM IMPLEMENTATION.
method CLEAN.
if INTRM_PART is not initial.
call transformation Z_CLEAN
source xml INTRM_PART
result xml FINAL_DOC.
endif.
endmethod. “CLEAN
method CONSTRUCTOR.
create object O_FILE.
O_FILE->DOWNLOAD( I_TEMPLATE ).
DOCUMENT = O_FILE->GET_BSTRING( ).
try.
O_DOC = CL_DOCX_DOCUMENT=>LOAD_DOCUMENT( IV_DATA = DOCUMENT ).
* get the maindocument part
O_DOCUMENTPART = O_DOC->GET_MAINDOCUMENTPART( ).
MAIN_PART = O_DOCUMENTPART->GET_DATA( ).
catch CX_OPENXML_FORMAT.
catch CX_OPENXML_NOT_ALLOWED.
catch CX_OPENXML_NOT_FOUND.
catch CX_TRANSFORMATION_ERROR.
endtry.
endmethod. “constructor
method DISPLAY.
if FINAL_DOC is not initial.
O_DOCUMENTPART->FEED_DATA( FINAL_DOC ).
elseif MAIN_PART is not initial.
O_DOCUMENTPART->FEED_DATA( MAIN_PART ).
endif.
FINAL_DOC = O_DOC->GET_PACKAGE_DATA( ).
if O_FILE->SAVE_ON_FRONTEND( FINAL_DOC ) ne 0.
message ‘Выгрузка отменена’ type ‘S’.
endif.
endmethod. “Display
method FIND_BLOCK.
data: LX_ROOT type ref to CX_SXML_ERROR.
data: LO_OPELEM type ref to IF_SXML_OPEN_ELEMENT.
data: L_AT_VAL type ref to IF_SXML_VALUE.
data: L_VAL type STRING.
if O_SREADER is bound and O_SWRITER is bound.
while RV_FOUND ne ABAP_TRUE.
try.
O_SNODE = O_SREADER->READ_NEXT_NODE( ).
if O_SNODE is initial.
exit.
endif.
if O_SNODE->TYPE eq IF_SXML_NODE=>CO_NT_ELEMENT_OPEN.
LO_OPELEM ?= O_SNODE.
if LO_OPELEM->IF_SXML_NAMED~QNAME-NAME eq ‘block’.
L_AT_VAL = LO_OPELEM->GET_ATTRIBUTE_VALUE( ‘num’ ).
L_VAL = L_AT_VAL->GET_VALUE( ).
if L_VAL eq I_BLOCK.
RV_FOUND = ABAP_TRUE.
endif.
endif.
endif.
O_SWRITER->WRITE_NODE( O_SNODE ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
endwhile.
endif.
endmethod. “FIND_BLOCK
method FIND_VARIABLE.
data: LX_ROOT type ref to CX_SXML_ERROR.
data: LO_OPELEM type ref to IF_SXML_OPEN_ELEMENT.
data: L_AT_VAL type ref to IF_SXML_VALUE.
data: L_VAL type STRING.
if O_SREADER is bound and O_SWRITER is bound.
while RV_FOUND ne ABAP_TRUE.
try.
O_SNODE = O_SREADER->READ_NEXT_NODE( ).
if O_SNODE is initial.
exit.
endif.
if O_SNODE->TYPE eq IF_SXML_NODE=>CO_NT_ELEMENT_OPEN.
LO_OPELEM ?= O_SNODE.
if LO_OPELEM->IF_SXML_NAMED~QNAME-NAME eq ‘variable’.
L_AT_VAL = LO_OPELEM->GET_ATTRIBUTE_VALUE( ‘mark’ ).
L_VAL = L_AT_VAL->GET_VALUE( ).
if L_VAL eq I_VAR.
RV_FOUND = ABAP_TRUE.
endif.
endif.
endif.
O_SWRITER->WRITE_NODE( O_SNODE ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
endwhile.
clear RV_FOUND.
while RV_FOUND ne ABAP_TRUE.
try.
O_SNODE = O_SREADER->READ_NEXT_NODE( ).
if O_SNODE is initial.
exit.
endif.
if O_SNODE->TYPE eq IF_SXML_NODE=>CO_NT_ELEMENT_OPEN.
LO_OPELEM ?= O_SNODE.
if LO_OPELEM->IF_SXML_NAMED~QNAME-NAME eq ‘t’.
RV_FOUND = ABAP_TRUE.
endif.
endif.
O_SWRITER->WRITE_NODE( O_SNODE ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
endwhile.
clear RV_FOUND.
while RV_FOUND ne ABAP_TRUE.
try.
O_SNODE = O_SREADER->READ_NEXT_NODE( ).
if O_SNODE is initial.
exit.
endif.
if O_SNODE->TYPE eq IF_SXML_NODE=>CO_NT_VALUE.
O_SVALUE_NODE ?= O_SNODE.
RV_FOUND = ABAP_TRUE.
exit.
endif.
O_SWRITER->WRITE_NODE( O_SNODE ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
endwhile.
endif.
endmethod. “FIND_VARIABLE
method FINISH_SEQ_ACCESS.
data: LX_ROOT type ref to CX_SXML_ERROR.
data: LO_WRITER type ref to CL_SXML_STRING_WRITER.
if O_SREADER is not initial and O_SWRITER is bound.
do.
try.
O_SNODE = O_SREADER->READ_NEXT_NODE( ).
if O_SNODE is initial.
exit.
endif.
O_SWRITER->WRITE_NODE( O_SNODE ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
enddo.
try.
LO_WRITER ?= O_SWRITER.
INTRM_PART = LO_WRITER->GET_OUTPUT( ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
endif.
endmethod. “FINISH_SEQ_ACCESS
method PREP_SEQ_ACCESS.
if INTRM_PART is not initial.
O_SREADER ?= CL_SXML_STRING_READER=>CREATE( INTRM_PART ).
O_SWRITER ?= CL_SXML_STRING_WRITER=>CREATE( ).
endif.
endmethod. “prep_seq_access
method REPLICATE.
if INTRM_PART is initial.
call transformation Z_REPLICATE
source xml MAIN_PART
result xml INTRM_PART
parameters TEMPLATE_ID = I_TEMPLATE_ID
COPY_NUM = I_COPY_NUM.
else.
call transformation Z_REPLICATE
source xml INTRM_PART
result xml INTRM_PART
parameters TEMPLATE_ID = I_TEMPLATE_ID
COPY_NUM = I_COPY_NUM.
endif.
endmethod. “replicate
method SET_VALUE.
data: LX_ROOT type ref to CX_SXML_ERROR.
data: L_XSTRING type XSTRING.
* L_XSTRING = CL_ABAP_CODEPAGE=>CONVERT_TO( I_VAL ).
if O_SVALUE_NODE is bound and O_SWRITER is bound.
try.
O_SVALUE_NODE->IF_SXML_VALUE~SET_VALUE( I_VAL ).
O_SWRITER->WRITE_NODE( O_SNODE ).
catch CX_SXML_ERROR into LX_ROOT.
exit.
endtry.
endif.
endmethod. “set_value
ENDCLASS.
Following are the transformations used in the class described above.
Transformation z_replicate
<xsl:transformxmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:sap="http://www.sap.com/sapxsl"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" omit-xml-declaration="no" standalone="yes"/>
<xsl:param name="TEMPLATE_ID"/>
<xsl:param name="COPY_NUM" sap:type="number"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="w:sdt">
<xsl:choose>
<xsl:when test="descendant::w:tag[@w:val=$TEMPLATE_ID]">
<xsl:call-template name="multiply">
<xsl:with-param name="maxCount" select="$COPY_NUM"/>
<xsl:with-param name="nodeToCopy" select="."/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="variable">
<xsl:attribute name="mark">
<xsl:value-of select="descendant::w:tag/@w:val"/>
</xsl:attribute>
<xsl:apply-templates select="w:sdtContent/node()|@*" mode="variable"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="multiply">
<xsl:param name="maxCount"/>
<xsl:param name="i" select="1"/>
<xsl:param name="nodeToCopy"/>
<xsl:choose>
<xsl:when test="$i <= $maxCount">
<xsl:element name="block">
<xsl:attribute name="num">
<xsl:value-of select="$i"/>
</xsl:attribute>
<!-- <xsl:copy-of select="$nodeToCopy/w:sdtContent/node()|@*"/>-->
<xsl:apply-templates select="$nodeToCopy/w:sdtContent/node()|@*"/>
</xsl:element>
<xsl:call-template name="multiply">
<xsl:with-param name="maxCount" select="$maxCount"/>
<xsl:with-param name="nodeToCopy" select="$nodeToCopy"/>
<xsl:with-param name="i" select="$i+1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:template>
<xsl:template match="node()|@*" mode="variable">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="variable"/>
</xsl:copy>
</xsl:template>
<xsl:template match="w:sdtContent/w:r[1]" mode="variable">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="variable"/>
</xsl:copy>
</xsl:template>
<xsl:template match="w:sdtContent/w:r[position() != 1]" mode="variable">
</xsl:template>
</xsl:transform>
Transformation z_clean
<xsl:transformxmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:sap="http://www.sap.com/sapxsl"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" omit-xml-declaration="no" standalone="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="variable">
<xsl:apply-templates select="node()|@*"/>
</xsl:template>
<xsl:template match="block">
<xsl:apply-templates select="node()|@*"/>
</xsl:template>
</xsl:transform>
Hello, thank you very much for your blog, it has been very useful to me. Would you know how to include dynamic charts based on a table?
Hello!!
And include image??
HI, cant help with charts, but the image is stored as a file in zip archive. You can store a template image and replace it by your own image. You have to make sure that image format is the same.