这是 Jerry 2021 年的第 71 篇文章,也是汪子熙公众号总共第 348 篇原创文章。

Jerry 之前发布过一篇文章 不使用任何框架,手写纯 JavaScript 实现上传本地文件到 ABAP 服务器,之后不少朋友留言,提出的问题概括为以下两类:

(1) 客户端通过 multipart/form-data 格式发送的数据,ABAP 端除了像 Jerry 文章采取字符串解析这种比较繁琐的方式处理外,还有其他方法吗?

(2) 能否上传二进制文件比如 Excel 到 ABAP 并进行解析?


本文就来解答这两个问题。

使用 JavaScript 通过 multipart/form-data 格式发送 PDF 和 Excel 文件到 ABAP 服务器

关于 multipart/form-data 格式的详细说明,参考 Mozilla 开发社区和 W3 Org 的文档:

  • https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects

  • https://www.w3.org/html/wg/spec/association-of-controls-and-forms.html#multipart-form-data

我在前文例子的基础上稍作修改,在 Form 里使用两个类型为 file 的 input 标签,分别上传 PDF 和 Excel 文件:


用来测试的本地 PDF 文件:PDF.pdf,大小为 30129 字节。


内容如下:

本地用来测试的 Excel 文件:TEST.xlsx,内容如下:

点击 HTML 页面上传文件的超链接,在 Chrome 开发者工具观察到 HTTP POST 请求的负载,包含了 PDF 和 Excel 两个 input 控件包含的二进制流(stream):

点击 view source,查看 multipart/form-data 数据明细:

我们仍然可以在 Chrome 开发者工具里观察到上传的 PDF 和 Excel 的文件名和 Content-Type 即文件类型。同前文上传文本文件的例子不同,这里无法看到两个文件的二进制内容——这些二进制内容可以在 ABAP 服务器端调试器里观察到。


以上传的 PDF 文件为例,在 ABAP 服务器端接收到的 form-data 数据,如下图所示,绿色高亮区域即为上图 Chrome 开发者工具里能够观测到的文件名 PDF.pdf 和文件类型 application/pdf, 而%PDF-1.4# 开头的,就是 PDF 文件的二进制内容。


准确的说,PDF 格式是文本和二进制流的混合模式。用文本编辑器打开 PDF.pdf, 能看到其文件头部包含的是文本字符描述的文件元数据,比如该文件的创建和修改时间,创建该文件的工具名等等,后半部分才是二进制流。

现在已经有很多开源工具比如 JavaScript 库可以用来生成和解析 PDF 文件了,感兴趣的朋友可以在搜索引擎里搜索 Jerry 这几篇文章:

  • 使用 ABAP 和 JavaScript 代码生成 PDF 文件的几种方式
  • 使用 JavaScript 将当前页面保存成 PDF,支持图片和文字的保存
  • PDF 文件如何转成 markdown 格式

对于上传到 ABAP 服务器的 PDF 文件的文件名,我们仍然采取和前一篇文章同样的方式解析,从下图红色矩形框中的字符串中提取。

而对于上图绿色高亮的 PDF 的二进制数据,CL_HTTP_REQUEST 提供了相应方法来提取。关键代码如下图所示:


当 ABAP 服务器接收到的客户端数据格式为 multipart/form-data 时,调用 CL_HTTP_REQUEST 的num_multiparts 方法可以得到 parts 的个数,再使用 get_multipart 方法,传入每个 part 的索引,就可以得到代表这个 part 的一个实例引用。

调用该引用的get_content_type 和 get_data 方法,就能解析出上传文件的类型(比如 pdf 格式对应的 application/pdf)和二进制内容。

至此调用 SAP CRM 附件创建 API 的三大参数:文件名,文件类型和文件二进制内容均已就绪,调用 API 即可将上传的 PDF 和 Excel 数据,创建成为 SAP CRM 销售订单的附件。

创建好的 PDF 和 Excel 附件在 SAP CRM 系统里显示如下:

打开这两个附件,确保上传之后,其内容同本地文件完全一致:

如何使用 ABAP 解析上传的 Excel 文件

这个话题,其实 Jerry 2019 年的文章 使用ABAP操作Excel的几种方法 已经系统介绍过。

