天天看點

【Web技術】661- 在前端如何玩轉 Word 文檔

在日常工作中,大部分人都會使用 Microsoft Office Word、WPS 或 macOS Pages 等文字處理程式進行 Word 文檔處理。除了使用上述的文字處理程式之外,對于 Word 文檔來說,還有其他的處理方式麼?答案是有的。

接下來阿寶哥将介紹在前端如何玩轉 Word 文檔,閱讀本文之後,你将了解以下内容:

  • Microsoft Office Word 支援的檔案格式和 Docx 文檔的特點;
  • 如何将 Word 文檔轉換成 HTML 文檔;
  • 如何在浏覽器中處理 ZIP 文檔;
  • 如何将 Word 文檔轉換成 Markdown 文檔;
  • 如何在前端動态生成 Word 文檔。
【Web技術】661- 在前端如何玩轉 Word 文檔

小夥伴們準備好了嗎,「玩轉 Word 文檔之旅」 開始了,Let's go!

一、Microsoft Office Word 簡介

Microsoft Office Word 是微軟公司的一個文字處理器應用程式。它最初是由 Richard Brodie 為了運作 DOS 的 IBM 計算機而在 1983 年編寫的。随後的版本可運作于 Apple Macintosh(1984 年)、SCO UNIX 和 Microsoft Windows(1989 年),并成為了 Microsoft Office 的一部分。

Word 給使用者提供了用于建立專業而優雅的文檔工具,幫助使用者節省時間,并得到優雅美觀的結果。一直以來,Microsoft Office Word 都是最流行的文字處理程式。

1.1 Word 支援的檔案格式

下表列出了常見的幾種 Word 支援的檔案格式,按擴充名的字母順序排序。

【Web技術】661- 在前端如何玩轉 Word 文檔

若想了解 Word 所有支援的格式,可參考微軟 office-file-format-reference 線上文檔。目前大家接觸比較多的是擴充名為 ​

​.docx​

​ 的文檔,是以它就是本文的主角。

1.2 Docx 文檔

俗話說 “知己知彼百戰百勝”,在 “出戰” 前我們先來簡單了解一下 「docx」 文檔。「97-2003 的舊版本檔案名字尾就是 .doc, 2007 版以後的字尾名是 .docx」。docx 格式是被壓縮過的文檔,體積更小,能處理更加複雜的内容,通路速度更快。

實際上 「docx」 文檔是一個壓縮檔案( ZIP 格式)。ZIP 檔案格式是一種資料壓縮和文檔儲存的檔案格式,原名 Deflate,發明者為菲爾·卡茨(Phil Katz),他于 1989 年 1 月公布了該格式的資料。ZIP 通常使用字尾名 “.zip”,它的 MIME 格式為 「application/zip」。

這裡阿寶哥已經提前準備了一個包含阿寶哥頭像和某些文本的 「abao.docx」 文檔,接着複制一份重命名為 「abao.zip」,然後使用 ZIP 壓縮/解壓軟體進行解壓。

【Web技術】661- 在前端如何玩轉 Word 文檔

通過觀察解壓後的目錄,我們發現 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 文檔轉換成 HTML 文檔

在日常工作中,有些時候我們希望在富文本編輯器中導入已有的 Word 文檔進行二次加工,要滿足這個需求,我們就需要先把 Word 文檔轉換成 HTML 文檔。要實作這個功能,有 「服務端轉換和前端轉換」 兩種方案:

  • 服務端轉換:對于 Java 開發者來說,可以直接基于 POI 項目,POI 是 Apache 的一個開源項目,它的初衷是處理基于 Office Open XML 标準(OOXML)和 Microsoft OLE 2 複合文檔格式(OLE2)的各種檔案格式的文檔,而且支援讀寫操作。
  • 前端轉換:對于前端開發者來說,要想在前端解析 Word 文檔,我們首先需要對 Word 文檔進行解壓,然後再進一步解析解壓後的 XML 文檔。看起來整個功能實作起來比較繁瑣,但值得慶幸的是 Mammoth.js 這個庫已經為我們實作上述功能。

在介紹如何利用 Mammoth.js 把之前建立的 Word 文檔轉換成 HTML 文檔前,我們來提前體驗一下最終的轉換效果。

【Web技術】661- 在前端如何玩轉 Word 文檔

2.1 Mammoth.js 簡介

Mammoth.js 旨在轉換 .docx 文檔(例如由 Microsoft Word 建立的文檔),并将其轉換為 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。另外文本框的内容被視為單獨的段落,出現在包含文本框的段落之後。

