前端處理 Word 文檔

對于 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í)如果你的文檔中不包括特殊的圖片類型,比如 wmfemf 類型,而是常見的 jpgpng 等類型的話,那么你可以看到 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.jsdist/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 等全棧干貨。![](https://cdn.xxx.com/rich_159444942843202)

需要注意的是,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() 方法下載到本地。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容