在之前的项目中,有一个要求,前端应该上传一个word文档,然后后端应该提取文档指定位置的内容并保存。这里,nodejs用于后端。接到这个需求后,发现无从下手,主要是没有处理过word类型的文档。怎么解析?Excel确实有相关的库可以使用,而且非常简单
思路
搜索了很久,在npm上找到了一个叫adm-zip的包。这个包可以解压word文档。原始word文档也可以解压缩。我以前不知道。下面的代码可以解压缩word文档并进一步提取内容。
var AdmZIp=require(' ADM-ZIp ');const zip=new AdmZIp(' test . docx ');//将docx提取到zip.extractallto('。/result ',/* overwrite */true );首先,我们创建一个新的docx文档,内容如下
然后运行上面的代码进行解压缩,得到下面的文件。从下图可以看出,生成了几个文件夹。word的内容其实在word文件夹的document.xml文件中(这里解压后源文件还在,没有消失)
进入word文件夹后的内容
让我们继续打开document.xml文件,看看里面有什么。注意直接用浏览器打开。如果用ide打开,所有显示的内容都在一行,看不懂!
上图只是word文档的一部分,会发现word文档中只有几个段落,但却是xml格式的长话短说,仔细分析是正常的。xml被称为可扩展标记语言,旨在传输和存储数据,它只是一种纯文本表示。然而,word中的内容格式是千变万化的,因此它肯定需要一种方法来有效地描述这些内容的格式,所以使用xml来描述它。
让我们试着让测试文档的四个字加粗、变色、倾斜,如下图所示
然后解压缩得到docuemnt.xml并查看相应的内容,如下所示
很明显,w:b/表示粗体文本,w:i/表示倾斜文本,w:color表示文本的颜色,所以这四个字需要这几行xml来描述,所以啰嗦的xml也就不足为奇了。
抽提率
如上所述,xml只是一种文本表示。我们可以用下面的代码读取整个xml内容,结果是一个字符串
var content XMl=zip . ReadAsText(' word/document . XMl ');接下来就是重点了,如何提取我们想要的内容,答案是正则表达式,首先我们要分析word文档的结构,word文档实际上是由名为paragraph的段落组成的,这在vb中很容易获取和修改,官网传输的闸门点在这里
那么什么是段落呢?其实很简单。仔细看word文档,看到下图中的小箭头。每个小箭头前面的内容是一个段落,那么下图中有16个段落。当然,有些段落是空的,没有内容。
让我们再次研究xml的结构,并把扩展的xml放在一边。如下图所示,我们发现标签w:p/w:p是一个段落,中间有一些w:p
隐藏在表格中,当你看表格的前三段和后三段时,它对应于上面的图片
因此,我们可以提取每个段落的文本并返回一个数组,每个项目都是一个段落的内容,这样我们就可以完整地分析整个单词的内容。关键在于如何提取每个w:p的内容。我们继续扩展w:p进行观察。如下图所示,我们发现虽然内容很多,但文本实际上存储在w:t的中间,所以思路很清晰。首先用正则表达式提取w:p的所有内容,然后从每个w:p中提取w:t的所有内容,拼接在一起形成一个段落的总内容
绝对代码
以下是具体的提取代码
//参数为word文件名,第二个参数为callback,表示解析完成。var parser=文档的函数解析器(absolutewordpath,回调){//返回内容var resultList=[]的数组;//如果文件存在fs.exists (absolutewordpath,function(exists){ if(exists){//解压const zip=new admzip(absolutewordpath);//将document.xml(解压缩后的文件)作为文本内容读取var content XML=zip . readastext(' word/document . XML ');//通过先匹配w:p,再匹配内部的w:t,将匹配的相加,有规律地匹配对应的w:p中的内容。//注意?表示非贪婪模式(匹配字符越少越好),否则只有一个w:p/w:p var匹配WP=contentxml.match (/w:p。*?*?/w : p/gi);//继续匹配每个w:p/w:p中的w:t。这里需要判断matchedWP的存在,否则,if (matched WP) {matched WP。foreach(function(wpitem){//这里注意w:t的匹配。w : XML 3360 space=' preserve '的格式是可能的,这需要特殊处理。var匹配wt=wpitem。match (/(w:t。*?/w:t)|(w:ts.[^]*?*?/w :t)/gi);var textContent=If(匹配重量){匹配重量。foreach(function(wt item){//如果不是w : XML 3360 space=' preserve ' if(wt item)的格式。(' xml3360space')==-1)的索引{text content=wt item。slice(5)else { textContent=wtitem . slice(26,-6);} });result list . push(text content)} });//解析完成回调(result list)} } else { callback(result list)});};注意,如果段落前有空格,则w:t的格式不一样,如下,增加了这个空格描述,需要特殊处理
其实代码量很小,关键在于有规律的写。上述docx文档提取后的输出结果如下
最后,我把这个工具写成了一个npm包,地址在这里