diff --git a/src/demos/zdemo_excel47.prog.abap b/src/demos/zdemo_excel47.prog.abap new file mode 100644 index 0000000..0f6b73d --- /dev/null +++ b/src/demos/zdemo_excel47.prog.abap @@ -0,0 +1,232 @@ +*&---------------------------------------------------------------------* +*& Report zdemo_excel47 +*&---------------------------------------------------------------------* +*& +*& - BIND_TABLE and Calculated Columns +*& +*&---------------------------------------------------------------------* +REPORT zdemo_excel47. + +DATA: lo_excel TYPE REF TO zcl_excel, + lo_worksheet TYPE REF TO zcl_excel_worksheet, + lo_hyperlink TYPE REF TO zcl_excel_hyperlink, + lo_column TYPE REF TO zcl_excel_column. + +CONSTANTS: gc_save_file_name TYPE string VALUE '47_ColumnFormulas.xlsx'. +INCLUDE zdemo_excel_outputopt_incl. + +CLASS lcl_app DEFINITION. + PUBLIC SECTION. + + METHODS main + RAISING + zcx_excel. + +ENDCLASS. + +CLASS lcl_app IMPLEMENTATION. + + METHOD main. + + TYPES: BEGIN OF ty_tblsheet1_line, + carrid TYPE sflight-carrid, + connid TYPE sflight-connid, + fldate TYPE sflight-fldate, + price TYPE sflight-price, + formula TYPE string, + formula_2 TYPE string, + column_formula TYPE string, + column_formula_2 TYPE sflight-price, + column_formula_3 TYPE sflight-price, + column_formula_4 TYPE sflight-price, + column_formula_5 TYPE string, + column_formula_6 TYPE string, + column_formula_7 TYPE string, + END OF ty_tblsheet1_line, + BEGIN OF ty_tblsheet2_line, + carrid TYPE scarr-carrid, + carrname TYPE scarr-carrname, + END OF ty_tblsheet2_line. + DATA: lv_f1 TYPE string, + ls_tblsheet1 TYPE ty_tblsheet1_line, + lt_tblsheet1 TYPE STANDARD TABLE OF ty_tblsheet1_line, + ls_tblsheet2 TYPE ty_tblsheet2_line, + lt_tblsheet2 TYPE STANDARD TABLE OF ty_tblsheet2_line, + lt_field_catalog TYPE zexcel_t_fieldcatalog, + ls_catalog TYPE zexcel_s_fieldcatalog, + ls_table_settings TYPE zexcel_s_table_settings, + lo_range TYPE REF TO zcl_excel_range. + FIELD-SYMBOLS: TYPE zexcel_s_fieldcatalog. + +*** Initialization + + CREATE OBJECT lo_excel. + + " Sheet1 + lv_f1 = 'TblSheet1[[#This Row],[Airfare]]+100'. " [@Airfare]+100 + ls_tblsheet1-carrid = `AA`. ls_tblsheet1-connid = '0017'. ls_tblsheet1-fldate = '20180116'. ls_tblsheet1-price = '422.94'. ls_tblsheet1-formula = lv_f1. ls_tblsheet1-formula_2 = lv_f1. + APPEND ls_tblsheet1 TO lt_tblsheet1. + ls_tblsheet1-carrid = `AZ`. ls_tblsheet1-connid = '0555'. ls_tblsheet1-fldate = '20180116'. ls_tblsheet1-price = '185.00'. ls_tblsheet1-formula = lv_f1. + APPEND ls_tblsheet1 TO lt_tblsheet1. + ls_tblsheet1-carrid = `LH`. ls_tblsheet1-connid = '0400'. ls_tblsheet1-fldate = '20180119'. ls_tblsheet1-price = '666.00'. ls_tblsheet1-formula = lv_f1. ls_tblsheet1-formula_2 = lv_f1. + APPEND ls_tblsheet1 TO lt_tblsheet1. + ls_tblsheet1-carrid = `AA`. ls_tblsheet1-connid = '0941'. ls_tblsheet1-fldate = '20180117'. ls_tblsheet1-price = '879.82'. ls_tblsheet1-formula = lv_f1. + APPEND ls_tblsheet1 TO lt_tblsheet1. + + " Sheet2 + ls_tblsheet2-carrid = `AA`. ls_tblsheet2-carrname = 'America Airlines'. + APPEND ls_tblsheet2 TO lt_tblsheet2. + ls_tblsheet2-carrid = `AZ`. ls_tblsheet2-carrname = 'Alitalia'. + APPEND ls_tblsheet2 TO lt_tblsheet2. + ls_tblsheet2-carrid = `LH`. ls_tblsheet2-carrname = 'Lufthansa'. + APPEND ls_tblsheet2 TO lt_tblsheet2. + +*** Sheet1 + lo_worksheet = lo_excel->get_active_worksheet( ). + + lt_field_catalog = zcl_excel_common=>get_fieldcatalog( ip_table = lt_tblsheet1 ). + + LOOP AT lt_field_catalog ASSIGNING . + CASE -fieldname. + WHEN 'CARRID'. + -scrtext_l = 'Company ID'. + WHEN 'AIRFARE'. + -scrtext_l = 'Airfare'. + WHEN 'PRICE'. + -totals_function = zcl_excel_table=>totals_function_average. + WHEN 'FORMULA'. + " Each cell may have a distinct formula, none formula is applied to future new rows + -scrtext_l = 'Formula and aggregate function'. + -formula = abap_true. + -totals_function = zcl_excel_table=>totals_function_sum. + WHEN 'FORMULA_2'. + " each cell may have a distinct formula, a formula is applied to future new rows + -scrtext_l = 'Formula except 1 cell & aggregate fu.'. + -formula = abap_true. + -column_formula = lv_f1. " to apply to future rows + -totals_function = zcl_excel_table=>totals_function_min. + WHEN 'COLUMN_FORMULA'. + " The column formula applies to all rows and to future new rows. Internally, the formula is NOT shared because a column name is used. + -scrtext_l = 'Column formula and aggregate function'. + -column_formula = 'TblSheet1[[#This Row],[Airfare]]+222'. " [@Airfare]+222 + -totals_function = zcl_excel_table=>totals_function_min. + WHEN 'COLUMN_FORMULA_2'. + " The column formula applies to all rows and to future new rows. Internally, the formula is shared. + -scrtext_l = 'C2. Column formula'. + -column_formula = 'D2+100'. + WHEN 'COLUMN_FORMULA_3'. + " The column formula applies to all rows and to future new rows. Internally, the formula is shared. + -scrtext_l = 'C3. Column formula & aggregate function'. + -column_formula = 'D2+100'. + -totals_function = zcl_excel_table=>totals_function_max. + WHEN 'COLUMN_FORMULA_4'. + " The column formula applies to all rows and to future new rows. Internally, the formula is shared. + -scrtext_l = 'C4. Column formula array fu./named range'. + -column_formula = 'A1&";"&_xlfn.IFS(TRUE,NamedRange)'. " =A1&";"&@IFS(TRUE,NamedRange) + WHEN 'COLUMN_FORMULA_5'. + " The column formula applies to all rows and to future new rows. Internally, the formula is NOT shared because it refers to a different sheet. + -scrtext_l = 'C5. Column formula refers to other sheet'. + -column_formula = 'OtherSheet!A2'. + WHEN 'COLUMN_FORMULA_6'. + " The column formula applies to all rows and to future new rows. Internally, the formula is NOT shared. + " The formula seen in Excel: =FILTER(TblSheet2[Company Name],TblSheet2[Airline ID]=[@Airline],"") + -scrtext_l = 'C6. Column formula array fu./other sheet'. + -column_formula = '_xlfn.FILTER(TblSheet2[Company Name],TblSheet2[Company ID]=TblSheet1[[#This Row],[Company ID]],"")'. + WHEN 'COLUMN_FORMULA_7'. + " The column formula applies to all rows and to future new rows. Internally, the formula is NOT shared. + " The formula seen in Excel: =FILTER(Tbl2_Sheet1[Company Name],Tbl2_Sheet1[Airline ID]=[@Airline],"") + -scrtext_l = 'C7. Column formula array fu./same sheet'. + -column_formula = '_xlfn.FILTER(Tbl2_Sheet1[Company Name],Tbl2_Sheet1[Company ID]=TblSheet1[[#This Row],[Company ID]],"")'. + ENDCASE. + ENDLOOP. + + ls_table_settings-table_style = zcl_excel_table=>builtinstyle_medium2. + ls_table_settings-table_name = 'TblSheet1'. + ls_table_settings-top_left_column = 'A'. + ls_table_settings-top_left_row = 1. + ls_table_settings-show_row_stripes = abap_true. + + lo_worksheet->bind_table( + ip_table = lt_tblsheet1 + it_field_catalog = lt_field_catalog + is_table_settings = ls_table_settings + iv_default_descr = 'L' ). + + " Named range for formula 4 + lo_range = lo_excel->add_new_range( ). + lo_range->name = 'NamedRange'. + lo_range->set_value( ip_sheet_name = lo_worksheet->get_title( ) + ip_start_column = 'B' + ip_start_row = 1 + ip_stop_column = 'B' + ip_stop_row = 1 ). + + + " Second table in same sheet + lt_field_catalog = zcl_excel_common=>get_fieldcatalog( ip_table = lt_tblsheet2 ). + + LOOP AT lt_field_catalog ASSIGNING . + CASE -fieldname. + WHEN 'CARRID'. + -scrtext_l = 'Company ID'. + WHEN 'CARRNAME'. + -scrtext_l = 'Company Name'. + ENDCASE. + ENDLOOP. + + CLEAR ls_table_settings. + ls_table_settings-table_style = zcl_excel_table=>builtinstyle_medium2. + ls_table_settings-table_name = 'Tbl2_Sheet1'. + ls_table_settings-top_left_column = 'O'. + ls_table_settings-top_left_row = 1. + ls_table_settings-show_row_stripes = abap_true. + + lo_worksheet->bind_table( + ip_table = lt_tblsheet2 + it_field_catalog = lt_field_catalog + is_table_settings = ls_table_settings + iv_default_descr = 'L' ). + +*** Sheet2 + lo_worksheet = lo_excel->add_new_worksheet( 'Sheet2' ). + + CLEAR ls_table_settings. + ls_table_settings-table_style = zcl_excel_table=>builtinstyle_medium2. + ls_table_settings-table_name = 'TblSheet2'. + ls_table_settings-top_left_column = 'A'. + ls_table_settings-top_left_row = 1. + ls_table_settings-show_row_stripes = abap_true. + + lo_worksheet->bind_table( + ip_table = lt_tblsheet2 + it_field_catalog = lt_field_catalog + is_table_settings = ls_table_settings + iv_default_descr = 'L' ). + +*** OtherSheet + lo_worksheet = lo_excel->add_new_worksheet( 'OtherSheet' ). + lo_worksheet->set_cell( ip_column = 1 ip_row = 1 ip_value = 'Title' ). + lo_worksheet->set_cell( ip_column = 1 ip_row = 2 ip_value = 'A2' ). + lo_worksheet->set_cell( ip_column = 1 ip_row = 3 ip_value = 'A3' ). + lo_worksheet->set_cell( ip_column = 1 ip_row = 4 ip_value = 'A4' ). + lo_worksheet->set_cell( ip_column = 1 ip_row = 5 ip_value = 'A5' ). + +*** Active sheet = Sheet1 + lo_excel->set_active_sheet_index_by_name( 'Sheet1' ). + +*** Create output + lcl_output=>output( lo_excel ). + + ENDMETHOD. + +ENDCLASS. + +START-OF-SELECTION. + DATA: go_app TYPE REF TO lcl_app, + go_error TYPE REF TO zcx_excel. + TRY. + CREATE OBJECT go_app. + go_app->main( ). + CATCH zcx_excel INTO go_error. + MESSAGE go_error TYPE 'I' DISPLAY LIKE 'E'. + ENDTRY. diff --git a/src/demos/zdemo_excel47.prog.xml b/src/demos/zdemo_excel47.prog.xml new file mode 100644 index 0000000..3469526 --- /dev/null +++ b/src/demos/zdemo_excel47.prog.xml @@ -0,0 +1,24 @@ + + + + + + ZDEMO_EXCEL47 + S + D$ + 1 + E + X + D$S + X + + + + R + abap2xlsx Demo: Table Calculated Columns (from Excel 365 or 2019) + 65 + + + + + diff --git a/src/zcl_excel_common.clas.abap b/src/zcl_excel_common.clas.abap index 7267b8e..3dc9729 100644 --- a/src/zcl_excel_common.clas.abap +++ b/src/zcl_excel_common.clas.abap @@ -1076,6 +1076,7 @@ CLASS ZCL_EXCEL_COMMON IMPLEMENTATION. lv_ref_formula TYPE string, lv_compare_1 TYPE string, lv_compare_2 TYPE string, + lv_level TYPE i, " Level of groups [..[..]..] or {..} lv_errormessage TYPE string. @@ -1165,6 +1166,30 @@ CLASS ZCL_EXCEL_COMMON IMPLEMENTATION. ENDIF. +*--------------------------------------------------------------------* +* Groups - Ignore values inside blocks [..[..]..] and {..} +* R1C1-Style Cell Reference: R[1]C[1] +* Cell References: 'C:\[Source.xlsx]Sheet1'!$A$1 +* Array constants: {1,3.5,TRUE,"Hello"} +* "Intra table reference": Flights[[#This Row],[Air fare]] +*--------------------------------------------------------------------* + IF lv_tchar CA '[]{}' OR lv_level > 0. + IF lv_tchar CA '[{'. + lv_level = lv_level + 1. + ELSEIF lv_tchar CA ']}'. + lv_level = lv_level - 1. + ENDIF. + IF lv_cnt2 = lv_flen. + lv_substr1 = iv_reference_formula+lv_offset1(lv_numchars). + CONCATENATE lv_cur_form lv_substr1 INTO lv_cur_form. + EXIT. + ENDIF. + lv_numchars = lv_numchars + 1. + lv_cnt = lv_cnt + 1. + lv_cnt2 = lv_cnt + 1. + CONTINUE. + ENDIF. + *--------------------------------------------------------------------* * Operators or parenthesis or last character in formula will separate possible cellreferences *--------------------------------------------------------------------* diff --git a/src/zcl_excel_common.clas.testclasses.abap b/src/zcl_excel_common.clas.testclasses.abap index e333d6a..af79da0 100644 --- a/src/zcl_excel_common.clas.testclasses.abap +++ b/src/zcl_excel_common.clas.testclasses.abap @@ -1197,6 +1197,20 @@ CLASS lcl_excel_common_test IMPLEMENTATION. iv_shift_rows = 1 iv_expected = |'A1'!$A$1| ). +" Reference to another column in the same row of a Table, with a space in the column name + macro_shift_formula( + iv_reference_formula = 'Tbl[[#This Row],[Air fare]]' + iv_shift_cols = 1 + iv_shift_rows = 1 + iv_expected = 'Tbl[[#This Row],[Air fare]]' ). + +" Reference to another column in the same row of a Table, inside more complex expression + macro_shift_formula( + iv_reference_formula = 'Tbl[[#This Row],[Air]]+A1' + iv_shift_cols = 1 + iv_shift_rows = 1 + iv_expected = 'Tbl[[#This Row],[Air]]+B2' ). + ENDMETHOD. METHOD is_cell_in_range. diff --git a/src/zcl_excel_worksheet.clas.abap b/src/zcl_excel_worksheet.clas.abap index fd9e7c5..f96888a 100644 --- a/src/zcl_excel_worksheet.clas.abap +++ b/src/zcl_excel_worksheet.clas.abap @@ -31,6 +31,20 @@ CLASS zcl_excel_worksheet DEFINITION END OF mty_s_outline_row . TYPES: mty_ts_outlines_row TYPE SORTED TABLE OF mty_s_outline_row WITH UNIQUE KEY row_from row_to . + TYPES: + BEGIN OF mty_s_column_formula, + id TYPE i, + column TYPE zexcel_cell_column, + formula TYPE string, + table_top_left_row TYPE zexcel_cell_row, + table_bottom_right_row TYPE zexcel_cell_row, + table_left_column_int TYPE zexcel_cell_column, + table_right_column_int TYPE zexcel_cell_column, + END OF mty_s_column_formula . + TYPES: + mty_th_column_formula + TYPE HASHED TABLE OF mty_s_column_formula + WITH UNIQUE KEY id . TYPES ty_doc_url TYPE c LENGTH 255. CONSTANTS c_break_column TYPE zexcel_break VALUE 2. "#EC NOTEXT @@ -44,6 +58,13 @@ CLASS zcl_excel_worksheet DEFINITION DATA show_rowcolheaders TYPE zexcel_show_gridlines READ-ONLY VALUE abap_true. "#EC NOTEXT DATA styles TYPE zexcel_t_sheet_style . DATA tabcolor TYPE zexcel_s_tabcolor READ-ONLY . + DATA column_formulas TYPE mty_th_column_formula READ-ONLY . + CLASS-DATA: BEGIN OF c_messages READ-ONLY, + formula_id_only_is_possible TYPE string, + column_formula_id_not_found TYPE string, + formula_not_in_this_table TYPE string, + formula_in_other_column TYPE string, + END OF c_messages. METHODS add_comment IMPORTING @@ -231,6 +252,7 @@ CLASS zcl_excel_worksheet DEFINITION VALUE(ep_guid) TYPE zexcel_cell_style RAISING zcx_excel . + CLASS-METHODS class_constructor. METHODS constructor IMPORTING !ip_excel TYPE REF TO zcl_excel @@ -412,6 +434,7 @@ CLASS zcl_excel_worksheet DEFINITION !ip_hyperlink TYPE REF TO zcl_excel_hyperlink OPTIONAL !ip_data_type TYPE zexcel_cell_data_type OPTIONAL !ip_abap_type TYPE abap_typekind OPTIONAL + !ip_column_formula_id TYPE mty_s_column_formula-id OPTIONAL RAISING zcx_excel . METHODS set_cell_formula @@ -632,6 +655,16 @@ CLASS zcl_excel_worksheet DEFINITION VALUE(ep_width) TYPE float RAISING zcx_excel . + CLASS-METHODS check_cell_column_formula + IMPORTING + it_column_formulas TYPE mty_th_column_formula + ip_column_formula_id TYPE mty_s_column_formula-id + ip_formula TYPE zexcel_cell_formula + ip_value TYPE simple + ip_row TYPE zexcel_cell_row + ip_column TYPE zexcel_cell_column + RAISING + zcx_excel. METHODS generate_title RETURNING VALUE(ep_title) TYPE zexcel_sheet_title . @@ -814,6 +847,8 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. lo_iterator TYPE REF TO cl_object_collection_iterator, lo_style_cond TYPE REF TO zcl_excel_style_cond, lo_curtable TYPE REF TO zcl_excel_table. + DATA: ls_column_formula TYPE mty_s_column_formula, + lv_mincol TYPE i. FIELD-SYMBOLS: TYPE zexcel_s_fieldcatalog, @@ -969,6 +1004,17 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. ip_value = lv_value ). ENDIF. + IF -column_formula IS NOT INITIAL. + ls_column_formula-id = lines( column_formulas ) + 1. + ls_column_formula-column = lv_column_int. + ls_column_formula-formula = -column_formula. + ls_column_formula-table_top_left_row = lo_table->settings-top_left_row. + ls_column_formula-table_bottom_right_row = lo_table->settings-bottom_right_row. + ls_column_formula-table_left_column_int = lv_mincol. + ls_column_formula-table_right_column_int = lv_maxcol. + INSERT ls_column_formula INTO TABLE column_formulas. + ENDIF. + ADD 1 TO lv_row_int. LOOP AT ip_table ASSIGNING . @@ -998,6 +1044,31 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. ip_row = lv_row_int ip_formula = ). ENDIF. + ELSEIF -column_formula IS NOT INITIAL. + " Column formulas + IF -style IS NOT INITIAL. + IF -abap_type IS NOT INITIAL. + me->set_cell( ip_column = lv_column_alpha + ip_row = lv_row_int + ip_column_formula_id = ls_column_formula-id + ip_abap_type = -abap_type + ip_style = -style ). + ELSE. + me->set_cell( ip_column = lv_column_alpha + ip_row = lv_row_int + ip_column_formula_id = ls_column_formula-id + ip_style = -style ). + ENDIF. + ELSEIF -abap_type IS NOT INITIAL. + me->set_cell( ip_column = lv_column_alpha + ip_row = lv_row_int + ip_column_formula_id = ls_column_formula-id + ip_abap_type = -abap_type ). + ELSE. + me->set_cell( ip_column = lv_column_alpha + ip_row = lv_row_int + ip_column_formula_id = ls_column_formula-id ). + ENDIF. ELSE. IF -style IS NOT INITIAL. IF -abap_type IS NOT INITIAL. @@ -1704,6 +1775,40 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. ENDMETHOD. "CHANGE_CELL_STYLE + METHOD check_cell_column_formula. + + FIELD-SYMBOLS TYPE zcl_excel_worksheet=>mty_s_column_formula. + + IF ip_value IS NOT INITIAL OR ip_formula IS NOT INITIAL. + zcx_excel=>raise_text( c_messages-formula_id_only_is_possible ). + ENDIF. + READ TABLE it_column_formulas WITH TABLE KEY id = ip_column_formula_id ASSIGNING . + IF sy-subrc <> 0. + zcx_excel=>raise_text( c_messages-column_formula_id_not_found ). + ENDIF. + IF ip_row < -table_top_left_row + 1 + OR ip_row > -table_bottom_right_row + 1 + OR ip_column < -table_left_column_int + OR ip_column > -table_right_column_int. + zcx_excel=>raise_text( c_messages-formula_not_in_this_table ). + ENDIF. + IF ip_column <> -column. + zcx_excel=>raise_text( c_messages-formula_in_other_column ). + ENDIF. + + ENDMETHOD. + + + METHOD class_constructor. + + c_messages-formula_id_only_is_possible = |{ 'If Formula ID is used, value and formula must be empty'(008) }|. + c_messages-column_formula_id_not_found = |{ 'The Column Formula does not exist'(009) }|. + c_messages-formula_not_in_this_table = |{ 'The cell uses a Column Formula which should be part of the same table'(010) }|. + c_messages-formula_in_other_column = |{ 'The cell uses a Column Formula which is in a different column'(011) }|. + + ENDMETHOD. + + METHOD constructor. DATA: lv_title TYPE zexcel_sheet_title. @@ -2884,15 +2989,27 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. TYPE t, TYPE simple, TYPE abap_typekind. + FIELD-SYMBOLS: TYPE mty_s_column_formula. - IF ip_value IS NOT SUPPLIED AND ip_formula IS NOT SUPPLIED. + IF ip_value IS NOT SUPPLIED + AND ip_formula IS NOT SUPPLIED + AND ip_column_formula_id = 0. zcx_excel=>raise_text( 'Please provide the value or formula' ). ENDIF. * Begin of change issue #152 - don't touch exisiting style if only value is passed * lv_style_guid = ip_style. lv_column = zcl_excel_common=>convert_column2int( ip_column ). + IF ip_column_formula_id <> 0. + check_cell_column_formula( + it_column_formulas = column_formulas + ip_column_formula_id = ip_column_formula_id + ip_formula = ip_formula + ip_value = ip_value + ip_row = ip_row + ip_column = lv_column ). + ENDIF. READ TABLE sheet_content ASSIGNING WITH TABLE KEY cell_row = ip_row " Changed to access via table key , Stefan Schmöcker, 2013-08-03 cell_column = lv_column. IF sy-subrc = 0. @@ -3035,6 +3152,7 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. * End of change issue #152 - don't touch exisiting style if only value is passed -cell_value = lv_value. -cell_formula = ip_formula. + -column_formula_id = ip_column_formula_id. -cell_style = lv_style_guid. -data_type = lv_data_type. ELSE. @@ -3042,6 +3160,7 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. ls_sheet_content-cell_column = lv_column. ls_sheet_content-cell_value = lv_value. ls_sheet_content-cell_formula = ip_formula. + ls_sheet_content-column_formula_id = ip_column_formula_id. ls_sheet_content-cell_style = lv_style_guid. ls_sheet_content-data_type = lv_data_type. lv_row_alpha = ip_row. diff --git a/src/zcl_excel_worksheet.clas.testclasses.abap b/src/zcl_excel_worksheet.clas.testclasses.abap index 4d7a4f5..3b101c6 100644 --- a/src/zcl_excel_worksheet.clas.testclasses.abap +++ b/src/zcl_excel_worksheet.clas.testclasses.abap @@ -1,3 +1,6 @@ +CLASS ltc_check_cell_column_formula DEFINITION DEFERRED. +CLASS zcl_excel_worksheet DEFINITION LOCAL FRIENDS + ltc_check_cell_column_formula. CLASS lcl_excel_worksheet_test DEFINITION FOR TESTING RISK LEVEL HARMLESS @@ -39,6 +42,40 @@ CLASS lcl_excel_worksheet_test DEFINITION FOR TESTING ENDCLASS. "lcl_Excel_Worksheet_Test +CLASS ltc_check_cell_column_formula DEFINITION FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + + METHODS: success FOR TESTING RAISING cx_static_check. + METHODS: fails_both_formula_id_value FOR TESTING RAISING cx_static_check. + METHODS: fails_both_formula_id_formula FOR TESTING RAISING cx_static_check. + METHODS: formula_id_not_found FOR TESTING RAISING cx_static_check. + METHODS: outside_table_fails__above FOR TESTING RAISING cx_static_check. + METHODS: outside_table_fails__below FOR TESTING RAISING cx_static_check. + METHODS: outside_table_fails__left FOR TESTING RAISING cx_static_check. + METHODS: outside_table_fails__right FOR TESTING RAISING cx_static_check. + METHODS: must_be_in_same_column FOR TESTING RAISING cx_static_check. + + METHODS: setup. + METHODS: should_fail + IMPORTING + ip_formula_id TYPE zexcel_s_cell_data-column_formula_id + ip_formula TYPE zexcel_s_cell_data-cell_formula OPTIONAL + ip_value TYPE zexcel_s_cell_data-cell_value OPTIONAL + ip_row TYPE zexcel_s_cell_data-cell_row + ip_column TYPE zexcel_s_cell_data-cell_column + ip_exp TYPE string + RAISING + zcx_excel. + + DATA: mt_column_formulas TYPE zcl_excel_worksheet=>mty_th_column_formula, + c_messages LIKE zcl_excel_worksheet=>c_messages. + +ENDCLASS. + + CLASS LCL_EXCEL_WORKSHEET_TEST IMPLEMENTATION. * ============================================== @@ -422,3 +459,100 @@ CLASS LCL_EXCEL_WORKSHEET_TEST IMPLEMENTATION. ENDMETHOD. ENDCLASS. "lcl_Excel_Worksheet_Test + + +CLASS ltc_check_cell_column_formula IMPLEMENTATION. + + METHOD setup. + + DATA: ls_column_formula TYPE zcl_excel_worksheet=>mty_s_column_formula. + + c_messages = zcl_excel_worksheet=>c_messages. + + " Column Formula in table A1:B4 (for unknown reason, bottom_right_row is last actual row minus 1) + CLEAR ls_column_formula. + ls_column_formula-id = 1. + ls_column_formula-column = 1. + ls_column_formula-table_top_left_row = 1. + ls_column_formula-table_bottom_right_row = 3. + ls_column_formula-table_left_column_int = 1. + ls_column_formula-table_right_column_int = 2. + INSERT ls_column_formula INTO TABLE mt_column_formulas. + + " Column Formula in table D1:E4 (for unknown reason, bottom_right_row is last actual row minus 1) + CLEAR ls_column_formula. + ls_column_formula-id = 2. + ls_column_formula-column = 4. + ls_column_formula-table_top_left_row = 1. + ls_column_formula-table_bottom_right_row = 3. + ls_column_formula-table_left_column_int = 4. + ls_column_formula-table_right_column_int = 5. + INSERT ls_column_formula INTO TABLE mt_column_formulas. + + ENDMETHOD. + + METHOD success. + + zcl_excel_worksheet=>check_cell_column_formula( + it_column_formulas = mt_column_formulas + ip_column_formula_id = 1 + ip_formula = '' + ip_value = '' + ip_row = 2 + ip_column = 1 ). + + ENDMETHOD. + + METHOD fails_both_formula_id_value. + should_fail( ip_formula_id = 1 ip_formula = '' ip_value = '3.14' ip_row = 2 ip_column = 1 ip_exp = c_messages-formula_id_only_is_possible ). + ENDMETHOD. + + METHOD fails_both_formula_id_formula. + should_fail( ip_formula_id = 1 ip_formula = 'A2' ip_value = '' ip_row = 2 ip_column = 1 ip_exp = c_messages-formula_id_only_is_possible ). + ENDMETHOD. + + METHOD formula_id_not_found. + should_fail( ip_formula_id = 3 ip_row = 1 ip_column = 1 ip_exp = c_messages-column_formula_id_not_found ). + ENDMETHOD. + + METHOD outside_table_fails__above. + should_fail( ip_formula_id = 2 ip_row = 1 ip_column = 1 ip_exp = c_messages-formula_not_in_this_table ). + ENDMETHOD. + + METHOD outside_table_fails__below. + should_fail( ip_formula_id = 2 ip_row = 5 ip_column = 1 ip_exp = c_messages-formula_not_in_this_table ). + ENDMETHOD. + + METHOD outside_table_fails__left. + should_fail( ip_formula_id = 2 ip_row = 2 ip_column = 0 ip_exp = c_messages-formula_not_in_this_table ). + ENDMETHOD. + + METHOD outside_table_fails__right. + should_fail( ip_formula_id = 2 ip_row = 2 ip_column = 3 ip_exp = c_messages-formula_not_in_this_table ). + ENDMETHOD. + + METHOD must_be_in_same_column. + should_fail( ip_formula_id = 1 ip_row = 2 ip_column = 2 ip_exp = c_messages-formula_in_other_column ). + ENDMETHOD. + + METHOD should_fail. + + DATA: lo_exception TYPE REF TO zcx_excel. + + TRY. + zcl_excel_worksheet=>check_cell_column_formula( + EXPORTING + it_column_formulas = mt_column_formulas + ip_column_formula_id = ip_formula_id + ip_formula = ip_formula + ip_value = ip_value + ip_row = ip_row + ip_column = ip_column ). + cl_abap_unit_assert=>fail( msg = |Should have failed with error "{ ip_exp }"| ). + CATCH zcx_excel INTO lo_exception. + cl_abap_unit_assert=>assert_equals( act = lo_exception->get_text( ) exp = ip_exp msg = ip_exp ). + ENDTRY. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcl_excel_writer_2007.clas.abap b/src/zcl_excel_writer_2007.clas.abap index ad008ba..e51797b 100644 --- a/src/zcl_excel_writer_2007.clas.abap +++ b/src/zcl_excel_writer_2007.clas.abap @@ -13,6 +13,13 @@ CLASS zcl_excel_writer_2007 DEFINITION *"* protected components of class ZCL_EXCEL_WRITER_2007 *"* do not include other source files here!!! + TYPES: BEGIN OF mty_column_formula_used, + id TYPE zexcel_s_cell_data-column_formula_id, + si TYPE string, + "! type: shared, etc. + t TYPE string, + END OF mty_column_formula_used, + mty_column_formulas_used TYPE HASHED TABLE OF mty_column_formula_used WITH UNIQUE KEY id. CONSTANTS c_content_types TYPE string VALUE '[Content_Types].xml'. "#EC NOTEXT CONSTANTS c_docprops_app TYPE string VALUE 'docProps/app.xml'. "#EC NOTEXT CONSTANTS c_docprops_core TYPE string VALUE 'docProps/core.xml'. "#EC NOTEXT @@ -197,6 +204,25 @@ CLASS zcl_excel_writer_2007 DEFINITION io_document TYPE REF TO if_ixml_document RETURNING VALUE(ep_content) TYPE xstring. + METHODS create_xl_sheet_column_formula + IMPORTING + io_document TYPE REF TO if_ixml_document + it_column_formulas TYPE zcl_excel_worksheet=>mty_th_column_formula + is_sheet_content TYPE zexcel_s_cell_data + EXPORTING + eo_element TYPE REF TO if_ixml_element + CHANGING + ct_column_formulas_used TYPE mty_column_formulas_used + cv_si TYPE i + RAISING + zcx_excel. + METHODS is_formula_shareable + IMPORTING + ip_formula TYPE string + RETURNING + VALUE(ep_shareable) TYPE abap_bool + RAISING + zcx_excel. PRIVATE SECTION. *"* private components of class ZCL_EXCEL_WRITER_2007 @@ -5386,6 +5412,83 @@ CLASS zcl_excel_writer_2007 IMPLEMENTATION. ENDMETHOD. + METHOD create_xl_sheet_column_formula. + + TYPES: ls_column_formula_used TYPE mty_column_formula_used, + lv_column_alpha TYPE zexcel_cell_column_alpha, + lv_top_cell_coords TYPE zexcel_cell_coords, + lv_bottom_cell_coords TYPE zexcel_cell_coords, + lv_cell_coords TYPE zexcel_cell_coords, + lv_ref_value TYPE string, + lv_test_shared TYPE string, + lv_si TYPE i, + lv_1st_line_shared_formula TYPE abap_bool. + DATA: lv_value TYPE string, + ls_column_formula_used TYPE mty_column_formula_used, + lv_column_alpha TYPE zexcel_cell_column_alpha, + lv_top_cell_coords TYPE zexcel_cell_coords, + lv_bottom_cell_coords TYPE zexcel_cell_coords, + lv_cell_coords TYPE zexcel_cell_coords, + lv_ref_value TYPE string, + lv_1st_line_shared_formula TYPE abap_bool. + FIELD-SYMBOLS: TYPE zcl_excel_worksheet=>mty_s_column_formula, + TYPE mty_column_formula_used. + + + READ TABLE it_column_formulas WITH TABLE KEY id = is_sheet_content-column_formula_id ASSIGNING . + ASSERT sy-subrc = 0. + + lv_value = -formula. + lv_1st_line_shared_formula = abap_false. + eo_element = io_document->create_simple_element( name = 'f' + parent = io_document ). + READ TABLE ct_column_formulas_used WITH TABLE KEY id = is_sheet_content-column_formula_id ASSIGNING . + IF sy-subrc <> 0. + CLEAR ls_column_formula_used. + ls_column_formula_used-id = is_sheet_content-column_formula_id. + IF is_formula_shareable( ip_formula = lv_value ) = abap_true. + ls_column_formula_used-t = 'shared'. + ls_column_formula_used-si = cv_si. + CONDENSE ls_column_formula_used-si. + cv_si = cv_si + 1. + lv_1st_line_shared_formula = abap_true. + ENDIF. + INSERT ls_column_formula_used INTO TABLE ct_column_formulas_used ASSIGNING . + ENDIF. + + IF lv_1st_line_shared_formula = abap_true OR -t <> 'shared'. + lv_column_alpha = zcl_excel_common=>convert_column2alpha( ip_column = is_sheet_content-cell_column ). + lv_top_cell_coords = |{ lv_column_alpha }{ -table_top_left_row + 1 }|. + lv_bottom_cell_coords = |{ lv_column_alpha }{ -table_bottom_right_row + 1 }|. + lv_cell_coords = |{ lv_column_alpha }{ is_sheet_content-cell_row }|. + IF lv_top_cell_coords = lv_cell_coords. + lv_ref_value = |{ lv_top_cell_coords }:{ lv_bottom_cell_coords }|. + ELSE. + lv_ref_value = |{ lv_cell_coords }:{ lv_bottom_cell_coords }|. + lv_value = zcl_excel_common=>shift_formula( + iv_reference_formula = lv_value + iv_shift_cols = 0 + iv_shift_rows = is_sheet_content-cell_row - -table_top_left_row - 1 ). + ENDIF. + ENDIF. + + IF -t = 'shared'. + eo_element->set_attribute( name = 't' + value = -t ). + eo_element->set_attribute( name = 'si' + value = -si ). + IF lv_1st_line_shared_formula = abap_true. + eo_element->set_attribute( name = 'ref' + value = lv_ref_value ). + eo_element->set_value( value = lv_value ). + ENDIF. + ELSE. + eo_element->set_value( value = lv_value ). + ENDIF. + + ENDMETHOD. + + METHOD create_xl_sheet_pagebreaks. DATA: lo_pagebreaks TYPE REF TO zcl_excel_worksheet_pagebreaks, lt_pagebreaks TYPE zcl_excel_worksheet_pagebreaks=>tt_pagebreak_at, @@ -5760,6 +5863,8 @@ CLASS zcl_excel_writer_2007 IMPLEMENTATION. lv_value TYPE string, lv_style_guid TYPE zexcel_cell_style. + DATA: lt_column_formulas_used TYPE mty_column_formulas_used, + lv_si TYPE i. FIELD-SYMBOLS: TYPE zexcel_s_cell_data, LIKE LINE OF lts_row_outlines. @@ -6016,6 +6121,18 @@ CLASS zcl_excel_writer_2007 IMPLEMENTATION. CONDENSE lv_value. lo_element_4->set_value( value = lv_value ). lo_element_3->append_child( new_child = lo_element_4 ). " fomula node + ELSEIF -column_formula_id <> 0. + create_xl_sheet_column_formula( + EXPORTING + io_document = io_document + it_column_formulas = io_worksheet->column_formulas + is_sheet_content = + IMPORTING + eo_element = lo_element_4 + CHANGING + ct_column_formulas_used = lt_column_formulas_used + cv_si = lv_si ). + lo_element_3->append_child( new_child = lo_element_4 ). ELSEIF -cell_value IS NOT INITIAL "cell can have just style or formula AND -cell_value <> lc_dummy_cell_content. IF -data_type IS NOT INITIAL. @@ -7137,6 +7254,7 @@ CLASS zcl_excel_writer_2007 IMPLEMENTATION. lo_element_root TYPE REF TO if_ixml_element, lo_element TYPE REF TO if_ixml_element, lo_element2 TYPE REF TO if_ixml_element, + lo_element3 TYPE REF TO if_ixml_element, lv_table_name TYPE string, lv_id TYPE i, lv_match TYPE i, @@ -7249,6 +7367,15 @@ CLASS zcl_excel_writer_2007 IMPLEMENTATION. value = ls_fieldcat-totals_function ). ENDIF. + IF ls_fieldcat-column_formula IS NOT INITIAL. + lv_value = ls_fieldcat-column_formula. + CONDENSE lv_value. + lo_element3 = lo_document->create_simple_element_ns( name = 'calculatedColumnFormula' + parent = lo_element2 ). + lo_element3->set_value( lv_value ). + lo_element2->append_child( new_child = lo_element3 ). + ENDIF. + lo_element->append_child( new_child = lo_element2 ). ENDLOOP. @@ -7635,6 +7762,21 @@ CLASS zcl_excel_writer_2007 IMPLEMENTATION. ENDMETHOD. + METHOD is_formula_shareable. + DATA: lv_test_shared TYPE string. + + ep_shareable = abap_false. + IF ip_formula NA '!'. + lv_test_shared = zcl_excel_common=>shift_formula( + iv_reference_formula = ip_formula + iv_shift_cols = 1 + iv_shift_rows = 1 ). + IF lv_test_shared <> ip_formula. + ep_shareable = abap_true. + ENDIF. + ENDIF. + ENDMETHOD. + METHOD set_vml_shape_footer. CONSTANTS: lc_shape TYPE string VALUE '', diff --git a/src/zcl_excel_writer_2007.clas.testclasses.abap b/src/zcl_excel_writer_2007.clas.testclasses.abap new file mode 100644 index 0000000..9d2e3cf --- /dev/null +++ b/src/zcl_excel_writer_2007.clas.testclasses.abap @@ -0,0 +1,170 @@ +*"* use this source file for your ABAP unit test classes + +CLASS ltc_column_formula DEFINITION DEFERRED. +CLASS ltc_is_formula_shareable DEFINITION DEFERRED. +CLASS zcl_excel_writer_2007 DEFINITION LOCAL FRIENDS + ltc_column_formula + ltc_is_formula_shareable. + +CLASS ltc_column_formula DEFINITION FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + + METHODS one_column_formula FOR TESTING RAISING cx_static_check. + METHODS two_column_formulas FOR TESTING RAISING cx_static_check. + + METHODS setup. + METHODS set_cell + IMPORTING + is_cell_data TYPE zexcel_s_cell_data + RAISING + zcx_excel. + METHODS get_xml + RETURNING + VALUE(rv_xml) TYPE string. + + DATA lo_document TYPE REF TO if_ixml_document. + DATA lo_excel TYPE REF TO zcl_excel_writer_2007. + DATA lo_ixml TYPE REF TO if_ixml. + DATA lo_root TYPE REF TO if_ixml_element. + DATA lt_column_formulas TYPE zcl_excel_worksheet=>mty_th_column_formula. + DATA lt_column_formulas_used TYPE zcl_excel_writer_2007=>mty_column_formulas_used. + DATA lv_si TYPE i. + +ENDCLASS. + + +CLASS ltc_is_formula_shareable DEFINITION FOR TESTING + RISK LEVEL HARMLESS + DURATION SHORT. + + PRIVATE SECTION. + + METHODS is_formula_shareable FOR TESTING RAISING cx_static_check. + +ENDCLASS. + + +CLASS ltc_column_formula IMPLEMENTATION. + + METHOD one_column_formula. + + DATA ls_cell_data TYPE zexcel_s_cell_data. + DATA lv_xml TYPE string. + + ls_cell_data-cell_row = 2. + ls_cell_data-cell_column = 1. + ls_cell_data-column_formula_id = 1. + set_cell( ls_cell_data ). + + lv_xml = get_xml( ). + + cl_abap_unit_assert=>assert_equals( act = lv_xml exp = 'A2' ). + + ENDMETHOD. + + METHOD two_column_formulas. + + DATA ls_cell_data TYPE zexcel_s_cell_data. + DATA lv_xml TYPE string. + + ls_cell_data-cell_row = 2. + ls_cell_data-cell_column = 1. + ls_cell_data-column_formula_id = 1. + set_cell( ls_cell_data ). + + ls_cell_data-cell_row = 3. + ls_cell_data-cell_column = 1. + ls_cell_data-column_formula_id = 1. + set_cell( ls_cell_data ). + + lv_xml = get_xml( ). + + cl_abap_unit_assert=>assert_equals( act = lv_xml exp = 'A2' + && '' ). + ENDMETHOD. + + + METHOD set_cell. + + DATA lo_cell TYPE REF TO if_ixml_element. + DATA lo_formula TYPE REF TO if_ixml_element. + + lo_cell = lo_document->create_element( 'c' ). + lo_cell->set_attribute( name = 'r' value = |R{ is_cell_data-cell_row }C{ is_cell_data-cell_column }| ). + lo_root->append_child( lo_cell ). + + lo_excel->create_xl_sheet_column_formula( + EXPORTING + io_document = lo_document + it_column_formulas = lt_column_formulas + is_sheet_content = is_cell_data + IMPORTING + eo_element = lo_formula + CHANGING + ct_column_formulas_used = lt_column_formulas_used + cv_si = lv_si ). + lo_cell->append_child( lo_formula ). + + ENDMETHOD. + + + METHOD get_xml. + + DATA lo_stream_factory TYPE REF TO if_ixml_stream_factory. + DATA lo_ostream TYPE REF TO if_ixml_ostream. + DATA lo_renderer TYPE REF TO if_ixml_renderer. + + lo_stream_factory = lo_ixml->create_stream_factory( ). + lo_ostream = lo_stream_factory->create_ostream_cstring( string = rv_xml ). + lo_renderer = lo_ixml->create_renderer( ostream = lo_ostream document = lo_document ). + lo_renderer->render( ). + CALL TRANSFORMATION id SOURCE XML rv_xml RESULT XML rv_xml OPTIONS xml_header = 'no'. + IF strlen( rv_xml ) >= 2. + rv_xml = substring( val = rv_xml off = 1 ). " Remove Byte Order Mark (one character in Unicode systems) + ENDIF. + FIND REGEX '^(.*)$' IN rv_xml SUBMATCHES rv_xml. + + ENDMETHOD. + + + METHOD setup. + + DATA ls_column_formula TYPE zcl_excel_worksheet=>mty_s_column_formula. + + lo_ixml = cl_ixml=>create( ). + lo_document = lo_ixml->create_document( ). + lo_root = lo_document->create_element( 'dummy' ). + lo_document->append_child( lo_root ). + + CREATE OBJECT lo_excel. + + ls_column_formula-id = 1. + ls_column_formula-formula = 'A2'. + ls_column_formula-table_top_left_row = 1. + ls_column_formula-table_bottom_right_row = 3. + INSERT ls_column_formula INTO TABLE lt_column_formulas. + + ENDMETHOD. + +ENDCLASS. + + +CLASS ltc_is_formula_shareable IMPLEMENTATION. + + METHOD is_formula_shareable. + + DATA lo_excel TYPE REF TO zcl_excel_writer_2007. + + CREATE OBJECT lo_excel. + + cl_abap_unit_assert=>assert_equals( act = lo_excel->is_formula_shareable( 'TblFlights[[#This Row],[Airfare]]+222' ) exp = abap_false ). " [@Airfare]+222 + cl_abap_unit_assert=>assert_equals( act = lo_excel->is_formula_shareable( 'OtherSheet!A2' ) exp = abap_false ). + cl_abap_unit_assert=>assert_equals( act = lo_excel->is_formula_shareable( 'D2+100' ) exp = abap_true ). + cl_abap_unit_assert=>assert_equals( act = lo_excel->is_formula_shareable( 'A1&";"&_xlfn.IFS(TRUE,NamedRange)' ) exp = abap_true ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcl_excel_writer_2007.clas.xml b/src/zcl_excel_writer_2007.clas.xml index ce077b7..a47de59 100644 --- a/src/zcl_excel_writer_2007.clas.xml +++ b/src/zcl_excel_writer_2007.clas.xml @@ -10,6 +10,7 @@ X X X + X diff --git a/src/zexcel_s_cell_data.tabl.xml b/src/zexcel_s_cell_data.tabl.xml index 6e153a2..f498d92 100644 --- a/src/zexcel_s_cell_data.tabl.xml +++ b/src/zexcel_s_cell_data.tabl.xml @@ -7,6 +7,7 @@ E INTTAB Cell data + E 1 @@ -52,6 +53,16 @@ 0 E + + COLUMN_FORMULA_ID + 0 + X + 000004 + INT4 + 000010 + INT4 + Column Formula ID + diff --git a/src/zexcel_s_fieldcatalog.tabl.xml b/src/zexcel_s_fieldcatalog.tabl.xml index ad9f5c8..12dcfb6 100644 --- a/src/zexcel_s_fieldcatalog.tabl.xml +++ b/src/zexcel_s_fieldcatalog.tabl.xml @@ -7,6 +7,7 @@ E INTTAB Fieldcatalog for Table Binding + E 1 @@ -101,6 +102,15 @@ 000001 CHAR + + COLUMN_FORMULA + 0 + g + 000008 + STRG + STRG + Column Formula +