浅析js前端如何将word文档转为html:docx是怎样存储图片的、Java使用POI如何实现、前端使用Mammoth.js如何实现

  先聊聊需求背景:公司运营需求,说在富文本编辑器中发布包含图片的 Word 文档时,图片和文本内容不能一起复制,每次她们都得分开处理,对于包含较多图片的 Word 时,她们处理起来很抓狂。

一、准备知识:docx 是怎样存储图片的

  要解决上述的问题,首先就需要能够解析 Word 文档中的图片。目前 Word 有两种格式后缀分别是 .doc 和 .docx。97-2003 的旧版本文件名后缀就是 .doc, 2007 版以后的后缀名是 .docx。docx 格式是被压缩过的文档,体积更小,能处理更加复杂的内容,访问速度更快。

  对于上述两种格式的 Word 文档,大家应该都很熟悉。但估计挺多小伙伴不知道 Word 文档是如何存储内容的,这里我们以 docx 格式为例。这里我已经提前准备了一个包含图片和文本的 test.docx 文件,然后复制一份重命名为 test.rar。看到 rar 后缀相信你已经猜到了,下一步我们要执行解压操作。

  当完成解压操作之后,默认在当前目录会生成一个文件夹,该文件夹的主要目录结构如下:

  这个就是我test文档里的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 如何实现

  这个就不多说了,这篇文章是比较靠谱的:https://blog.csdn.net/qq_30631063/article/details/109276000

三、前端使用 Mammoth.js 如何实现

1、Mammoth.js 简介

  Mammoth 旨在转换 .docx 文档,并将其转换为 HTML。Mammoth 的目标是通过使用文档中的语义信息并忽略其他细节来生成简单干净的 HTML。比如Mammoth 会将应用标题 1 样式的任何段落转换为 h1 元素,而不是尝试完全复制标题的样式(字体,文本大小,颜色等)。

  由于 .docx 使用的结构与 HTML 的结构之间存在很大的不匹配,这意味着对于较复杂的文档而言,这种转换不太可能是完美的。但如果你仅使用样式在语义上标记文档,则 Mammoth 能实现较好的转换效果。当前 Mammoth 支持以下主要特性:

  • Headings

  • Lists,Table

  • Images

  • Bold, italics, underlines, strikethrough, superscript and subscript

  • Links,Line breaks

  • Footnotes and endnotes

  它还支持自定义映射规则。例如,你可以通过提供适当的样式映射将 WarningHeading 转换为 h1.warning。另外文本框的内容被视为单独的段落,出现在包含文本框的段落之后。

2、Mammoth.js API 为我们提供了很多方法,这里我们来介绍三个比较常用的 API:

(1)mammoth.convertToHtml(input,options):把源文档转换为 HTML 文档

(2)mammoth.convertToMarkdown(input,options):把源文档转换为 Markdown 文档。这个方法与 convertToHtml 方法类似,区别就是返回的 result 对象的 value 属性是 Markdown 而不是 HTML

(3)mammoth.extractRawText(input):提取文档的原始文本。这将忽略文档中的所有格式,每个段落后跟两个换行符

  介绍完 Mammoth.js 相关的特性和 API,接下来我们开始进入实战环节。

四、Mammoth.js 代码实战

  Mammoth.js 这个库同时支持 Node.js 和浏览器两个平台,在浏览器端 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 文档。

mammoth.convertToHtml({ arrayBuffer })

  如果文档中不包括特殊的图片类型,比如 wmfemf 类型,而是常见的 jpgpng 等类型的话,那么可以看到 Word 文档中的图片。

  难道这样就搞定了,那是不是太简单了,其实这只是刚开始。当你通过浏览器的开发者工具审查 Word 解析后的 HTML 文档后,会发现图片都以 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 对象进行提交。

  至此解析 Word 文档并自动把文档中的图片上传至文件资源服务器的基本功能已经实现了。目前该方案遇到的问题就是无法处理 wmfemf 类型的图片文件,了解了下一些第三方图片服务也不太支持,所以对于这种特殊的图片格式目前的解决方案就是让用户手动上传对应原始图片。