nodejs實(shí)現(xiàn)一個(gè)word文檔解析器思路詳解
之前項(xiàng)目里遇到一個(gè)需求,需要前端上傳一個(gè)word文檔,然后后端提取出該文檔的指定位置的內(nèi)容并保存。這里后端用的是nodejs,開始接到這個(gè)需求,發(fā)現(xiàn)無(wú)從下手,主要是沒(méi)有處理過(guò)word這種類型的文檔,怎么解析? Excel倒是有相關(guān)的庫(kù)可以用,而且很簡(jiǎn)單
思路
搜索了好一會(huì)兒,在npm上發(fā)現(xiàn)了一個(gè)叫做 adm-zip 的包,這個(gè)包可以解壓縮word文檔,原來(lái)word文檔也是可以解壓縮的,之前一直不知道,通過(guò)如下代碼就可以將word文檔解壓縮,并進(jìn)一步提取內(nèi)容
var admZip = require('adm-zip'); const zip = new admZip('test.docx'); //將該docx解壓到指定文件夾result下 zip.extractAllTo("./result", /*overwrite*/true);
首先我們新建一個(gè)docx文檔,內(nèi)容如下
然后運(yùn)行上述代碼進(jìn)行解壓縮,得到如下的文件,由下圖可以看出生成了好幾個(gè)文件夾,word的內(nèi)容其實(shí)是在word文件夾里的document.xml文件內(nèi)(這里解壓縮后其實(shí)源文件還在,并沒(méi)有消失)
進(jìn)入word文件夾后的內(nèi)容
我們繼續(xù)打開document.xml文件來(lái)一探究竟里面到底是啥?注意要用瀏覽器直接打開,如果用ide打開顯示出的所有內(nèi)容都在一行,無(wú)法閱讀!
上圖只是word文檔的一部分,會(huì)發(fā)現(xiàn)word文檔內(nèi)看著只有幾段文字,但是xml中卻是長(zhǎng)篇大論,仔細(xì)分析下也很正常,xml全稱可擴(kuò)展標(biāo)記語(yǔ)言,其被設(shè)計(jì)為傳輸和存儲(chǔ)數(shù)據(jù),它僅僅是一個(gè)純文本的表示,而word中內(nèi)容格式千變?nèi)f化,肯定需要一種方法來(lái)有效描述這些內(nèi)容的格式,因此采用了xml來(lái)描述
我們嘗試一下將 測(cè)試文檔 四個(gè)字加粗變色傾斜字體,如下圖
然后再進(jìn)行解壓縮,得到docuemnt.xml并查看對(duì)應(yīng)的內(nèi)容,如下
這就很明顯了, <w:b/> 表示文字加粗, <w:i/> 表示文字傾斜, <w:color>
表示文字的顏色,所以這么4個(gè)字就需要這幾行xml來(lái)描述,因此長(zhǎng)篇大論的xml也就不足為奇
提取內(nèi)容
上面說(shuō)到了xml僅僅是一個(gè)文本的表示,我們可以用如下代碼讀取整個(gè)xml的內(nèi)容,結(jié)果是一個(gè) string
var contentXml = zip.readAsText("word/document.xml");
接下來(lái)是重點(diǎn),如何提取我們想要的內(nèi)容呢,答案是正則表達(dá)式,首先我們得分析一下word文檔的結(jié)構(gòu),word文檔其實(shí)是由叫做 Paragraph 的段落所構(gòu)成,在vb中可以很輕松的獲取并修改段落,官網(wǎng)傳送門點(diǎn)此
那么到底怎么樣才是一個(gè) Paragraph 呢,其實(shí)很簡(jiǎn)單,仔細(xì)觀察word文檔,見到下圖中的小箭頭了么,每個(gè)小箭頭前面的內(nèi)容就是一個(gè)段落,那么下圖中一共有16個(gè) Paragraph ,當(dāng)然有些段落是空的,沒(méi)有任何內(nèi)容
我們?cè)賮?lái)研究xml的結(jié)構(gòu),收起展開的xml,如下圖,發(fā)現(xiàn) <w:p></w:p> 這么個(gè)標(biāo)簽就是表示的一個(gè)段落,中間還有些 <w:p>
藏在表格內(nèi),這么一看表格前面3個(gè)段落,后面3個(gè)段落,和上圖是對(duì)應(yīng)的
因此, 我們就可以提取出每個(gè)段落的文本并返回一個(gè)數(shù)組,每一項(xiàng)就是一個(gè)段落的內(nèi)容 ,這樣就能夠完整的解析出整個(gè)word的內(nèi)容,關(guān)鍵在于如何提取每個(gè) <w:p> 的內(nèi)容,我們繼續(xù)展開一個(gè) <w:p> 進(jìn)行觀察,如下圖,發(fā)現(xiàn)內(nèi)容雖多,其實(shí)文本都保存在 <w:t> 中間,因此思路就清晰了, 首先用正則表達(dá)式提取出所有<w:p>的內(nèi)容,再針對(duì)每個(gè)<w:p>的內(nèi)容,進(jìn)行進(jìn)一步正則提取,提取出其里面所有<w:t>的內(nèi)容,并拼接在一起構(gòu)成一個(gè)段落的總內(nèi)容
具體代碼
下面是具體的提取代碼
//參數(shù)是word文件名,第二個(gè)參數(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"); //正則匹配出對(duì)應(yīng)的<w:p>里面的內(nèi)容,方法是先匹配<w:p>,再匹配里面的<w:t>,將匹配到的加起來(lái)即可 //注意?表示非貪婪模式(盡可能少匹配字符),否則只能匹配到一個(gè)<w:p></w:p> var matchedWP = contentXml.match(/<w:p.*?>.*?<\/w:p>/gi); //繼續(xù)匹配每個(gè)<w:p></w:p>里面的<w:t>,這里必須判斷matchedWP存在否則報(bào)錯(cuò) 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> 的格式是不同的,如下,多了這個(gè)space描述,所以需要特殊處理
代碼量其實(shí)很少,關(guān)鍵在于正則的編寫,上述docx文檔提取后的輸出結(jié)果如下
最后我把這個(gè)工具寫成了一個(gè)npm包,地址點(diǎn)這里
相關(guān)文章
Nodejs多站點(diǎn)切換Htpps協(xié)議詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Nodejs多站點(diǎn)切換Htpps協(xié)議詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02Express框架實(shí)現(xiàn)簡(jiǎn)單攔截器功能示例
這篇文章主要介紹了Express框架實(shí)現(xiàn)簡(jiǎn)單攔截器功能,結(jié)合實(shí)例形式分析了express框架攔截器相關(guān)功能與使用方法,需要的朋友可以參考下2023-05-05Windows 系統(tǒng)下設(shè)置Nodejs NPM全局路徑
這篇文章主要介紹了Windows 系統(tǒng)下設(shè)置Nodejs NPM全局路徑2016-04-04node.js對(duì)于數(shù)據(jù)庫(kù)MySQL基本操作實(shí)例總結(jié)【增刪改查】
這篇文章主要介紹了node.js對(duì)于數(shù)據(jù)庫(kù)MySQL基本操作,結(jié)合實(shí)例形式總結(jié)分析了node.js針對(duì)mysql數(shù)據(jù)庫(kù)基本配置、連接與增刪改查相關(guān)操作技巧,需要的朋友可以參考下2023-04-04