Всем привет!
Никак не доходили руки довести до рабочей версии свою поделку, но вроде как получилось.
Ниже класс генерации отчета в формате DOCX средствами АБАП кода.
Плюсы и минусы такого подхода описывать не имеет смысла, всем и так понятно.
Если будут ошибки пишите, оперативно не исправлю, как будет возможность сделаю.
Если вы нашли и поправили баг, буду вам благодарен.
Сделал сразу с программой примером ее использования.
Здесь после кода программы идет файл 1.docx, который надо скопировать в 'c:\temp'.
Готовый файл упадет туда же с именем 3.docx.
Code:
*&---------------------------------------------------------------------*
*& Report ZHCM_TEST
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*
REPORT zhcm_test.
*----------------------------------------------------------------------*
* CLASS lcl_docx DEFINITION
*----------------------------------------------------------------------*
* Поделка: Братковский Михаил можно просто Брат Мигель
* версия один.и все нули
*
* Класс генерации отчетов в формате DOCX
* при создании класса на вход передается строка xstring, содержащая
* шаблон файла docx.
* формат объявления полей в шаблоне следующий:
* 1. если это не табличное поле, то оно должно начинаться на #0
* 2. если это поле таблицы, то оно должно начинантсья на #1 - #9,
* где 1-9 значения параметра iv_table.
* 3. После №0-№9 идет наименование поля.
* 4. После наименования поля идет #.
* Например: поле для простой замены #0DATE#
* ВАЖНО! Все поле надо вписывать за один раз (без редактирования),
* Если редактировать пол частично,
* то почему-то текст развивается на разные теги
*
* 5. строка таблицы 1-9 может состоять из множества строк в шаблоне,
* 6. перед внесением данных для новой строки табличной части, необходимо
* вызывать метод new_line (для полей простой замены это не требуется).
* 7. методы внесения данных (методы подходят для внесения любого поля):
* 8. add_field - добавляет для заполнения одно поле.
* 9. add_structure - добавляет все поля структуры
* 10. add_table - добавляет все строки по всей структуре
* 11. процесс заполнения осуществляется методом fill_data
* 12. получения результирующего отчета - get_document
*
* что надо доделать: причесать код программы, добавить кучу проверок,
* добавить преобразование полей согласно типа
*----------------------------------------------------------------------*
CLASS lcl_docx DEFINITION.
PUBLIC SECTION.
METHODS new_line.
METHODS constructor
IMPORTING
string TYPE xstring.
METHODS add_field
IMPORTING
iv_table TYPE string DEFAULT '0'
iv_field TYPE string
iv_value TYPE any.
METHODS get_document
RETURNING
value(rv_string) TYPE xstring.
METHODS clear_field_value.
METHODS fill_data.
METHODS clear_used_blocks.
METHODS add_structure
IMPORTING
iv_table TYPE string
is_data TYPE any.
METHODS add_table
IMPORTING
iv_table TYPE string
it_data TYPE any.
PRIVATE SECTION.
TYPE-POOLS: ixml, abap.
CLASS cl_ixml DEFINITION LOAD.
TYPES: BEGIN OF ts_vfields,
number TYPE i,
table TYPE num1,
fieldname TYPE string,
fieldrep TYPE string,
value TYPE string,
END OF ts_vfields.
TYPES: tt_vfields TYPE TABLE OF ts_vfields WITH NON-UNIQUE KEY number table fieldname.
TYPES: BEGIN OF ts_nodes_tables,
table TYPE num1,
gid TYPE i,
END OF ts_nodes_tables.
TYPES: tt_nodes_tables TYPE TABLE OF ts_nodes_tables WITH NON-UNIQUE KEY table.
TYPES: BEGIN OF ts_nodes_fields,
table TYPE num1,
gid TYPE i,
index TYPE i,
END OF ts_nodes_fields.
TYPES: tt_nodes_fields TYPE TABLE OF ts_nodes_fields WITH NON-UNIQUE KEY table.
TYPES: BEGIN OF ts_number_tables,
number TYPE i,
table TYPE num1,
END OF ts_number_tables.
TYPES: tt_number_tables TYPE TABLE OF ts_number_tables WITH NON-UNIQUE KEY number table.
TYPES: BEGIN OF ts_table_fields,
table TYPE num1,
fieldname TYPE string,
fieldtype TYPE char1,
END OF ts_table_fields.
TYPES: tt_table_fields TYPE TABLE OF ts_table_fields WITH NON-UNIQUE KEY table fieldname.
CLASS-DATA: gr_document TYPE REF TO if_ixml_document
,gr_istream TYPE REF TO if_ixml_istream
,gr_ixml TYPE REF TO if_ixml
,gr_ostream TYPE REF TO if_ixml_ostream
,gr_parser TYPE REF TO if_ixml_parser
,gr_renderer TYPE REF TO if_ixml_renderer
,gr_stream_factory TYPE REF TO if_ixml_stream_factory
,gr_zipper TYPE REF TO cl_abap_zip
,gt_fields_val TYPE tt_vfields
,gt_nodes_fields TYPE tt_nodes_fields
,gt_nodes_tables TYPE tt_nodes_tables
,gt_number_tables TYPE tt_number_tables
,gt_table_fields TYPE tt_table_fields
,gv_document TYPE xstring
,gv_number TYPE i.
METHODS set_field_value
IMPORTING
ir_node TYPE REF TO if_ixml_node
iv_number TYPE i.
METHODS check_fields.
METHODS check_tables.
METHODS check_report.
ENDCLASS. "lcl_docx DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_docx IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_docx IMPLEMENTATION.
METHOD new_line.
gv_number = gv_number + 1.
ENDMETHOD. "NEW_LINE
METHOD constructor.
CREATE OBJECT gr_zipper.
gr_zipper->load( string ).
gr_zipper->get( EXPORTING name = 'word/document.xml' IMPORTING content = gv_document ).
gr_ixml = cl_ixml=>create( ).
gr_stream_factory = gr_ixml->create_stream_factory( ).
gr_istream = gr_stream_factory->create_istream_xstring( gv_document ).
gr_document = gr_ixml->create_document( ).
gr_parser = gr_ixml->create_parser( stream_factory = gr_stream_factory
istream = gr_istream
document = gr_document ).
gr_parser->parse( ).
gv_number = 1.
ENDMETHOD. "constructor
METHOD add_field.
DATA: ls_fields_val TYPE ts_vfields,
ls_number_tables TYPE ts_number_tables.
IF iv_table = '0'.
ls_number_tables-number = ls_fields_val-number = 1.
ELSE.
ls_number_tables-number = ls_fields_val-number = gv_number.
ENDIF.
ls_number_tables-table = ls_fields_val-table = iv_table.
READ TABLE gt_number_tables WITH KEY number = gv_number table = iv_table BINARY SEARCH TRANSPORTING NO FIELDS.
IF sy-subrc IS NOT INITIAL.
APPEND ls_number_tables TO gt_number_tables.
ENDIF.
ls_fields_val-fieldname = iv_field.
CONCATENATE '#' iv_table iv_field '#' INTO ls_fields_val-fieldrep.
ls_fields_val-value = iv_value.
APPEND ls_fields_val TO gt_fields_val.
ENDMETHOD. "add_field
METHOD get_document.
DATA: lv_document TYPE xstring.
gr_ostream = gr_stream_factory->create_ostream_xstring( string = lv_document ).
gr_renderer = gr_ixml->create_renderer( ostream = gr_ostream
document = gr_document ).
gr_renderer->render( ).
gr_zipper->delete( EXPORTING name = 'word/document.xml' ).
gr_zipper->add( EXPORTING name = 'word/document.xml' content = lv_document ).
rv_string = gr_zipper->save( ).
ENDMETHOD. "get_document
METHOD clear_field_value.
CLEAR: gt_fields_val[].
gv_number = 1.
ENDMETHOD. "CLEAR_FIELD_VALUE
METHOD fill_data.
DATA: lr_cnode TYPE REF TO if_ixml_node.
DATA: lr_nnode TYPE REF TO if_ixml_node.
DATA: lr_pnode TYPE REF TO if_ixml_node.
DATA: lr_fnode TYPE REF TO if_ixml_node,
lr_ffilter TYPE REF TO if_ixml_node_filter,
lr_fiterator TYPE REF TO if_ixml_node_iterator.
DATA: ls_field_val TYPE ts_vfields,
ls_nodes_tables TYPE ts_nodes_tables,
ls_number_tables TYPE ts_number_tables.
DATA: lv_number TYPE i,
lv_gid TYPE i,
lv_index TYPE i,
lv_tabix TYPE i.
me->check_report( ).
SORT gt_fields_val BY number.
DO gv_number TIMES.
lv_number = sy-index.
READ TABLE gt_number_tables WITH KEY number = lv_number BINARY SEARCH TRANSPORTING NO FIELDS.
IF sy-subrc IS INITIAL.
lv_tabix = sy-tabix.
ELSE.
lv_tabix = 1.
ENDIF.
LOOP AT gt_number_tables FROM lv_tabix INTO ls_number_tables.
IF ls_number_tables-number NE lv_number. EXIT. ENDIF.
LOOP AT gt_nodes_tables INTO ls_nodes_tables WHERE table = ls_number_tables-table.
IF ls_nodes_tables-table > '0'.
lr_cnode = gr_document->find_from_gid( ls_nodes_tables-gid ).
lr_nnode = lr_cnode->clone( ).
lr_pnode = lr_cnode->get_parent( ).
lr_pnode->append_child( lr_nnode ).
lv_gid = lr_nnode->get_gid( ).
lr_ffilter = lr_nnode->create_filter_name( name = '#text' ).
lr_fiterator = lr_nnode->create_iterator_filtered( lr_ffilter ).
lr_fnode = lr_fiterator->get_next( ).
WHILE lr_fnode IS NOT INITIAL.
lv_index = sy-index.
READ TABLE gt_nodes_fields WITH KEY table = ls_nodes_tables-table gid = ls_nodes_tables-gid index = lv_index TRANSPORTING NO FIELDS.
IF sy-subrc IS INITIAL.
me->set_field_value( ir_node = lr_fnode iv_number = lv_number ).
ENDIF.
lr_fnode = lr_fiterator->get_next( ).
ENDWHILE.
FREE: lr_ffilter, lr_fiterator.
ELSE.
lr_fnode = gr_document->find_from_gid( ls_nodes_tables-gid ).
me->set_field_value( ir_node = lr_fnode iv_number = lv_number ).
ENDIF.
ENDLOOP.
ENDLOOP.
ENDDO.
clear_used_blocks( ).
clear_field_value( ).
ENDMETHOD. "fill_data
METHOD clear_used_blocks.
DATA: lr_node TYPE REF TO if_ixml_node.
DATA: ls_nodes_tables TYPE ts_nodes_tables.
LOOP AT gt_nodes_tables INTO ls_nodes_tables WHERE table > '0'.
lr_node = gr_document->find_from_gid( ls_nodes_tables-gid ).
IF lr_node IS NOT INITIAL.
lr_node->remove_node( ).
ENDIF.
ENDLOOP.
ENDMETHOD. "clear_used_blocks
METHOD add_structure.
DATA: lr_struc_descr TYPE REF TO cl_abap_structdescr,
lt_fields TYPE ddfields,
ls_fields TYPE LINE OF ddfields.
DATA: lt_components TYPE abap_compdescr_tab,
ls_components TYPE LINE OF abap_compdescr_tab.
DATA: lv_field TYPE string,
lv_value TYPE string.
FIELD-SYMBOLS: <value> TYPE any.
new_line( ).
lr_struc_descr ?= cl_abap_structdescr=>describe_by_data( is_data ).
LOOP AT lr_struc_descr->components INTO ls_components.
ASSIGN COMPONENT sy-tabix OF STRUCTURE is_data TO <value>.
lv_field = ls_components-name."ls_fields-fieldname.
lv_value = <value>.
add_field( iv_table = iv_table iv_field = lv_field iv_value = lv_value ).
ENDLOOP.
ENDMETHOD. "add_structure
METHOD add_table.
FIELD-SYMBOLS: <table> TYPE ANY TABLE,
<structure> TYPE any.
ASSIGN it_data TO <table>.
LOOP AT <table> ASSIGNING <structure>.
add_structure( iv_table = iv_table is_data = <structure> ).
ENDLOOP.
ENDMETHOD. "add_table
METHOD set_field_value.
DATA: ls_fields_val TYPE ts_vfields.
DATA: lv_value TYPE string,
lv_count TYPE i,
lv_tabix TYPE i.
lv_value = ir_node->get_value( ).
*< loop optimize в таком виде loop работает на порядок быстрее, но таблица должна быть отсортирована по ключу
READ TABLE gt_fields_val WITH KEY number = iv_number BINARY SEARCH TRANSPORTING NO FIELDS.
IF sy-subrc IS INITIAL.
lv_tabix = sy-tabix.
ELSE.
lv_tabix = 1.
ENDIF.
LOOP AT gt_fields_val FROM lv_tabix INTO ls_fields_val.
IF ls_fields_val-number NE iv_number.
EXIT.
ENDIF.
REPLACE ls_fields_val-fieldrep IN lv_value WITH ls_fields_val-value.
IF sy-subrc = 0.
lv_count = 1.
ENDIF.
ENDLOOP.
*> loop optimize
IF lv_count = 1.
ir_node->set_value( lv_value ).
ENDIF.
ENDMETHOD. "set_field_value
METHOD check_fields.
DATA: lr_cnode TYPE REF TO if_ixml_node.
DATA: lr_filter TYPE REF TO if_ixml_node_filter,
lr_iterator TYPE REF TO if_ixml_node_iterator.
DATA: ls_nodes_fields TYPE ts_nodes_fields,
ls_nodes_tables TYPE ts_nodes_tables.
DATA: lv_gid TYPE i,
lv_num TYPE num1,
lv_count TYPE i,
lv_value TYPE string,
lv_tname TYPE text2 VALUE '#0'.
lr_filter = gr_document->create_filter_name( name = '#text').
lr_iterator = gr_document->create_iterator_filtered( lr_filter ).
lr_cnode = lr_iterator->get_next( ).
WHILE lr_cnode IS NOT INITIAL.
lv_gid = lr_cnode->get_gid( ).
lv_value = lr_cnode->get_value( ).
FIND lv_tname IN lv_value MATCH COUNT lv_count.
IF lv_count > 0.
READ TABLE gt_nodes_tables WITH KEY table = 0 gid = lv_gid TRANSPORTING NO FIELDS.
IF sy-subrc IS NOT INITIAL.
ls_nodes_tables-table = 0.
ls_nodes_tables-gid = lv_gid.
APPEND ls_nodes_tables TO gt_nodes_tables.
ENDIF.
ENDIF.
lr_cnode = lr_iterator->get_next( ).
ENDWHILE.
ENDMETHOD. "check_fields
METHOD check_tables.
DATA: lr_tnode TYPE REF TO if_ixml_node,
lr_fnode TYPE REF TO if_ixml_node,
lr_tfilter TYPE REF TO if_ixml_node_filter,
lr_ffilter TYPE REF TO if_ixml_node_filter,
lr_titerator TYPE REF TO if_ixml_node_iterator,
lr_fiterator TYPE REF TO if_ixml_node_iterator.
DATA: ls_nodes_fields TYPE ts_nodes_fields,
ls_nodes_tables TYPE ts_nodes_tables.
DATA: lv_gid TYPE i,
lv_index TYPE i,
lv_num TYPE num1,
lv_count TYPE i,
lv_value TYPE string,
lv_tname TYPE text2.
lr_tfilter = gr_document->create_filter_name( name = 'tr' namespace = 'w' ).
lr_titerator = gr_document->create_iterator_filtered( lr_tfilter ).
lr_tnode = lr_titerator->get_next( ).
WHILE lr_tnode IS NOT INITIAL.
lv_gid = lr_tnode->get_gid( ).
* me->check_fields( lv_gid ).
lr_ffilter = lr_tnode->create_filter_name( name = '#text' ).
lr_fiterator = lr_tnode->create_iterator_filtered( lr_ffilter ).
lr_fnode = lr_fiterator->get_next( ).
WHILE lr_fnode IS NOT INITIAL.
CLEAR lv_count.
lv_num = 1.
ls_nodes_fields-index = sy-index.
ls_nodes_fields-gid = lv_gid.
lv_value = lr_fnode->get_value( ).
DO 10 TIMES.
CONCATENATE '#' lv_num INTO lv_tname.
FIND lv_tname IN lv_value MATCH COUNT lv_count.
IF lv_count > 0.
ls_nodes_fields-table = lv_num.
APPEND ls_nodes_fields TO gt_nodes_fields.
EXIT.
ENDIF.
lv_num = lv_num + 1.
ENDDO.
IF lv_count > 0.
READ TABLE gt_nodes_tables WITH KEY table = lv_num gid = lv_gid TRANSPORTING NO FIELDS.
IF sy-subrc IS NOT INITIAL.
ls_nodes_tables-table = lv_num.
ls_nodes_tables-gid = lv_gid.
APPEND ls_nodes_tables TO gt_nodes_tables.
ENDIF.
ENDIF.
lr_fnode = lr_fiterator->get_next( ).
ENDWHILE.
FREE: lr_ffilter, lr_fiterator, lr_fnode.
lr_tnode = lr_titerator->get_next( ).
ENDWHILE.
ENDMETHOD. "check_fields
METHOD check_report.
me->check_tables( ).
me->check_fields( ).
ENDMETHOD. "check_tables
ENDCLASS. "lcl_docx IMPLEMENTATION
PARAMETERS p_file TYPE text250 DEFAULT 'c:\temp\1.docx'.
DATA: l_data_xml TYPE xstring.
DATA: l_filename TYPE string.
START-OF-SELECTION.
l_filename = p_file.
TRY.
l_data_xml = cl_openxml_helper=>load_local_file( l_filename ).
CATCH
cx_openxml_not_found.
ENDTRY.
DATA: gr_docx TYPE REF TO lcl_docx.
CREATE OBJECT gr_docx
EXPORTING
string = l_data_xml.
gr_docx->add_field( iv_field = 'DATE1' iv_value = '01.01.2013').
gr_docx->add_field( iv_field = 'FIO' iv_value = 'Иванов Иван Иванович').
DO 1000 TIMES.
gr_docx->new_line( ).
gr_docx->add_field( iv_table = '1' iv_field = 'DATA' iv_value = 'text1 table1').
gr_docx->add_field( iv_table = '1' iv_field = 'NUM' iv_value = sy-index ).
gr_docx->new_line( ).
gr_docx->add_field( iv_table = '1' iv_field = 'DATA' iv_value = 'text2 table1').
gr_docx->add_field( iv_table = '1' iv_field = 'NUM' iv_value = '1.2.').
gr_docx->new_line( ).
gr_docx->add_field( iv_table = '2' iv_field = 'DATA' iv_value = 'text1 table2').
gr_docx->add_field( iv_table = '2' iv_field = 'NUM' iv_value = '2.1.').
gr_docx->new_line( ).
gr_docx->add_field( iv_table = '2' iv_field = 'DATA' iv_value = 'text2 table2').
gr_docx->add_field( iv_table = '2' iv_field = 'NUM' iv_value = '2.2.').
gr_docx->new_line( ).
gr_docx->add_field( iv_table = '1' iv_field = 'DATA' iv_value = 'text3 table1').
gr_docx->add_field( iv_table = '1' iv_field = 'NUM' iv_value = '1.3.').
ENDDO.
gr_docx->fill_data( ).
l_data_xml = gr_docx->get_document( ).
cl_openxml_helper=>store_local_file( im_file_name = 'c:\temp\3.docx' im_data = l_data_xml ).