對于 Word 文檔來說,還有其他的處理方式么?答案是有的。
閱讀本文之后,你將了解以下內(nèi)容:
- Microsoft Office Word 支持的文件格式和 Docx 文檔的特點(diǎn);
- 如何將 Word 文檔轉(zhuǎn)換成 HTML 文檔;
- 如何在瀏覽器中處理 ZIP 文檔;
- 如何將 Word 文檔轉(zhuǎn)換成 Markdown 文檔;
- 如何在前端動態(tài)生成 Word 文檔。
一、Microsoft Office Word 簡介
Microsoft Office Word 是微軟公司的一個(gè)文字處理器應(yīng)用程序。它最初是由 Richard Brodie 為了運(yùn)行 DOS 的 IBM 計(jì)算機(jī)而在 1983 年編寫的。隨后的版本可運(yùn)行于 Apple Macintosh(1984 年)、SCO UNIX 和 Microsoft Windows(1989 年),并成為了 Microsoft Office 的一部分。
Word 給用戶提供了用于創(chuàng)建專業(yè)而優(yōu)雅的文檔工具,幫助用戶節(jié)省時(shí)間,并得到優(yōu)雅美觀的結(jié)果。一直以來,Microsoft Office Word 都是最流行的文字處理程序。
1.1 Word 支持的文件格式
下表列出了常見的幾種 Word 支持的文件格式,按擴(kuò)展名的字母順序排序。
若想了解 Word 所有支持的格式,可參考微軟 office-file-format-reference 在線文檔。目前大家接觸比較多的是擴(kuò)展名為 .docx 的文檔,因此它就是本文的主角。
1.2 Docx 文檔
俗話說 “知己知彼百戰(zhàn)百勝”,在 “出戰(zhàn)” 前我們先來簡單了解一下 「docx」 文檔。「97-2003 的舊版本文件名后綴就是 .doc, 2007 版以后的后綴名是 .docx」。docx 格式是被壓縮過的文檔,體積更小,能處理更加復(fù)雜的內(nèi)容,訪問速度更快。
實(shí)際上 「docx」 文檔是一個(gè)壓縮文件( ZIP 格式)。ZIP 文件格式是一種數(shù)據(jù)壓縮和文檔儲存的文件格式,原名 Deflate,發(fā)明者為菲爾·卡茨(Phil Katz),他于 1989 年 1 月公布了該格式的資料。ZIP 通常使用后綴名 “.zip”,它的 MIME 格式為 「application/zip」。
這里已經(jīng)提前準(zhǔn)備了一個(gè)包含頭像和某些文本的 「abao.docx」 文檔,接著復(fù)制一份重命名為 「abao.zip」,然后使用 ZIP 壓縮/解壓軟件進(jìn)行解壓。
通過觀察解壓后的目錄,我們發(fā)現(xiàn) Word 文檔由一系列的 XML 文件和多媒體文件組成, 「abao.docx」 文檔中的頭像,最終被解壓到 「word/media」 目錄下。下面我們來查看一下 abao 文件夾的目錄結(jié)構(gòu):
-rw-rw-r--@ 1 fer staff 1641 7 11 01:25
[Content_Types].xmldrwxr-xr-x@ 3 fer staff 96 7 11 09:41
_relsdrwxr-xr-x@ 4 fer staff 128 7 11 09:41
docPropsdrwxr-xr-x@ 13 fer staff 416 7 11 09:42 word
很明顯 abao 目錄下含有一個(gè) 「[Content_Types].xml」 文件和 「_rels、docProps、word」 三個(gè)子目錄。
-
[Content_Types].xml:該文件用于定義里面每一個(gè) XML 文件的內(nèi)容類型; -
_rels:該目錄下一般會有一個(gè) 「.rels」 后綴的文件,它里面保存了這個(gè)目錄下各個(gè) Part 之間的關(guān)系。_rels目錄不止一個(gè),它實(shí)際上是有層級的。 -
docProps:該目錄下的 XML 文件用于保存 docx 文件的屬性; -
word:該目錄下包含了 Word 文檔中的內(nèi)容、字體、樣式或主題等信息。
介紹完 Word 支持的文件格式和 Docx 文檔,我們開始進(jìn)入正題 —— 「“在前端如何玩轉(zhuǎn) Word 文檔”」。
二、Word 文檔轉(zhuǎn)換成 HTML 文檔
在日常工作中,有些時(shí)候我們希望在富文本編輯器中導(dǎo)入已有的 Word 文檔進(jìn)行二次加工,要滿足這個(gè)需求,我們就需要先把 Word 文檔轉(zhuǎn)換成 HTML 文檔。要實(shí)現(xiàn)這個(gè)功能,有 「服務(wù)端轉(zhuǎn)換和前端轉(zhuǎn)換」 兩種方案:
- 服務(wù)端轉(zhuǎn)換:對于 Java 開發(fā)者來說,可以直接基于 POI 項(xiàng)目,POI 是 Apache 的一個(gè)開源項(xiàng)目,它的初衷是處理基于 Office Open XML 標(biāo)準(zhǔn)(OOXML)和 Microsoft OLE 2 復(fù)合文檔格式(OLE2)的各種文件格式的文檔,而且支持讀寫操作。
- 前端轉(zhuǎn)換:對于前端開發(fā)者來說,要想在前端解析 Word 文檔,我們首先需要對 Word 文檔進(jìn)行解壓,然后再進(jìn)一步解析解壓后的 XML 文檔。看起來整個(gè)功能實(shí)現(xiàn)起來比較繁瑣,但值得慶幸的是 Mammoth.js 這個(gè)庫已經(jīng)為我們實(shí)現(xiàn)上述功能。
在介紹如何利用 Mammoth.js 把之前創(chuàng)建的 Word 文檔轉(zhuǎn)換成 HTML 文檔前,我們來提前體驗(yàn)一下最終的轉(zhuǎn)換效果。
2.1 Mammoth.js 簡介
Mammoth.js 旨在轉(zhuǎn)換 .docx 文檔(例如由 Microsoft Word 創(chuàng)建的文檔),并將其轉(zhuǎn)換為 HTML。「Mammoth 的目標(biāo)是通過使用文檔中的語義信息并忽略其他細(xì)節(jié)來生成簡單干凈的 HTML?!?/strong> 比如,Mammoth 會將應(yīng)用標(biāo)題 1 樣式的任何段落轉(zhuǎn)換為 h1 元素,而不是嘗試完全復(fù)制標(biāo)題的樣式(字體,文本大小,顏色等)。
由于 .docx 使用的結(jié)構(gòu)與 HTML 的結(jié)構(gòu)之間存在很大的不匹配,這意味著對于較復(fù)雜的文檔而言,這種轉(zhuǎn)換不太可能是完美的。但如果你僅使用樣式在語義上標(biāo)記文檔,則 Mammoth 能實(shí)現(xiàn)較好的轉(zhuǎn)換效果。
當(dāng)前 Mammoth 支持以下主要特性:
- Headings
- Lists,Table
- Images
- Bold, italics, underlines, strikethrough, superscript and subscript
- Links,Line breaks
- Footnotes and endnotes
它還支持自定義映射規(guī)則。例如,你可以通過提供適當(dāng)?shù)臉邮接成鋵?WarningHeading 轉(zhuǎn)換為 h1.warning。另外文本框的內(nèi)容被視為單獨(dú)的段落,出現(xiàn)在包含文本框的段落之后。
Mammoth.js 這個(gè)庫為我們提供了很多方法,這里我們來介紹三個(gè)比較常用的 API:
-
mammoth.convertToHtml(input, options):把源文檔轉(zhuǎn)換為 HTML 文檔 -
mammoth.convertToMarkdown(input, options):把源文檔轉(zhuǎn)換為 Markdown 文檔。這個(gè)方法與convertToHtml方法類似,區(qū)別就是返回的 result 對象的 value 屬性是 Markdown 而不是 HTML。 -
mammoth.extractRawText(input):提取文檔的原始文本。這將忽略文檔中的所有格式。每個(gè)段落后跟兩個(gè)換行符。
介紹完 Mammoth.js 相關(guān)的特性和 API,接下來我們開始進(jìn)入實(shí)戰(zhàn)環(huán)節(jié)。
2.2 Mammoth.js 實(shí)戰(zhàn)
Mammoth.js 這個(gè)庫同時(shí)支持 Node.js 和瀏覽器兩個(gè)平臺,在瀏覽器端 mammoth.convertToHtml 方法的 input 參數(shù)的格式是 {arrayBuffer: arrayBuffer},其中 arrayBuffer 就是 .docx 文件的內(nèi)容。在前端我們可以通過 FileReader API 來讀取文件的內(nèi)容,此外該接口也提供了 readAsArrayBuffer 方法,用于讀取指定的 Blob 中的內(nèi)容,一旦讀取完成,result 屬性中保存的將是被讀取文件的 ArrayBuffer 數(shù)據(jù)對象。下面我們定義一個(gè) 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);}
該方法用于實(shí)現(xiàn)把輸入的 File 對象轉(zhuǎn)換為 ArrayBuffer 對象。在獲取 Word 文檔對應(yīng)的 ArrayBuffer 對象之后,就可以調(diào)用 convertToHtml 方法,把 Word 文檔內(nèi)容轉(zhuǎn)換為 HTML 文檔。
mammoth.convertToHtml({ arrayBuffer })
此時(shí)如果你的文檔中不包括特殊的圖片類型,比如 wmf 或 emf 類型,而是常見的 jpg 或 png 等類型的話,那么你可以看到 Word 文檔中的圖片。難道這樣就搞定了,那是不是太簡單了,其實(shí)這只是個(gè)開始。當(dāng)你通過瀏覽器的開發(fā)者工具審查 Word 解析后的 HTML 文檔后,會發(fā)現(xiàn)圖片都以 Base64 的格式進(jìn)行嵌入。如果圖片不多且單張圖片也不會太大的話,那這種方案是可以考慮的。
針對多圖或大圖的情況,一種比較好的方案是把圖片提交到文件資源服務(wù)器上。在 Mammoth.js 中要實(shí)現(xiàn)上述的功能,可以使用 「convertImage」 配置選項(xiàng)來自定義圖片處理器。具體的使用示例如下:
let options = {
convertImage: mammoth.images.imgElement(function(image) {
return image.read("base64").then(function(imageBuffer) {
return {
src: "data:" + image.contentType + ";base64," + imageBuffer
};
});
})
};
以上示例實(shí)現(xiàn)的功能就是把 Word 中的圖片進(jìn)行 Base64 編碼,然后轉(zhuǎn)成 Data URL 的形式,以實(shí)現(xiàn)圖片的顯示。很明顯這不符合我們的要求,所以我們需要做以下調(diào)整:
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 格式的圖片先轉(zhuǎn)成 Blob 對象,然后在通過創(chuàng)建 FormData 對象進(jìn)行提交。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 });}
這時(shí)把 Word 文檔轉(zhuǎn)換為 HTML 并自動把 Word 文檔中的圖片上傳至文件資源服務(wù)器的基本功能已經(jīng)實(shí)現(xiàn)了。對于 Mammoth.js 內(nèi)部是如何解析 Word 中的 XML 文件,我們就不做介紹了,反之我們來簡單介紹一下 Mammoth.js 內(nèi)部依賴的 JSZip 這個(gè)庫。
2.3 JSZip 簡介
JSZip 是一個(gè)用于創(chuàng)建、讀取和編輯 「.zip」 文件的 JavaScript 庫,含有可愛而簡單的 API。該庫的兼容性如下所示:
| Opera | Firefox | Safari | Chrome | Internet Explorer | Node.js |
|---|---|---|---|---|---|
| Yes | Yes | Yes | Yes | Yes | Yes |
| 經(jīng)過最新版本的測試 | 經(jīng)過 3.0/3.6/最新版本測試 | 經(jīng)過最新版本的測試 | 經(jīng)過最新版本的測試 | 經(jīng)過 IE 6 / 7 / 8 / 9 / 10 測試 | 經(jīng)過 Node.js 0.10 / 最新版本測試 |
2.3.1 JSZip 安裝
使用 JSZip 時(shí),你可以通過以下幾種方式進(jìn)行安裝:
「npm」:
npm install jszip「bower」:
bower install Stuk/jszip「component」 :
component install Stuk/jszip「手動」:先下載 JSZip 安裝包,然后引入
dist/jszip.js或dist/jszip.min.js文件
2.3.2 JSZip 使用示例
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 官網(wǎng),成功運(yùn)行之后,會自動下載并保存 「example.zip」 文件。該文件解壓后的目錄結(jié)構(gòu)如下所示:

三、Word 文檔轉(zhuǎn)換成 Markdown 文檔
「Markdown 是一種輕量級標(biāo)記語言」 ,創(chuàng)始人為約翰·格魯伯(英語:John Gruber)。它允許人們使用易讀易寫的純文本格式編寫文檔,然后轉(zhuǎn)換成有效的 XHTML(或者 HTML)文檔。這種語言吸收了很多在電子郵件中已有的純文本標(biāo)記的特性。
由于 Markdown 的輕量化、易讀易寫特性,并且對于圖片,圖表、數(shù)學(xué)式都有支持,目前許多網(wǎng)站都廣泛使用 Markdown 來撰寫幫助文檔或是用于論壇上發(fā)表消息。
了解完 Markdown 是什么之后,我們來分析一下如何把 Word 文檔轉(zhuǎn)換成 Markdown 文檔。對于這個(gè)功能,我們也有兩種處理方式:
- 第一種:使用 Mammoth.js 這個(gè)庫提供的
mammoth.convertToMarkdown(input, options)方法; - 第二種:基于
mammoth.convertToHtml(input, options)生成的 HTML 文檔,在利用 HTML to Markdown 的轉(zhuǎn)換工具,來間接實(shí)現(xiàn)上述功能。
下面我們來介紹第二種方案,這里我們使用 Github 上一個(gè)開源的轉(zhuǎn)換器 —— turndown,它是使用 JavaScript 開發(fā)的 HTML to Markdown 轉(zhuǎn)換器,使用起來很簡單。
首先你可以通過以下兩種方式來安裝它:
- npm:
npm install turndown - script:
<script src="https://unpkg.com/turndown/dist/turndown.js"></script>
安裝完之后,你就可以通過調(diào)用 TurndownService 構(gòu)造函數(shù),來創(chuàng)建 turndownService 實(shí)例,然后調(diào)用該實(shí)例的 turndown() 方法執(zhí)行轉(zhuǎn)換操作:
let markdown = turndownService.turndown( document.getElementById('content'))
對于前面使用的 「abao.docx」 文檔,最終轉(zhuǎn)換生成的 Markdown 文檔如下:
全棧修仙之路,聚焦全棧,專注分享 TypeScript、Web API、Node.js、Deno 等全棧干貨。
需要注意的是,TurndownService 構(gòu)造函數(shù)支持很多配置項(xiàng),這里就不詳細(xì)介紹了。感興趣的小伙伴,可以自行閱讀 turndown 官方文檔或訪問 turndown 在線示例 實(shí)際體驗(yàn)一下。
既然已經(jīng)講到 Markdown,再給小伙伴們介紹一個(gè) Github 上不錯(cuò)的開源庫 markmap,該庫使用思維導(dǎo)圖的方式來實(shí)現(xiàn) Markdown 文檔的可視化,整體效果還蠻不錯(cuò)的:
最后,我們再來看一下在前端如何動態(tài)生成 Word 文檔。
四、前端動態(tài)生成 Word 文檔
在前端如果要動態(tài)生成 Word 文檔,我們可以直接利用一些成熟的第三方開源庫,比如:docx 或 html-docx-js。
下面我們將以 docx 為例,來介紹如何在前端如何生成 「.docx」 格式的 Word 文檔。Docx 這個(gè)庫提供了優(yōu)雅的聲明式 API,讓我們可以使用 JS/TS 輕松生成 .docx 文件。此外,它還同時(shí)支持 Node.js 和瀏覽器。
Docx 這個(gè)庫為開發(fā)者提供了許多類,用于創(chuàng)建 Word 中的對應(yīng)元素,這里我們簡單介紹幾個(gè)常見的類:
- Document:用于創(chuàng)建新的 Word 文檔;
- Paragraph:用于創(chuàng)建新的段落;
- TextRun:用于創(chuàng)建文本,支持設(shè)置加粗、斜體和下劃線樣式;
- Tables:用于創(chuàng)建表格,支持設(shè)置表格每一行和每個(gè)表格單元的內(nèi)容。
接下來將使用 Docx 這個(gè)庫,來動態(tài)生成前面介紹過的 「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>\動態(tài)生成 Word 文檔示例</h1>
<button type="button" onclick="generate()">
點(diǎn)擊生成 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>
在以上示例中,當(dāng)用戶點(diǎn)擊 「點(diǎn)擊生成 Docx 文檔」 按鈕之后,會調(diào)用 generate() 回調(diào)函數(shù)。在該回調(diào)函數(shù)內(nèi),首先會創(chuàng)建新的 Document 對象,然后使用 fetch API 從 Github 上下載的頭像,當(dāng)成功獲取圖片的數(shù)據(jù)之后,會繼續(xù)調(diào)用 docx.Media.addImage() 方法添加圖片。
接著我們會調(diào)用 doc.addSection() 方法來添加 Section 塊,該塊將作為段落的容器。在示例中,我們創(chuàng)建的 Section 塊包含兩個(gè)段落,一個(gè)用于存放文本信息,而另一個(gè)用于存放圖片信息。最后我們會把 Document 對象轉(zhuǎn)換成 Blob 對象,然后通過 saveAs() 方法下載到本地。