我们在 ABAP 调试器里观察到,本地扩展名为 xlsx 的 Excel 文件,上传到 ABAP 服务器时,其 content-type 为:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

什么是 openxmlformats?下面通过具体的例子来说明。

以我这个本地 Excel 文件为例,将扩展名从 xlsx 更改为 zip,然后解压:

发现 xlsx 文件其实是一个压缩包,解压之后生成了一个文件夹,包含了下图所示的若干子文件夹和文件。

上图 Excel 文件有一个名为 Sheet1 的内容页,A1 值为 ABAP,B1 值为 Java,这个信息维护在解压出来的 worksheets 文件夹的子文件 sheet1.xml 内:

上图高亮的 XML c 节点代表 Cell,r="A1" 和 r="B1", 代表这两个 cell 所在的 Row ID,c 的子节点 v 包含了 Cell 的具体值。

不难发现,sheet1.xml 里并未直接将 ABAP 和 Java 的字符串字面量在内,而仅仅存放了其索引,0 和 1. 做过 Java 开发的朋友,可以把这种设计类比成 Java 的字符串常量池。

在解压出的文件夹里有另一个文件 sharedStrings.xml, 顾名思义,维护了 Excel worksheets 里出现的所有字符串,用于在 sheets 之间共享。每个多带带的 sheet xml 文件只维护使用到的字符串的索引,以减小 Excel 文件的尺寸。

因此,只要熟悉了 TEST.xlsx 重命名为 TEST.zip 并解压之后生成的每一个文件的用途,即 Open XML Formats 的协议规范,就可以使用任何高级编程语言解析 Excel 文件。

可以在 WikiPedia 里找到 Open XML Formats 协议定义的每个文件的作用:

https://en.wikipedia.org/wiki/Office_Open_XML_file_formats

SAP CRM 提供了一个工具类,基于 Open XML Formats 解析 Excel 文件内容:cl_xlsx_document.

只需将 Excel 文件的二进制内容传入,该工具类即返回一个 Excel 文件的引用,根据该引用的各种 GET 方法,即可访问到 Excel 文件内由 Open XML Formats 协议定义的各个部分的内容。

核心逻辑如下图所示,代码都是自描述的,这里不再赘述。

当然,开源项目 abap2xlsx 也是另一个选择:

https://github.com/sapmentors/abap2xlsx

至于 SAP Fiori 应用通过 SAP Gateway 上传附件的技术细节,Jerry 将来会介绍。

本文涉及到的前后端完整源代码,请在这个链接处下载。

感谢阅读。