Mammoth.js 這個庫為我們提供了很多方法,這裡我們來介紹三個比較常用的 API:

  • ​mammoth.convertToHtml(input, options)​

    ​:把源文檔轉換為 HTML 文檔
  • ​mammoth.convertToMarkdown(input, options)​

    ​​:把源文檔轉換為 Markdown 文檔。這個方法與 ​

    ​convertToHtml​

    ​ 方法類似,差別就是傳回的 result 對象的 value 屬性是 Markdown 而不是 HTML。
  • ​mammoth.extractRawText(input)​

    ​:提取文檔的原始文本。這将忽略文檔中的所有格式。每個段落後跟兩個換行符。

介紹完 Mammoth.js 相關的特性和 API,接下來我們開始進入實戰環節。

2.2 Mammoth.js 實戰

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 這個庫。

2.3 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 / 最新版本測試
2.3.1 JSZip 安裝

使用 JSZip 時,你可以通過以下幾種方式進行安裝:

  • 「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 官網,成功運作之後,會自動下載下傳并儲存 「example.zip」 檔案。該檔案解壓後的目錄結構如下所示:

【Web技術】661- 在前端如何玩轉 Word 文檔

三、Word 文檔轉換成 Markdown 文檔

「Markdown 是一種輕量級标記語言」 ,創始人為約翰·格魯伯(英語:John Gruber)。它允許人們使用易讀易寫的純文字格式編寫文檔,然後轉換成有效的 XHTML(或者 HTML)文檔。這種語言吸收了很多在電子郵件中已有的純文字标記的特性。

由于 Markdown 的輕量化、易讀易寫特性,并且對于圖檔,圖表、數學式都有支援,目前許多網站都廣泛使用 Markdown 來撰寫幫助文檔或是用于論壇上發表消息。

了解完 Markdown 是什麼之後,我們來分析一下如何把 Word 文檔轉換成 Markdown 文檔。對于這個功能,我們也有兩種處理方式:

  • 第一種:使用 Mammoth.js 這個庫提供的​

    ​mammoth.convertToMarkdown(input, options)​

    ​ 方法;
  • 第二種:基于​

    ​mammoth.convertToHtml(input, options)​

    ​ 生成的 HTML 文檔,在利用 HTML to Markdown 的轉換工具,來間接實作上述功能。

下面我們來介紹第二種方案,這裡我們使用 Github 上一個開源的轉換器 ——  turndown,它是使用 JavaScript 開發的 HTML to Markdown 轉換器,使用起來很簡單。

首先你可以通過以下兩種方式來安裝它:

  • npm:​

    ​npm install turndown​

  • script:​

    ​<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 等全棧幹貨。

![](https://cdn.xxx.com/rich_159444942843202)      

需要注意的是,TurndownService 構造函數支援很多配置項,這裡阿寶哥就不詳細介紹了。感興趣的小夥伴,可以自行閱讀 turndown 官方文檔或通路 turndown 線上示例 實際體驗一下。

既然已經講到 Markdown,阿寶哥再給小夥伴們介紹一個 Github 上不錯的開源庫 markmap,該庫使用思維導圖的方式來實作 Markdown 文檔的可視化,整體效果還蠻不錯的:

【Web技術】661- 在前端如何玩轉 Word 文檔

(圖檔來源:https://markmap.js.org/repl/)

最後,我們再來看一下在前端如何動态生成 Word 文檔。

四、前端動态生成 Word 文檔

在前端如果要動态生成 Word 文檔,我們可以直接利用一些成熟的第三方開源庫,比如:docx 或 html-docx-js。

下面我們将以 docx 為例,來介紹如何在前端如何生成 「.docx」 格式的 Word 文檔。Docx 這個庫提供了優雅的聲明式 API,讓我們可以使用 JS/TS 輕松生成 .docx 檔案。此外,它還同時支援 Node.js 和浏覽器。

Docx 這個庫為開發者提供了許多類,用于建立 Word 中的對應元素,這裡我們簡單介紹幾個常見的類:

  • Document:用于建立新的 Word 文檔;
  • Paragraph:用于建立新的段落;
  • TextRun:用于建立文本,支援設定加粗、斜體和下劃線樣式;
  • Tables:用于建立表格,支援設定表格每一行和每個表格單元的内容。

接下來阿寶哥将使用 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/[email protected]/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()​

​ 方法下載下傳到本地。

五、參考資源

  • MDN - FileReader
  • 百度百科 - Microsoft Office Word
  • office-file-format-reference
  • Github - mammoth.js