【Web技術】423- 在前端 Word 還能這樣玩

  • 2019 年 11 月 27 日
  • 筆記

一、背景概述

前陣子聽到公司運營的小姐姐們在抱怨,說在富文本編輯器中發布包含圖片的 Word 文檔時,圖片和文本內容不能一起複制,每次她們都得分開處理,對於包含較多圖片的 Word 時,她們處理起來很抓狂。目前她們所使用後台的富文本編輯器是 Ueditor,剛好近期也在研究一款富文本編輯器 —— Editor.js(block styled editor ),也會遇到這種問題,所以就自覺攬下這個小任務。

要解決上述的問題,首先就需要能夠解析 Word 文檔中的圖片。目前 Word 有兩種格式後綴分別是 .doc 和 .docx。97-2003 的舊版本文件名後綴就是 .doc, 2007 版以後的後綴名是 .docx。docx 格式是被壓縮過的文檔,體積更小,能處理更加複雜的內容,訪問速度更快。

對於上述兩種格式的 Word 文檔,大家應該都很熟悉。但估計挺多小夥伴不知道 Word 文檔是如何存儲內容的,這裡我們以 docx 格式為例。這裡我已經提前準備了一個包含圖片和文本的 word2html.docx 文件,然後複製一份重命名為 word2html.rar。看到 rar 後綴相信你已經猜到了,下一步我們要執行解壓操作。當完成解壓操作之後,默認在當前目錄會生成一個 word2html 文件夾,該文件夾的主要目錄結構如下:

感興趣的小夥伴可以自行解壓一下 Word 文檔,簡單分析一下解壓後的文件。

經過本人認真觀察後發現,在解壓後 Word 文檔中包含的圖片會被保存到 word/media 目錄下。而我們要解決的問題就是能識別到 Word 文檔中的圖片,然後自動上傳到文件資源伺服器。要實現這個功能的前提就是能夠解析當前的 Word 文檔,值得慶幸的是這個功能已經有前人幫我們實現了。

對於 Java 開發者來說,可以直接基於 POI 項目,POI 是 Apache 的一個開源項目,它的初衷是處理基於 Office Open XML 標準(OOXML)和 Microsoft OLE 2 複合文檔格式(OLE2)的各種文件格式的文檔,而且支援讀寫操作。當然本文的重點不是服務端解析方案,而是在前端如何實現 Word 解析並提取 Word 中的圖片。同樣對於純前端的解析方案,mwilliamson 大佬已經幫我們實現了,下面我們來簡單介紹一下 Mammoth.js 這個庫。

二、Mammoth.js

2.1 Mammoth.js 簡介

Mammoth 旨在轉換 .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。另外文本框的內容被視為單獨的段落,出現在包含文本框的段落之後。

2.2 Mammoth.js API

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

  • mammoth.convertToHtml(input,options:把源文檔轉換為 HTML 文檔
  • mammoth.convertToMarkdown(input,options):把源文檔轉換為 Markdown 文檔。這個方法與 convertToHtml 方法類似,區別就是返回的 result 對象的 value 屬性是 Markdown 而不是 HTML。
  • 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 數據對象。下面我們定義一個 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 })

如果你的文檔中不包括特殊的圖片類型,比如 wmfemf 類型,而是常見的 jpgpng 等類型的話,那麼你可以看到 Word 文檔中的圖片。難道這樣就搞定了,那是不是太簡單了,其實這只是剛開始。當你通過瀏覽器的開發者工具審查 Word 解析後的 HTML 文檔後,會發現圖片都以 Base64 的格式進行嵌入。如果圖片不多且單張圖片也不會太大的話,那這種方案是可以考慮的。針對這種情況,一種比較好的方案是把圖片提交到文件資源伺服器上。

在 Mammoth.js 中要實現上述的功能,可以使用 convertImage 配置選項來自定義圖片處理器。使用示例如下:

var options = {    convertImage: mammoth.images.imgElement(function(image) {        return image.read("base64").then(function(imageBuffer) {            return {                src: "data:" + image.contentType + ";base64," + imageBuffer            };        });    })};

上面示例實現的功能就是把圖片轉成 Base64 的格式,很明顯不符合我們的要求。這裡我們需要做以下調整:

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 方法的定義如下:

export function 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 });}

至此解析 Word 文檔並自動把文檔中的圖片上傳至文件資源伺服器的基本功能已經實現了。目前該方案遇到的問題就是無法處理 wmfemf 類型的圖片文件,針對這個問題一開始就想到了七牛雲的圖片處理服務,但閱讀官方相關的使用文檔後,發現所有的圖片處理服務均不支援 wmfemf 類型。當然,期間也嘗試了國外在線的圖片格式化服務和網上一些大佬提供的格式化方案,可惜的是最終的效果都不好,所以對於這種特殊的圖片格式目前的解決方案就是讓用戶手動上傳對應原始圖片,如果小夥伴們有好的方案,歡迎給我留言喲。

四、參考資源

  • MDN – FileReader
  • Github – mammoth.js