ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SAP ABAP利用SMW0与CL_FDT_XLSP实现Excel模板化报表生成

SAP ABAP利用SMW0与CL_FDT_XLSP实现Excel模板化报表生成 1. 项目缘起当标准ALV报表无法满足业务需求时在SAP ABAP的日常开发中我们经常需要生成格式复杂、样式多变的Excel报表。标准的ALV输出到Excel功能REUSE_ALV_GRID_DISPLAY或CL_SALV_TABLE虽然方便但在面对固定表头、多级标题、特定单元格合并、预置公式或公司标准模板时就显得力不从心了。业务部门常常会扔过来一个精心设计的Excel文件说“就按这个模板的样式和布局把数据填进去然后发给我。” 这时候如果还想着用ALV去“画”出这个模板不仅开发工作量巨大后期模板稍有改动代码就得大动干戈。几年前我接手过一个财务月度报表的开发任务。业务提供的模板包含了合并单元格的标题、带背景色的分类小计行、以及一些用于校验的预计算公式。最初尝试用ALV的布局控制去模拟代码写得异常复杂且脆弱一个单元格的偏移量算错整个报表的格式就全乱了。更头疼的是每次业务调整模板哪怕只是增加一列我都需要重新计算所有输出位置的坐标调试过程苦不堪言。直到我系统地掌握了通过SMW0读取Excel模板并写入数据的方法才真正从这种“格式地狱”中解脱出来。这种方法的核心思想是“模板与数据分离”业务人员用Excel设计他们想要的任何样式模板程序员只负责在ABAP里把数据精准地“灌入”模板的指定位置。今天我就把这个高效、稳定的方案完整地分享出来。2. 理解SMW0SAP的二进制对象仓库在深入代码之前我们必须先搞清楚SMW0是什么以及它在这个方案中扮演的角色。SMW0Web Repository Object Maintenance是SAP系统中的一个中央仓库用于存储和管理各种二进制和文本对象例如图像、HTML页面、样式表、PDF表单当然也包括我们的Excel模板文件。你可以把它理解成SAP服务器上的一个“内置网盘”或“资源目录”。为什么要把Excel模板上传到SMW0集中管理与版本控制模板作为系统配置的一部分被统一管理与传输请求绑定便于在不同系统开发、测试、生产间迁移和版本追溯。避免了将模板文件放在应用服务器某个目录下带来的管理混乱和权限问题。程序与模板解耦你的ABAP程序不再需要硬编码服务器文件路径如/usr/sap/...。程序通过一个逻辑对象名如ZEXCEL_TEMPLATE_01来引用模板即使底层文件更换只要对象名不变程序就无需修改。安全性通过SMW0的权限控制可以限制谁有权修改模板保证了模板的稳定性和安全性。SMW0中的关键概念对象名 (Object Name)你为模板定义的唯一标识符通常以Z或Y开头例如ZMM_PO_REPORT_TEMPLATE。MIME类型 (MIME Type)对于Excel文件通常是application/vnd.ms-excel针对.xls或application/vnd.openxmlformats-officedocument.spreadsheetml.sheet针对.xlsx。正确设置MIME类型至关重要它告诉SAP如何正确处理这个二进制流。开发类/包 (Development Class/Package)指定模板所属的包便于组织和管理。将模板上传至SMW0是这个方案的第一步也是基石。只有模板安稳地待在仓库里后续的读取和填充才有可靠的数据源。3. 从SMW0到内表读取Excel模板的完整流程有了模板下一步就是如何在ABAP程序中把它读出来。这里的目标不是用GUI_UPLOAD读一个本地文件而是从SMW0仓库中获取二进制流。整个过程可以分解为几个清晰的步骤。3.1 步骤一使用WWWDATA表读取模板二进制数据SAP将SMW0中存储的对象内容主要放在WWWDATA这张数据库表中。我们需要编写一个函数或方法来获取它。首先定义必要的变量DATA: lv_objkey TYPE wwwdata-objkey, 对象键即对象名 lv_mime_type TYPE wwwdatatab-mime_type, MIME类型 lt_bintab TYPE TABLE OF w3mime, 存储二进制数据的内部表 lv_size TYPE i. 文件大小然后构建读取逻辑。objkey就是你在SMW0中创建的对象名。lv_objkey ‘ZEXCEL_TEMPLATE_01‘. “ 替换为你的模板对象名 “ 方法1使用函数模块 READ_OBJECT CALL FUNCTION ‘READ_OBJECT‘ EXPORTING key lv_objkey IMPORTING mime_type lv_mime_type TABLES object lt_bintab EXCEPTIONS object_not_found 1 OTHERS 2. IF sy-subrc 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. ENDIF. “ 获取数据大小 DESCRIBE TABLE lt_bintab LINES lv_size. IF lv_size 0. MESSAGE ‘从SMW0读取的模板文件为空‘ TYPE ‘E‘. ENDIF.关键点与避坑经验WWWDATA表结构WWWDATA表是簇表其真实数据存储在WRF_MIME这样的底层结构中。直接查表很麻烦因此强烈推荐使用封装好的函数模块READ_OBJECT它帮你处理了所有底层细节。对象名大小写SMW0中的对象名通常是大写存储的但在代码中传入时最好使用大写以避免不必要的麻烦。异常处理务必处理object_not_found等异常。在生产环境中模板可能被误删或未传输清晰的错误信息能快速定位问题。性能考量对于非常大的Excel模板几十MB将其存储在SMW0中并频繁读取可能不是最佳选择需评估性能。但对于几百KB到几MB的常规报表模板完全可行。3.2 步骤二将二进制流转换为XSTRING从WWWDATA读出的lt_bintab是类型为W3MIME的行表每行约1023字节。为了便于后续使用比如用OOABAP的CL_FDT_XLSP或ABAP2XLSX库处理我们通常将其转换为一个连续的二进制字符串XSTRING。DATA: lv_xstring TYPE xstring. “ 调用标准函数将内表转换为XSTRING CALL FUNCTION ‘SCMS_BINARY_TO_XSTRING‘ EXPORTING input_length lv_size IMPORTING buffer lv_xstring TABLES binary_tab lt_bintab EXCEPTIONS failed 1 OTHERS 2. IF sy-subrc 0. MESSAGE ‘转换二进制数据为XSTRING失败‘ TYPE ‘E‘. ENDIF.至此lv_xstring变量中就包含了完整的Excel模板文件的二进制内容。你可以把它想象成在内存中拥有了那个模板文件的一个完整副本。4. 向模板注入灵魂多种数据写入技术详解拿到了模板的二进制流接下来就是最核心的部分如何把ABAP中的数据通常来自内表写入到这个模板的指定位置。这里有几种主流的技术路径各有优劣需要根据你的SAP版本、技术栈和复杂度要求来选择。4.1 方案A使用SAP标准类CL_FDT_XLSP推荐用于较新系统这是SAP官方提供的、相对现代和强大的Excel处理类属于FDT - Form Data Toolkit的一部分。它支持.xlsx格式功能比较全面。核心操作流程创建实例与加载模板DATA: lo_excel TYPE REF TO cl_fdt_xlsp, lo_worksheet TYPE REF TO cl_fdt_xlsp_worksheet, lo_range TYPE REF TO cl_fdt_xlsp_range. CREATE OBJECT lo_excel. “ 将之前得到的XSTRING模板加载到Excel对象中 lo_excel-load( EXPORTING i_xstring lv_xstring ).获取工作表并定位单元格“ 获取第一个工作表索引从0开始 lo_worksheet lo_excel-get_worksheet( i_index 0 ). “ 定义一个范围Range例如我们要从B2单元格开始写数据 lo_range lo_worksheet-get_range( iv_address ‘B2‘ ).写入数据CL_FDT_XLSP提供了多种写入方法。写入单个值lo_range-set_value( iv_value ‘标题‘ )。写入内表最常用这是将ABAP内表数据批量填充到一片连续区域的关键。DATA: lt_data TYPE TABLE OF zmy_structure. “ 你的数据内表 “ ... 填充lt_data ... “ 将内表数据写入到以B2为左上角的区域 lo_range-set_table( EXPORTING it_table lt_data ).set_table方法会自动根据内表的行数和列数将数据从起始单元格开始向下向右展开填充。内表的字段顺序决定了写入Excel的列顺序。保存为新的XSTRINGDATA: lv_output_xstring TYPE xstring. lo_excel-save( IMPORTING e_xstring lv_output_xstring ).优缺点与心得优点官方支持与SAP系统集成度好支持.xlsx性能不错能处理较大的文件。缺点文档相对较少某些高级格式控制可能需要深入挖掘其API。重要提示set_table方法在写入时会覆盖目标区域内所有原有内容。如果你的模板在数据区域旁边有固定的边框或公式需要小心规划写入的起始位置和区域大小。4.2 方案B使用开源库ABAP2XLSXABAP2XLSX是一个在GitHub上非常流行的开源ABAP库功能极其强大几乎可以完成所有Excel操作并且有活跃的社区和丰富的示例。基本使用模式DATA: lo_excel TYPE REF TO zcl_excel, lo_worksheet TYPE REF TO zcl_excel_worksheet, lo_reader TYPE REF TO zif_excel_reader. “ 1. 使用Reader加载模板 CREATE OBJECT lo_reader TYPE zcl_excel_reader_2007. lo_excel lo_reader-load( i_excel2007 lv_xstring ). “ 2. 获取工作表 lo_worksheet lo_excel-get_active_worksheet( ). “ 3. 写入数据方式灵活 “ 写入单个单元格 lo_worksheet-set_cell( ip_column ‘B‘ ip_row 2 ip_value ‘项目编号‘ ). “ 通过行列索引写入更编程化 DATA(lv_col_index) zcl_excel_commonconvert_column2int( ‘C‘ ). lo_worksheet-set_cell( ip_column lv_col_index ip_row 3 ip_value ‘1001‘ ). “ 批量写入内表需要循环 DATA: ls_data LIKE LINE OF lt_data. LOOP AT lt_data INTO ls_data. DATA(lv_row) sy-tabix 1. “ 假设从第2行开始写表头在第1行 lo_worksheet-set_cell( ip_column 1 ip_row lv_row ip_value ls_data-matnr ). lo_worksheet-set_cell( ip_column 2 ip_row lv_row ip_value ls_data-maktx ). “ ... 其他字段 ENDLOOP.优缺点与心得优点功能最全社区支持好示例丰富可以创建复杂的格式、图表、冻结窗格等。缺点需要额外安装这个开源库到你的SAP系统通常通过传输请求。对于简单的模板填充可能有点“杀鸡用牛刀”。性能提示在循环中大量调用set_cell写入单个单元格时如果数据量很大上万行可能会比较慢。ABAP2XLSX提供了update_cell等批量接口或者可以考虑先准备好数据再一次性设置。4.3 方案C基于OLE的旧式方法不推荐仅作了解在CL_FDT_XLSP和ABAP2XLSX普及之前很多开发会使用OLEObject Linking and Embedding技术在服务器端调用Microsoft Excel的COM接口来操作。这种方法严重依赖服务器上安装的Excel有性能、稳定性和许可问题在现代SAP开发中已基本被弃用。这里仅简要提及以示对比。“ 伪代码示意过程 CREATE OBJECT excel ‘Excel.Application‘. CALL METHOD OF excel ‘Workbooks‘ workbook. CALL METHOD OF workbook ‘Open‘ worksheet EXPORTING #1 ‘服务器文件路径‘. “ ... 通过OLE操作单元格 ... CALL METHOD OF workbook ‘SaveAs‘ EXPORTING #1 ‘新文件路径‘. FREE OBJECT: worksheet, workbook, excel.为什么不推荐依赖性与稳定性要求SAP应用服务器上安装有对应版本的Excel且OLE自动化调用不稳定容易导致后台作业挂起或dump。性能与资源会启动一个完整的Excel进程消耗大量服务器资源不适合高并发。无头环境在纯后台作业或无GUI的环境中无法运行。许可风险可能涉及Microsoft Office的服务器端许可问题。结论对于“读取SMW0模板并写入数据”这个场景首选CL_FDT_XLSP如果系统版本较低未提供此类或需要极其复杂的功能则考虑引入ABAP2XLSX。坚决避免使用OLE方案。5. 实战演练构建一个可复用的报表生成函数模块理论讲完了我们动手封装一个实用的函数模块。这个函数将集成从SMW0读取模板到写入数据并返回文件的所有步骤力求健壮和通用。假设我们要生成一个采购订单清单报表模板ZPO_LIST_TEMPLATE已上传至SMW0数据区域从B5单元格开始。FUNCTION z_generate_excel_from_smw0. *“---------------------------------------------------------------------- *“*“本地接口 *“ IMPORTING *“ VALUE(IV_TEMPLATE_NAME) TYPE WWWDATA-OBJKEY *“ VALUE(IV_START_CELL) TYPE STRING DEFAULT ‘B5‘ *“ VALUE(IT_DATA_TABLE) TYPE TABLE *“ EXPORTING *“ VALUE(EV_XSTRING) TYPE XSTRING *“ VALUE(EV_FILENAME) TYPE STRING *“ EXCEPTIONS *“ TEMPLATE_NOT_FOUND *“ DATA_WRITE_ERROR *“ INTERNAL_ERROR *“---------------------------------------------------------------------- DATA: lt_bintab TYPE TABLE OF w3mime, lv_size TYPE i, lv_xstring_template TYPE xstring, lo_excel TYPE REF TO cl_fdt_xlsp, lo_worksheet TYPE REF TO cl_fdt_xlsp_worksheet, lo_range TYPE REF TO cl_fdt_xlsp_range. “ 1. 从SMW0读取模板 CLEAR: lt_bintab, lv_xstring_template. CALL FUNCTION ‘READ_OBJECT‘ EXPORTING key iv_template_name IMPORTING mime_type DATA(lv_mime_type) TABLES object lt_bintab EXCEPTIONS object_not_found 1 OTHERS 2. IF sy-subrc 0. RAISE template_not_found. ENDIF. DESCRIBE TABLE lt_bintab LINES lv_size. IF lv_size 0. RAISE template_not_found. ENDIF. CALL FUNCTION ‘SCMS_BINARY_TO_XSTRING‘ EXPORTING input_length lv_size IMPORTING buffer lv_xstring_template TABLES binary_tab lt_bintab EXCEPTIONS failed 1 OTHERS 2. IF sy-subrc 0. RAISE internal_error. ENDIF. “ 2. 使用CL_FDT_XLSP加载模板并写入数据 TRY. CREATE OBJECT lo_excel. lo_excel-load( i_xstring lv_xstring_template ). lo_worksheet lo_excel-get_worksheet( i_index 0 ). lo_range lo_worksheet-get_range( iv_address iv_start_cell ). “ 关键的一步将内表数据写入模板 lo_range-set_table( it_table it_data_table ). “ 3. 保存填充后的Excel为新的XSTRING lo_excel-save( IMPORTING e_xstring ev_xstring ). CATCH cx_root INTO DATA(lx_error). “ 记录详细的错误信息到系统日志或应用日志 DATA(lv_error_text) lx_error-get_text( ). “ 可以在这里使用MESSAGE ID ... 或者记录到自定义日志表 RAISE data_write_error. ENDTRY. “ 4. 生成建议的文件名可选 ev_filename |Report_{ sy-datum }_{ sy-uzeit }.xlsx|. ENDFUNCTION.调用示例DATA: lt_po_list TYPE TABLE OF zpo_item, lv_xstring TYPE xstring, lv_filename TYPE string. “ 从数据库或其他逻辑中获取数据 SELECT * FROM ekpo INTO CORRESPONDING FIELDS OF TABLE lt_po_list WHERE ebeln IN so_ebeln. TRY. CALL FUNCTION ‘Z_GENERATE_EXCEL_FROM_SMW0‘ EXPORTING iv_template_name ‘ZPO_LIST_TEMPLATE‘ iv_start_cell ‘B5‘ it_data_table lt_po_list IMPORTING ev_xstring lv_xstring ev_filename lv_filename EXCEPTIONS template_not_found 1 data_write_error 2 internal_error 3 OTHERS 4. IF sy-subrc 0. “ 成功现在lv_xstring就是包含数据的Excel文件。 “ 可以调用GUI_DOWNLOAD让用户下载或通过SOXI发送邮件等。 ELSE. “ 处理错误 ENDIF. CATCH cx_root. ENDTRY.封装的好处复用性任何需要按模板生成Excel的程序都可以调用此函数。可维护性模板读取和核心写入逻辑集中在一处修改方便。错误处理统一的异常管理便于问题定位。6. 进阶技巧与深度避坑指南掌握了基本流程后下面这些从实际项目中总结的经验和技巧能帮你避开很多“坑”让方案更加稳健和高效。6.1 动态确定数据写入的起始位置上面的例子中起始单元格IV_START_CELL是硬编码的。但在更复杂的模板中数据起始行可能因为表头行数变化而改变。一个更好的做法是在模板中设置一个定位标记。方法在模板的数据区域左上角单元格比如B5填入一个特殊的、不会在业务数据中出现的字符串例如##DATA_START##。在ABAP程序中先读取模板遍历可能的行或使用CL_FDT_XLSP的查找功能找到包含这个标记的单元格其位置就是你的数据起始位置。找到后可以用空字符串覆盖该标记再写入真实数据。“ 伪代码逻辑 DATA: lv_found TYPE abap_bool, lv_search_value TYPE string VALUE ‘##DATA_START##‘. “ 假设我们只在第5到第20行之间查找B列 DO 16 TIMES. “ 20-51 DATA(lv_current_row) 4 sy-index. “ 从第5行开始 lo_worksheet-get_range( iv_address |B{ lv_current_row }| )-get_value( IMPORTING ev_value DATA(lv_cell_value) ). IF lv_cell_value lv_search_value. “ 找到起始位置 lv_start_row lv_current_row. lv_found abap_true. “ 清空标记 lo_worksheet-get_range( iv_address |B{ lv_current_row }| )-set_value( iv_value ‘‘ ). EXIT. ENDIF. ENDDO. IF lv_found abap_false. RAISE EXCEPTION TYPE cx_dynamic_check. ENDIF.6.2 处理模板中的公式和格式当你在模板的某些单元格比如合计行预设了Excel公式如SUM(B5:B100)使用set_table等方法写入数据时这些公式会被覆盖掉这是一个常见的陷阱。解决方案写入数据后重新设置公式在set_table写入数据后再单独对需要公式的单元格调用set_formula方法如果所用类支持。“ 假设数据从B5写到B104合计在B105 lo_worksheet-get_range( iv_address ‘B105‘ )-set_formula( iv_formula ‘SUM(B5:B104)‘ ).使用命名区域 (Named Range)在Excel模板中为数据区域定义一个名称如Data_Range。这样公式可以写为SUM(Data_Range)。在ABAP中写入数据时虽然单元格值变了但名称Data_Range引用的区域没变公式依然有效。但需要注意set_table是否会破坏名称引用需要测试。预留公式行分区域写入不把公式所在行包含在set_table的写入区域内。例如数据写在B5:B100公式在B101。先写数据区域再单独写其他静态行或表头。关于格式字体、颜色、边框CL_FDT_XLSP和ABAP2XLSX都提供了丰富的API进行设置。但更佳实践是所有样式都在Excel模板中预先设置好。ABAP程序只负责填充“空白”的数据单元格。这样做到了最大程度的“表现与逻辑分离”业务人员可以随意调整样式而无需开发介入。6.3 大文件处理与性能优化当数据量达到数万行时需要注意性能。分批处理如果可能将数据分批写入。但set_table通常是最高效的批量操作。禁用自动计算在写入大量数据前如果类库支持可以尝试禁用Excel的自动计算写入完成后再开启。CL_FDT_XLSP可能没有直接接口但ABAP2XLSX的lo_excel-zexcel-calculation_on属性可以控制。内存监控操作非常大的XSTRING时注意ABAP工作进程的内存使用。确保有足够的em/initial_size配置。最终输出生成最终的XSTRING后如果直接提供给用户下载是流式传输对服务器内存压力不大。但如果需要临时保存在内表或数据库中要考虑大小。6.4 调试与日志记录调试此类程序有时比较棘手因为错误可能发生在Excel库内部。结构化异常处理务必使用TRY...CATCH包裹核心操作并获取异常文本(lx_error-get_text())记录到应用日志中。生成中间文件在开发调试阶段可以在关键步骤如从SMW0读取模板后、写入数据后将XSTRING通过GUI_DOWNLOAD保存到本地用Excel打开检查看是模板问题、数据问题还是写入逻辑问题。使用CL_FDT_XLSP的辅助方法查看是否有导出工作表内容为内表的方法用于验证内存中的数据状态。7. 生态扩展从下载到邮件与工作流集成生成包含数据的XSTRING后它的旅程才刚刚开始。我们需要将它交付给用户。这里有几个常见的下游集成场景。场景一前端ALV报表的“导出”按钮这是最常见的场景。在ALV工具栏添加一个自定义按钮点击后调用我们的函数生成Excel然后使用GUI_DOWNLOAD让用户保存到本地。DATA: lv_xstring TYPE xstring, lv_bytecount TYPE i, lt_data TYPE TABLE OF solix. “ 1. 调用函数生成Excel得到lv_xstring “ ... “ 2. 将XSTRING转换为SOLIX表GUI_DOWNLOAD所需格式 lv_bytecount xstrlen( lv_xstring ). CALL FUNCTION ‘SCMS_XSTRING_TO_BINARY‘ EXPORTING buffer lv_xstring IMPORTING output_length lv_bytecount TABLES binary_tab lt_data. “ 3. 弹出保存对话框并下载 CALL FUNCTION ‘GUI_DOWNLOAD‘ EXPORTING bin_filesize lv_bytecount filename lv_filename “ 例如 ‘PO_Report.xlsx‘ filetype ‘BIN‘ TABLES data_tab lt_data EXCEPTIONS file_write_error 1 no_batch 2 gui_refuse_filetransfer 3 invalid_type 4 OTHERS 5.场景二通过邮件自动发送报表很多业务需要定期如每日、每周将报表自动发送给相关责任人。这时需要将XSTRING作为邮件附件发送。DATA: lo_send_request TYPE REF TO cl_bcs, lo_document TYPE REF TO cl_document_bcs, lo_recipient TYPE REF TO if_recipient_bcs. “ 1. 创建邮件发送请求和文档 lo_send_request cl_bcscreate_persistent( ). lo_document cl_document_bcscreate_document( i_type ‘RAW‘ “ 附件类型 i_text ‘这是月度采购报表请查收。‘ “ 邮件正文 i_subject ‘月度采购报表‘ “ 邮件主题 ). “ 2. 将XSTRING添加为附件 “ 需要先将XSTRING转换为SOLIX DATA: lt_attachment_content TYPE solix_tab. “ ... 转换逻辑同上 ... lo_document-add_attachment( i_attachment_type ‘XLS‘ “ 附件类型Excel i_attachment_subject ‘Purchase_Order_List.xlsx‘ i_attachment_size lv_bytecount i_att_content_hex lt_attachment_content ). “ 3. 设置发件人、收件人 lo_document-set_sender( ‘sendercompany.com‘ ). lo_recipient cl_cam_address_bcscreate_internet_address( ‘recipientcompany.com‘ ). lo_send_request-add_recipient( lo_recipient ). “ 4. 关联文档并发送 lo_send_request-set_document( lo_document ). lo_send_request-send( ).场景三上传到SAP Content Server或其他存储对于需要归档或供其他系统访问的报表可以将生成的Excel文件上传到SAP Content Server (HTTP) 或直接保存到特定的应用服务器目录。“ 示例保存到应用服务器 DATA: lv_path TYPE string VALUE ‘/usr/sap/trans/excel_reports/‘, lv_fullpath TYPE string. CONCATENATE lv_path lv_filename INTO lv_fullpath. “ 使用OPEN DATASET进行二进制写操作 OPEN DATASET lv_fullpath FOR OUTPUT IN BINARY MODE. IF sy-subrc 0. LOOP AT lt_attachment_content INTO DATA(ls_solix). “ lt_attachment_content是之前转换的SOLIX表 TRANSFER ls_solix-line TO lv_fullpath. ENDLOOP. CLOSE DATASET lv_fullpath. ENDIF.将SMW0模板读取、数据填充、文件分发这三个环节串联起来你就构建了一个完整的、自动化的报表生成与分发流水线。这套组合拳打下来无论是临时的数据导出还是定期的自动化报告都能应对自如彻底告别手动整理Excel的繁琐。
返回列表