Jerry 的 ABAP 专题

  • Jerry的ABAP, Java和JavaScript乱炖

  • ABAP开发人员未来应该学些什么

  • Jerry 2017年的五一小长假:8种经典排序算法的ABAP实现

  • Jerry的ABAP原创技术文章合集

  • 300行ABAP代码实现一个最简单的区块链原型

  • 使用Java+SAP云平台+SAP Cloud Connector调用ABAP On-Premise系统里的函数

  • 在SAP云平台的CloudFoundry环境下消费ABAP On-Premise OData服务

  • ABAP vs Java, 蛙泳 vs 自由泳

  • 聊聊C语言和ABAP

  • 动手使用ABAP Channel开发一些小工具,提升日常工作效率

  • 我用ABAP做过的那些无聊的事情

  • 不喜欢SAP GUI?那试试用Eclipse进行ABAP开发吧

  • 使用Visual Studio Code编写和激活ABAP代码

  • 你的ABAP程序给佛祖开过光么?来试试Jerry这个小技巧

  • 在SAP云平台ABAP编程环境上编写第一段ABAP程序

  • SAP官方发布的ABAP编程规范

  • ABAP Code Inspector那些隐藏的功能,您都知道吗?

  • 还在用ABAP进行SAP产品的二次开发?来了解下这种全新的二次开发理念吧

  • ABAP Netweaver体内的那些寄生式编程语言

  • 从SAP社区上的一篇博客开始,聊聊SAP产品命名背后的那份情怀

  • 云端的ABAP Restful服务开发

  • 如何在SAP云平台ABAP编程环境里把CDS view暴露成OData服务

  • 使用abapGit在ABAP On-Premises系统和SAP云平台ABAP环境之间进行代码传输

  • 30分钟用Restful ABAP Programming模型开发一个支持增删改查的Fiori应用

  • Jerry带您了解Restful ABAP Programming模型系列之二:Action和Validation的实现

  • Jerry带您了解Restful ABAP Programming模型系列之三:云端ABAP应用调试

  • SAP云平台上的ABAP编程环境里如何消费第三方服务

  • ABAP开发者上云的时候到了 - 现在大家可以免费使用SAP云平台ABAP环境的试用版了

  • 学而不思则罔 - SAP云平台ABAP编程环境的由来和适用场景

  • SAP云平台里的三叉戟应用

  • 如何基于Restful ABAP Programming模型开发并部署一个支持增删改查的Fiori应用

  • SAP 2019 TechEd Key Note解读:云时代下SAP从业人员如何做二次开发?

  • 有哪些ABAP关键字和语法,到了ABAP云环境上就没办法用了?

  • ABAP开发环境终于支持以驼峰命名法自动格式化ABAP变量名了

  • 利用ABAP 740的新关键字REDUCE完成一个实际工作任务

  • 一段让人瑟瑟发抖的ABAP代码

  • 昨日万圣节ABAP怪兽级代码谜团,公布答案啦

  • 介绍一种在ABAP内核态进行内表高效拷贝的方法

  • 使用SAP Cloud Application Programming模型开发OData的一个实际例子

  • 当ABAP遇见普罗米修斯

  • 使用ABAP绘制可伸缩矢量图

  • ABAP开发环境语法高亮的那些事儿

  • SAP错误消息调试之七种武器:让所有的错误消息都能被定位

  • 使用ABAP操作Excel的几种方法

  • SAP GUI里的收藏夹事务码管理工具

  • SAP GUI和Windows注册表

  • 有了Debug权限就能干坏事?小心了,你的一举一动尽在系统监控中

  • ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX这些东东是什么鬼

  • 实现ABAP条件断点的三种方式

  • 使用SAT跟踪监控从浏览器打开的SAP应用的性能和调用栈

  • 一个13年ABAP老兵的建议:了解这些基础知识,对ABAP开发有百利而无一害

  • SAP ABAP Netweaver容器化, 不可能完成的任务吗?

  • SAP产品增强技术回顾

  • SAP API开发方法大全

  • 浅谈Java和SAP ABAP的静态代理和动态代理,以及ABAP面向切面编程的尝试

  • SAP ABAP应用服务器的HTTP响应状态码(Status Code)

  • SAP ABAP里存在Java List这种集合工具类么?CL_OBJECT_COLLECTION了解一下

  • ABAP面试题系列:写一组会出现死锁(Deadlock)的ABAP程序

  • SAP ABAP Netweaver服务器的标准登录方式讲解

  • SAP ABAP关键字语法图和ABAP代码自动生成工具Code Composer

  • SAP ABAP SM50的另类用途 - ABAP工作进程对数据库表读取操作的检测

  • 关于SAP ABAP字符变量和字符串变量字符个数的一个知识点,和一个血案

  • SAP ABAP一组关键字 IS BOUND, IS NOT INITIAL和IS ASSIGNED的用法辨析

  • SAP ABAP和Java里的弱引用(WeakReference)和软引用(SoftReference)

  • SAP AMDP介绍 - ABAP托管的HANA数据库过程

  • 给你的ABAP对象打上标签(Tag)

  • 历史上的今天:编程语言中null引用的十亿美元错误

  • ABAP Development Tool 代码模板和其他一些实用技巧汇总

  • SAP ABAP Development Tool 提高开发效率的十个小技巧

  • 如何在 SAP BTP 平台 ABAP 编程环境里消费基于 SOAP 的 Web Service

  • ABAP 真的会过时吗?聊聊 ABAP 的过去,现在和未来

  • 基于 abapGit 和 abaplint 的 ABAP 持续集成的一个例子

  • 不使用任何框架,手写纯 JavaScript 实现上传本地文件到 ABAP 服务器

更多Jerry的原创文章,尽在:"汪子熙"。