在平常工做中,大部分人都会使用 Microsoft Office Word、WPS 或 macOS Pages 等文字处理程序进行 Word 文档处理。除了使用上述的文字处理程序以外,对于 Word 文档来讲,还有其余的处理方式么?答案是有的。javascript
接下来阿宝哥将介绍在前端如何玩转 Word 文档,阅读本文以后,你将了解如下内容:html
阅读阿宝哥近期热门文章(感谢掘友的鼓励与支持🌹🌹🌹):前端
小伙伴们准备好了吗,「玩转 Word 文档之旅」 开始了,Let's go!java
Microsoft Office Word 是微软公司的一个文字处理器应用程序。它最初是由 Richard Brodie 为了运行 DOS 的 IBM 计算机而在 1983 年编写的。随后的版本可运行于 Apple Macintosh(1984 年)、SCO UNIX 和 Microsoft Windows(1989 年),并成为了 Microsoft Office 的一部分。ios
Word 给用户提供了用于建立专业而优雅的文档工具,帮助用户节省时间,并获得优雅美观的结果。一直以来,Microsoft Office Word 都是最流行的文字处理程序。git
下表列出了常见的几种 Word 支持的文件格式,按扩展名的字母顺序排序。github
若想了解 Word 全部支持的格式,可参考微软 office-file-format-reference 在线文档。目前你们接触比较多的是扩展名为 .docx
的文档,所以它就是本文的主角。web
俗话说 “知己知彼百战百胜”,在 “出战” 前咱们先来简单了解一下 「docx」 文档。「97-2003 的旧版本文件名后缀就是 .doc, 2007 版之后的后缀名是 .docx」。docx 格式是被压缩过的文档,体积更小,能处理更加复杂的内容,访问速度更快。ajax
实际上 「docx」 文档是一个压缩文件( ZIP 格式)。ZIP 文件格式是一种数据压缩和文档储存的文件格式,原名 Deflate,发明者为菲尔·卡茨(Phil Katz),他于 1989 年 1 月公布了该格式的资料。ZIP 一般使用后缀名 “.zip”,它的 MIME 格式为 「application/zip」。npm
这里阿宝哥已经提早准备了一个包含阿宝哥头像和某些文本的 「abao.docx」 文档,接着复制一份重命名为 「abao.zip」,而后使用 ZIP 压缩/解压软件进行解压。
经过观察解压后的目录,咱们发现 Word 文档由一系列的 XML 文件和多媒体文件组成, 「abao.docx」 文档中的阿宝哥头像,最终被解压到 「word/media」 目录下。下面咱们来查看一下 abao 文件夹的目录结构:
-rw-rw-r--@ 1 fer staff 1641 7 11 01:25 [Content_Types].xml drwxr-xr-x@ 3 fer staff 96 7 11 09:41 _rels drwxr-xr-x@ 4 fer staff 128 7 11 09:41 docProps drwxr-xr-x@ 13 fer staff 416 7 11 09:42 word 复制代码
很明显 abao 目录下含有一个 「[Content_Types].xml」 文件和 「_rels、docProps、word」 三个子目录。
[Content_Types].xml
:该文件用于定义里面每个 XML 文件的内容类型;
_rels
:该目录下通常会有一个
「.rels」 后缀的文件,它里面保存了这个目录下各个 Part 之间的关系。
_rels
目录不止一个,它其实是有层级的。
docProps
:该目录下的 XML 文件用于保存 docx 文件的属性;
word
:该目录下包含了 Word 文档中的内容、字体、样式或主题等信息。
介绍完 Word 支持的文件格式和 Docx 文档,咱们开始进入正题 —— 「“在前端如何玩转 Word 文档”」。
在平常工做中,有些时候咱们但愿在富文本编辑器中导入已有的 Word 文档进行二次加工,要知足这个需求,咱们就须要先把 Word 文档转换成 HTML 文档。要实现这个功能,有 「服务端转换和前端转换」 两种方案:
在介绍如何利用 Mammoth.js 把以前建立的 Word 文档转换成 HTML 文档前,咱们来提早体验一下最终的转换效果。
Mammoth.js 旨在转换 .docx 文档(例如由 Microsoft Word 建立的文档),并将其转换为 HTML。 「Mammoth 的目标是经过使用文档中的语义信息并忽略其余细节来生成简单干净的 HTML。」 好比,Mammoth 会将应用标题 1 样式的任何段落转换为 h1 元素,而不是尝试彻底复制标题的样式(字体,文本大小,颜色等)。
因为 .docx 使用的结构与 HTML 的结构之间存在很大的不匹配,这意味着对于较复杂的文档而言,这种转换不太多是完美的。但若是你仅使用样式在语义上标记文档,则 Mammoth 能实现较好的转换效果。
当前 Mammoth 支持如下主要特性:
它还支持自定义映射规则。例如,你能够经过提供适当的样式映射将 WarningHeading 转换为 h1.warning。另外文本框的内容被视为单独的段落,出如今包含文本框的段落以后。
Mammoth.js 这个库为咱们提供了不少方法,这里咱们来介绍三个比较经常使用的 API:
mammoth.convertToHtml(input, options)
:把源文档转换为 HTML 文档
mammoth.convertToMarkdown(input, options)
:把源文档转换为 Markdown 文档。这个方法与
convertToHtml
方法相似,区别就是返回的 result 对象的 value 属性是 Markdown 而不是 HTML。
mammoth.extractRawText(input)
:提取文档的原始文本。这将忽略文档中的全部格式。每一个段落后跟两个换行符。
介绍完 Mammoth.js 相关的特性和 API,接下来咱们开始进入实战环节。
Mammoth.js 这个库同时支持 Node.js 和浏览器两个平台,在浏览器端 mammoth.convertToHtml
方法的 input 参数的格式是 {arrayBuffer: arrayBuffer}
,其中 arrayBuffer 就是 .docx 文件的内容。在前端咱们能够经过 FileReader API 来读取文件的内容,此外该接口也提供了 readAsArrayBuffer 方法,用于读取指定的 Blob 中的内容,一旦读取完成,result 属性中保存的将是被读取文件的 ArrayBuffer
数据对象。下面咱们定义一个 readFileInputEventAsArrayBuffer 方法:
export function readFileInputEventAsArrayBuffer(event, callback) {
const file = event.target.files[0]; const reader = new FileReader(); reader.onload = function(loadEvent: Event) { const arrayBuffer = loadEvent.target["result"]; callback(arrayBuffer); }; reader.readAsArrayBuffer(file); } 复制代码
该方法用于实现把输入的 File 对象转换为 ArrayBuffer 对象。在获取 Word 文档对应的 ArrayBuffer 对象以后,就能够调用 convertToHtml 方法,把 Word 文档内容转换为 HTML 文档。
mammoth.convertToHtml({ arrayBuffer })
复制代码
此时若是你的文档中不包括特殊的图片类型,好比 wmf
或 emf
类型,而是常见的 jpg
或 png
等类型的话,那么你能够看到 Word 文档中的图片。难道这样就搞定了,那是否是太简单了,其实这只是个开始。当你经过浏览器的开发者工具审查 Word 解析后的 HTML 文档后,会发现图片都以 Base64 的格式进行嵌入。若是图片很少且单张图片也不会太大的话,那这种方案是能够考虑的。
针对多图或大图的状况,一种比较好的方案是把图片提交到文件资源服务器上。在 Mammoth.js 中要实现上述的功能,可使用 「convertImage」 配置选项来自定义图片处理器。具体的使用示例以下:
let options = {
convertImage: mammoth.images.imgElement(function(image) { return image.read("base64").then(function(imageBuffer) { return { src: "data:" + image.contentType + ";base64," + imageBuffer }; }); }) }; 复制代码
以上示例实现的功能就是把 Word 中的图片进行 Base64 编码,而后转成 Data URL 的形式,以实现图片的显示。很明显这不符合咱们的要求,因此咱们须要作如下调整:
const mammothOptions = {
convertImage: mammoth.images.imgElement(function(image) { return image.read("base64").then(async (imageBuffer) => { const result = await uploadBase64Image(imageBuffer, image.contentType); return { src: result.data.path // 获取图片线上的URL地址 }; }); }) }; 复制代码
顾名思义 uploadBase64Image 方法的做用就是上传 Base64 编码后的图片:
async function uploadBase64Image(base64Image, mime) {
const formData = new FormData(); formData.append("file", base64ToBlob(base64Image, mime)); return await axios({ method: "post", url: "http://localhost:3000/uploadfile", // 本地图片上传的API地址 data: formData, config: { headers: { "Content-Type": "multipart/form-data" } } }); } 复制代码
为了减小图片文件的大小,咱们须要把 Base64 格式的图片先转成 Blob 对象,而后在经过建立 FormData 对象进行提交。base64ToBlob 方法的定义以下:
function base64ToBlob(base64, mimeType) {
let bytes = window.atob(base64); let ab = new ArrayBuffer(bytes.length); let ia = new Uint8Array(ab); for (let i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i); } return new Blob([ia], { type: mimeType }); } 复制代码
这时把 Word 文档转换为 HTML 并自动把 Word 文档中的图片上传至文件资源服务器的基本功能已经实现了。对于 Mammoth.js 内部是如何解析 Word 中的 XML 文件,咱们就不作介绍了,反之咱们来简单介绍一下 Mammoth.js 内部依赖的 JSZip 这个库。
JSZip 是一个用于建立、读取和编辑 「.zip」 文件的 JavaScript 库,含有可爱而简单的 API。该库的兼容性以下所示:
Opera | Firefox | Safari | Chrome | Internet Explorer | Node.js |
---|---|---|---|---|---|
Yes | Yes | Yes | Yes | Yes | Yes |
通过最新版本的测试 | 通过 3.0/3.6/最新版本测试 | 通过最新版本的测试 | 通过最新版本的测试 | 通过 IE 6 / 7 / 8 / 9 / 10 测试 | 通过 Node.js 0.10 / 最新版本测试 |
使用 JSZip 时,你能够经过如下几种方式进行安装:
「npm」: npm install jszip
「bower」:bower install Stuk/jszip
「component」 :component install Stuk/jszip
「手动」:先下载 JSZip 安装包,而后引入 dist/jszip.js
或 dist/jszip.min.js
文件
let zip = new JSZip();
zip.file("Hello.txt", "Hello Semlinker\n"); let img = zip.folder("images"); img.file("smile.gif", imgData, {base64: true}); zip.generateAsync({type: "blob"}) .then(function(content) { // see FileSaver.js saveAs(content, "example.zip"); }); 复制代码
该示例来自 JSZip 官网,成功运行以后,会自动下载并保存 「example.zip」 文件。该文件解压后的目录结构以下所示:
「Markdown 是一种轻量级标记语言」 ,创始人为约翰·格鲁伯(英语:John Gruber)。它容许人们使用易读易写的纯文本格式编写文档,而后转换成有效的 XHTML(或者 HTML)文档。这种语言吸取了不少在电子邮件中已有的纯文本标记的特性。
因为 Markdown 的轻量化、易读易写特性,而且对于图片,图表、数学式都有支持,目前许多网站都普遍使用 Markdown 来撰写帮助文档或是用于论坛上发表消息。
了解完 Markdown 是什么以后,咱们来分析一下如何把 Word 文档转换成 Markdown 文档。对于这个功能,咱们也有两种处理方式:
mammoth.convertToMarkdown(input, options)
方法;
mammoth.convertToHtml(input, options)
生成的 HTML 文档,在利用 HTML to Markdown 的转换工具,来间接实现上述功能。
下面咱们来介绍第二种方案,这里咱们使用 Github 上一个开源的转换器 —— turndown,它是使用 JavaScript 开发的 HTML to Markdown 转换器,使用起来很简单。
首先你能够经过如下两种方式来安装它:
npm install turndown
<script src="https://unpkg.com/turndown/dist/turndown.js"></script>
安装完以后,你就能够经过调用 TurndownService
构造函数,来建立 turndownService 实例,而后调用该实例的 turndown()
方法执行转换操做:
let markdown = turndownService.turndown(
document.getElementById('content') ) 复制代码
对于前面使用的 「abao.docx」 文档,最终转换生成的 Markdown 文档以下:
全栈修仙之路,聚焦全栈,专一分享 TypeScript、Web API、Node.js、Deno 等全栈干货。  复制代码
须要注意的是,TurndownService 构造函数支持不少配置项,这里阿宝哥就不详细介绍了。感兴趣的小伙伴,能够自行阅读 turndown 官方文档或访问 turndown 在线示例 实际体验一下。
既然已经讲到 Markdown,阿宝哥再给小伙伴们介绍一个 Github 上不错的开源库 markmap,该库使用思惟导图的方式来实现 Markdown 文档的可视化,总体效果还蛮不错的:
(图片来源:https://markmap.js.org/repl/)
最后,咱们再来看一下在前端如何动态生成 Word 文档。
在前端若是要动态生成 Word 文档,咱们能够直接利用一些成熟的第三方开源库,好比:docx 或 html-docx-js。
下面咱们将以 docx 为例,来介绍如何在前端如何生成 「.docx」 格式的 Word 文档。Docx 这个库提供了优雅的声明式 API,让咱们可使用 JS/TS 轻松生成 .docx 文件。此外,它还同时支持 Node.js 和浏览器。
Docx 这个库为开发者提供了许多类,用于建立 Word 中的对应元素,这里咱们简单介绍几个常见的类:
接下来阿宝哥将使用 Docx 这个库,来动态生成前面介绍过的 「abao.docx」 文档,具体代码以下所示:
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> </head> <body> <h1>阿宝哥 - 动态生成 Word 文档示例</h1> <button type="button" onclick="generate()"> 点击生成 Docx 文档 </button> <script src="https://unpkg.com/docx@5.0.2/build/index.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js"></script> <script> async function generate() { const doc = new docx.Document(); const imageBuffer = await fetch( "https://avatars3.githubusercontent.com/u/4220799" ).then((response) => response.arrayBuffer()); const image = docx.Media.addImage(doc, imageBuffer, 230, 230); doc.addSection({ properties: {}, children: [ new docx.Paragraph({ children: [ new docx.TextRun({ text: "全栈修仙之路,", bold: true, }), new docx.TextRun({ text: "聚焦全栈,专一分享 TypeScript、Web API、Node.js、Deno 等全栈干货。", }), ], }), new docx.Paragraph(image), ], }); docx.Packer.toBlob(doc).then((blob) => { console.log(blob); saveAs(blob, "abao.docx"); console.log("文档生成成功"); }); } </script> </body> </html> 复制代码
在以上示例中,当用户点击 「点击生成 Docx 文档」 按钮以后,会调用 generate()
回调函数。在该回调函数内,首先会建立新的 Document 对象,而后使用 fetch API 从 Github 上下载阿宝哥的头像,当成功获取图片的数据以后,会继续调用 docx.Media.addImage()
方法添加图片。
接着咱们会调用 doc.addSection()
方法来添加 Section 块,该块将做为段落的容器。在示例中,咱们建立的 Section 块包含两个段落,一个用于存放文本信息,而另外一个用于存放图片信息。最后咱们会把 Document 对象转换成 Blob 对象,而后经过 saveAs()
方法下载到本地。
本文使用 mdnice 排版