您好,登錄后才能下訂單哦!
之前項目里遇到一個需求,需要前端上傳一個word文檔,然后后端提取出該文檔的指定位置的內(nèi)容并保存。這里后端用的是nodejs,開始接到這個需求,發(fā)現(xiàn)無從下手,主要是沒有處理過word這種類型的文檔,怎么解析? Excel倒是有相關(guān)的庫可以用,而且很簡單
思路
搜索了好一會兒,在npm上發(fā)現(xiàn)了一個叫做 adm-zip 的包,這個包可以解壓縮word文檔,原來word文檔也是可以解壓縮的,之前一直不知道,通過如下代碼就可以將word文檔解壓縮,并進一步提取內(nèi)容
var admZip = require('adm-zip'); const zip = new admZip('test.docx'); //將該docx解壓到指定文件夾result下 zip.extractAllTo("./result", /*overwrite*/true);
首先我們新建一個docx文檔,內(nèi)容如下
然后運行上述代碼進行解壓縮,得到如下的文件,由下圖可以看出生成了好幾個文件夾,word的內(nèi)容其實是在word文件夾里的document.xml文件內(nèi)(這里解壓縮后其實源文件還在,并沒有消失)
進入word文件夾后的內(nèi)容
我們繼續(xù)打開document.xml文件來一探究竟里面到底是啥?注意要用瀏覽器直接打開,如果用ide打開顯示出的所有內(nèi)容都在一行,無法閱讀!
上圖只是word文檔的一部分,會發(fā)現(xiàn)word文檔內(nèi)看著只有幾段文字,但是xml中卻是長篇大論,仔細分析下也很正常,xml全稱可擴展標記語言,其被設(shè)計為傳輸和存儲數(shù)據(jù),它僅僅是一個純文本的表示,而word中內(nèi)容格式千變?nèi)f化,肯定需要一種方法來有效描述這些內(nèi)容的格式,因此采用了xml來描述
我們嘗試一下將 測試文檔 四個字加粗變色傾斜字體,如下圖
然后再進行解壓縮,得到docuemnt.xml并查看對應(yīng)的內(nèi)容,如下
這就很明顯了, <w:b/> 表示文字加粗, <w:i/> 表示文字傾斜, <w:color>
表示文字的顏色,所以這么4個字就需要這幾行xml來描述,因此長篇大論的xml也就不足為奇
提取內(nèi)容
上面說到了xml僅僅是一個文本的表示,我們可以用如下代碼讀取整個xml的內(nèi)容,結(jié)果是一個 string
var contentXml = zip.readAsText("word/document.xml");
接下來是重點,如何提取我們想要的內(nèi)容呢,答案是正則表達式,首先我們得分析一下word文檔的結(jié)構(gòu),word文檔其實是由叫做 Paragraph 的段落所構(gòu)成,在vb中可以很輕松的獲取并修改段落,官網(wǎng)傳送門點此
那么到底怎么樣才是一個 Paragraph 呢,其實很簡單,仔細觀察word文檔,見到下圖中的小箭頭了么,每個小箭頭前面的內(nèi)容就是一個段落,那么下圖中一共有16個 Paragraph ,當(dāng)然有些段落是空的,沒有任何內(nèi)容
我們再來研究xml的結(jié)構(gòu),收起展開的xml,如下圖,發(fā)現(xiàn) <w:p></w:p> 這么個標簽就是表示的一個段落,中間還有些 <w:p>
藏在表格內(nèi),這么一看表格前面3個段落,后面3個段落,和上圖是對應(yīng)的
因此, 我們就可以提取出每個段落的文本并返回一個數(shù)組,每一項就是一個段落的內(nèi)容 ,這樣就能夠完整的解析出整個word的內(nèi)容,關(guān)鍵在于如何提取每個 <w:p> 的內(nèi)容,我們繼續(xù)展開一個 <w:p> 進行觀察,如下圖,發(fā)現(xiàn)內(nèi)容雖多,其實文本都保存在 <w:t> 中間,因此思路就清晰了, 首先用正則表達式提取出所有<w:p>的內(nèi)容,再針對每個<w:p>的內(nèi)容,進行進一步正則提取,提取出其里面所有<w:t>的內(nèi)容,并拼接在一起構(gòu)成一個段落的總內(nèi)容
具體代碼
下面是具體的提取代碼
//參數(shù)是word文件名,第二個參數(shù)是回調(diào)表示解析完成 var parser = function parseWordDocument(absoluteWordPath,callback){ //返回內(nèi)容的數(shù)組 var resultList = []; //如果文件存在 fs.exists(absoluteWordPath, function(exists){ if(exists){ //解壓縮 const zip = new admZip(absoluteWordPath); //將document.xml(解壓縮后得到的文件)讀取為text內(nèi)容 var contentXml = zip.readAsText("word/document.xml"); //正則匹配出對應(yīng)的<w:p>里面的內(nèi)容,方法是先匹配<w:p>,再匹配里面的<w:t>,將匹配到的加起來即可 //注意?表示非貪婪模式(盡可能少匹配字符),否則只能匹配到一個<w:p></w:p> var matchedWP = contentXml.match(/<w:p.*?>.*?<\/w:p>/gi); //繼續(xù)匹配每個<w:p></w:p>里面的<w:t>,這里必須判斷matchedWP存在否則報錯 if(matchedWP){ matchedWP.forEach(function(wpItem){ //注意這里<w:t>的匹配,有可能是<w:t xml:space="preserve">這種格式,需要特殊處理 var matchedWT = wpItem.match(/(<w:t>.*?<\/w:t>)|(<w:t\s.[^>]*?>.*?<\/w:t>)/gi); var textContent = ''; if(matchedWT){ matchedWT.forEach(function(wtItem){ //如果不是<w:t xml:space="preserve">格式 if(wtItem.indexOf('xml:space')===-1){ textContent+=wtItem.slice(5,-6); }else{ textContent+=wtItem.slice(26,-6); } }); resultList.push(textContent) } }); //解析完成 callback(resultList) } }else{ callback(resultList) } }); };
注意一下如果段落前有空格,那么 <w:t> 的格式是不同的,如下,多了這個space描述,所以需要特殊處理
代碼量其實很少,關(guān)鍵在于正則的編寫,上述docx文檔提取后的輸出結(jié)果如下
最后我把這個工具寫成了一個npm包,地址點這里
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。