以前項目裏遇到一個需求,須要前端上傳一個word文檔,而後後端提取出該文檔的指定位置的內容並保存。這裏後端用的是nodejs,開始接到這個需求,發現無從下手,主要是沒有處理過word這種類型的文檔,怎麼解析? Excel卻是有相關的庫能夠用,並且很簡單前端
搜索了好一下子,在npm上發現了一個叫作adm-zip
的包,這個包能夠解壓縮word文檔,原來word文檔也是能夠解壓縮的,以前一直不知道,經過以下代碼就能夠將word文檔解壓縮,並進一步提取內容node
var admZip = require('adm-zip');
const zip = new admZip('test.docx');
//將該docx解壓到指定文件夾result下
zip.extractAllTo("./result", /*overwrite*/true);
複製代碼
首先咱們新建一個docx文檔,內容以下正則表達式
而後運行上述代碼進行解壓縮,獲得以下的文件,由下圖能夠看出生成了好幾個文件夾,word的內容實際上是在word文件夾裏的document.xml文件內(這裏解壓縮後其實源文件還在,並無消失)npm
進入word文件夾後的內容 後端
咱們嘗試一下將測試文檔
四個字加粗變色傾斜字體,以下圖數組
<w:b/>
表示文字加粗,
<w:i/>
表示文字傾斜,
<w:color>
表示文字的顏色,因此這麼4個字就須要這幾行xml來描述,所以長篇大論的xml也就不足爲奇
上面說到了xml僅僅是一個文本的表示,咱們能夠用以下代碼讀取整個xml的內容,結果是一個string
瀏覽器
var contentXml = zip.readAsText("word/document.xml");
複製代碼
接下來是重點,如何提取咱們想要的內容呢,答案是正則表達式,首先咱們得分析一下word文檔的結構,word文檔實際上是由叫作Paragraph
的段落所構成,在vb中能夠很輕鬆的獲取並修改段落,官網傳送門點此ide
那麼到底怎麼樣纔是一個Paragraph
呢,其實很簡單,仔細觀察word文檔,見到下圖中的小箭頭了麼,每一個小箭頭前面的內容就是一個段落,那麼下圖中一共有16個Paragraph
,固然有些段落是空的,沒有任何內容工具
<w:p></w:p>
這麼個標籤就是表示的一個段落,中間還有些
<w:p>
藏在表格內,這麼一看錶格前面3個段落,後面3個段落,和上圖是對應的
<w:p>
的內容,咱們繼續展開一個
<w:p>
進行觀察,以下圖,發現內容雖多,其實文本都保存在
<w:t>
中間,所以思路就清晰了,
首先用正則表達式提取出全部<w:p>的內容,再針對每一個<w:p>的內容,進行進一步正則提取,提取出其裏面全部<w:t>的內容,並拼接在一塊兒構成一個段落的總內容
下面是具體的提取代碼測試
//參數是word文件名,第二個參數是回調錶示解析完成
var parser = function parseWordDocument(absoluteWordPath,callback){
//返回內容的數組
var resultList = [];
//若是文件存在
fs.exists(absoluteWordPath, function(exists){
if(exists){
//解壓縮
const zip = new admZip(absoluteWordPath);
//將document.xml(解壓縮後獲得的文件)讀取爲text內容
var contentXml = zip.readAsText("word/document.xml");
//正則匹配出對應的<w:p>裏面的內容,方法是先匹配<w:p>,再匹配裏面的<w:t>,將匹配到的加起來便可
//注意?表示非貪婪模式(儘量少匹配字符),不然只能匹配到一個<w:p></w:p>
var matchedWP = contentXml.match(/<w:p.*?>.*?<\/w:p>/gi);
//繼續匹配每一個<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描述,因此須要特殊處理
代碼量其實不多,關鍵在於正則的編寫,上述docx文檔提取後的輸出結果以下
最後我把這個工具寫成了一個npm包,地址點這裏