“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第5篇文章,点击查看活动详情”
背景介绍
先聊聊需求背景:公司运营人员的需求,说在富文本编辑器中发布包含图片的 Word 文档时,图片和文本内容不能一起复制,每次她们都得分开处理,对于包含较多图片的 Word 时,她们处理起来很抓狂。
想象一下画面感,确实如此🤦♂️,安排
首先,我们需要了解需求的核心应用场景。因为作为程序员,我们不能仅仅局限于接受需求、实现需求,我们更需要了解其背后的业务价值,然后从技术的专业角度,给出合理合适的解决方案。
所以,我们可以看到其需求核心就是:处理多张图片,上传word文档转化为文章。那么我们就安排在富文本编辑器工具栏栏上增加一个“导入word”文档按钮,导入文档自动解析文档内容,然后运营人员再去优化文章内容。
准备知识:docx 是怎样存储图片的
要解决上述的问题,首先就需要能够解析 Word 文档中的图片。目前 Word 有两种格式后缀分别是 .doc 和 .docx。Office 1997-2003 的旧版本文件名后缀就是 .doc, 2007 版以后的后缀名是 .docx。docx 格式是被压缩过的文档,体积更小,能处理更加复杂的内容,访问速度更快。
对于上述两种格式的 Word 文档,大家应该都很熟悉。但估计挺多小伙伴不知道 Word 文档是如何存储内容的,这里我们以 docx 格式为例。这里我已经提前准备了一个包含图片和文本的 test.docx 文件,然后复制一份重命名为 test.rar。看到 rar 后缀相信你已经猜到了,下一步我们要执行解压操作。
当完成解压操作之后,默认在当前目录会生成一个文件夹,该文件夹的主要目录结构如下:
我们再点击 word,再点击 media 就可以看到我们 word 文档里的那 3 张图片
这个就是我 test.docx 文档里的3张图片。
由此可见,在解压后,Word 文档中包含的图片会被保存到 word/media 目录下。而我们要解决的问题就是能识别到 Word 文档中的图片,然后自动上传到文件资源服务器。要实现这个功能的前提就是能够解析当前的 Word 文档,值得庆幸的是这个功能已经有前人帮我们实现了。
对于 Java 开发者来说,可以直接基于 POI 项目,POI 是 Apache 的一个开源项目,它的初衷是处理基于 Office Open XML 标准(OOXML)和 Microsoft OLE 2 复合文档格式(OLE2)的各种文件格式的文档,而且支持读写操作。
当然本文重点不是服务端解析方案,而是在前端如何实现 Word 解析并提取 Word 中的图片。同样对于纯前端的解析方案,mwilliamson 大佬已经帮我们实现了,也就是 Mammoth.js 这个库。
Java 使用 POI 如何实现
这个就不多说了,这篇文章是比较靠谱的:blog.csdn.net/qq_30631063…
前端使用 Mammoth.js 如何实现
1、Mammoth.js 旨在转换 .docx 文档,并将其转换为 HTML。Mammoth 的目标是通过使用文档中的语义信息并忽略其他细节来生成简单干净的 HTML。
2、详细的介绍见项目文档:github.com/mwilliamson…
这里主要介绍下 3 个转换的 API:
(1)mammoth.convertToHtml(input,options):把源文档转换为 HTML 文档
(2)mammoth.convertToMarkdown(input,options):把源文档转换为 Markdown 文档。这个方法与 convertToHtml 方法类似,区别就是返回的 result 对象的 value 属性是 Markdown 而不是 HTML
(3)mammoth.extractRawText(input):提取文档的原始文本。这将忽略文档中的所有格式,每个段落后跟两个换行符
前端代码实战
先看官方文档介绍的 API 如何使用
可以看到描述:在 Node 端 input 参数可以是 path 或 buffer。而在浏览器端 mammoth.convertToHtml
方法的 input 参数的格式是 {arrayBuffer:arrayBuffer}
,其中 arrayBuffer 就是 .docx 文件的内容。
那么在前端我们可以通过 FileReader API 来读取文件的内容,此外该接口也提供了 readAsArrayBuffer
方法,用于读取指定的 Blob 中的内容,一旦读取完成,result 属性中保存的将是被读取文件的 ArrayBuffer
数据对象。
// 1、上传word文档
<input class="hide" type="file" id="wordFile" @change="getWordFile" accept=".docx" />
<label for="wordFile" class="editor-export cur-p c-hover">{{ loading ? '转换中,请稍后' : '导入word' }}</label>
// 2、getWordFile 获取文档内容
getWordFile (e) {
const _this = this
const file = e.target.files[0]
let reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onload = function (evt) {
let arrayBuffer = evt.target.result
mammoth
.convertToHtml({ arrayBuffer: arrayBuffer })
.then(_this.displayResult)
.done()
}
},
该方法用于:(1)实现把输入的 file 对象转换为 ArrayBuffer 对象。(2)在获取 Word 文档的 ArrayBuffer 对象之后,就可以调用 convertToHtml 方法,把 Word 文档内容转换为 HTML 文档。
难道这样就搞定了,那是不是太简单了,其实这只是刚开始。当你通过浏览器的开发者工具审查 Word 解析后的 HTML 文档后,会发现图片都是以 Base64 的格式进行嵌入的。
而我们文章的内容是要存入数据库去的,图片以 Base64 存入会导致内容过大。针对这种情况,一种比较好的方案是把图片提交到文件资源服务器上。
在 Mammoth.js 中要实现上述的功能,可以使用 convertImage 配置选项来自定义图片处理器。使用示例如下:
const mammothOptions = {
convertImage: mammoth.images.imgElement(
function(image) {
return image.read("base64").then(async (imageBuffer) => {
const result = await _this.uploadBase64Image(imageBuffer, image.contentType)
return { src: result }
})
})
}
uploadBase64Image 方法的作用就是上传 Base64 格式的图片:
async uploadBase64Image (base64Image, mime) {
const formdata = new FormData()
const _file = this.base64ToBlob(base64Image, mime)
formdata.append('file', _file)
let { data } = await attachNoticesApi(formdata)
return data.operateCallBackObj
},
base64ToBlob(base64, mime) {
mime = mime || ""
const sliceSize = 1024
const byteChars = window.atob(base64)
const byteArrays = []
for ( let offset = 0, len = byteChars.length; offset < len; offset += sliceSize ) {
const slice = byteChars.slice(offset, offset + sliceSize)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
return new Blob(byteArrays, { type: mime })
}
为了减少图片文件的大小,我们需要把 Base64 格式的图片先转成 Blob 对象,然后再通过创建 FormData 对象进行提交,比如传到 oss 对象存储里,最后拿到图片链接,插入到图片展示位置。
至此解析 Word 文档并自动把文档中的图片上传至文件资源服务器的基本功能已经实现了。