nodejs实现一个word文档解析器思路详解


Posted in NodeJs onAugust 14, 2018

之前项目里遇到一个需求,需要前端上传一个word文档,然后后端提取出该文档的指定位置的内容并保存。这里后端用的是nodejs,开始接到这个需求,发现无从下手,主要是没有处理过word这种类型的文档,怎么解析? Excel倒是有相关的库可以用,而且很简单

思路

搜索了好一会儿,在npm上发现了一个叫做 adm-zip 的包,这个包可以解压缩word文档,原来word文档也是可以解压缩的,之前一直不知道,通过如下代码就可以将word文档解压缩,并进一步提取内容

var admZip = require('adm-zip');
const zip = new admZip('test.docx');
//将该docx解压到指定文件夹result下
zip.extractAllTo("./result", /*overwrite*/true);

首先我们新建一个docx文档,内容如下

nodejs实现一个word文档解析器思路详解 

然后运行上述代码进行解压缩,得到如下的文件,由下图可以看出生成了好几个文件夹,word的内容其实是在word文件夹里的document.xml文件内(这里解压缩后其实源文件还在,并没有消失)

nodejs实现一个word文档解析器思路详解 

进入word文件夹后的内容

nodejs实现一个word文档解析器思路详解 

我们继续打开document.xml文件来一探究竟里面到底是啥?注意要用浏览器直接打开,如果用ide打开显示出的所有内容都在一行,无法阅读!

nodejs实现一个word文档解析器思路详解 

上图只是word文档的一部分,会发现word文档内看着只有几段文字,但是xml中却是长篇大论,仔细分析下也很正常,xml全称可扩展标记语言,其被设计为传输和存储数据,它仅仅是一个纯文本的表示,而word中内容格式千变万化,肯定需要一种方法来有效描述这些内容的格式,因此采用了xml来描述

我们尝试一下将 测试文档 四个字加粗变色倾斜字体,如下图

nodejs实现一个word文档解析器思路详解 

然后再进行解压缩,得到docuemnt.xml并查看对应的内容,如下

nodejs实现一个word文档解析器思路详解 

这就很明显了, <w:b/> 表示文字加粗, <w:i/> 表示文字倾斜, <w:color>
表示文字的颜色,所以这么4个字就需要这几行xml来描述,因此长篇大论的xml也就不足为奇

提取内容

上面说到了xml仅仅是一个文本的表示,我们可以用如下代码读取整个xml的内容,结果是一个 string

var contentXml = zip.readAsText("word/document.xml");

接下来是重点,如何提取我们想要的内容呢,答案是正则表达式,首先我们得分析一下word文档的结构,word文档其实是由叫做 Paragraph 的段落所构成,在vb中可以很轻松的获取并修改段落,官网传送门点此

nodejs实现一个word文档解析器思路详解 

那么到底怎么样才是一个 Paragraph 呢,其实很简单,仔细观察word文档,见到下图中的小箭头了么,每个小箭头前面的内容就是一个段落,那么下图中一共有16个 Paragraph ,当然有些段落是空的,没有任何内容

nodejs实现一个word文档解析器思路详解 

我们再来研究xml的结构,收起展开的xml,如下图,发现 <w:p></w:p> 这么个标签就是表示的一个段落,中间还有些 <w:p>

藏在表格内,这么一看表格前面3个段落,后面3个段落,和上图是对应的

nodejs实现一个word文档解析器思路详解 

因此, 我们就可以提取出每个段落的文本并返回一个数组,每一项就是一个段落的内容 ,这样就能够完整的解析出整个word的内容,关键在于如何提取每个 <w:p> 的内容,我们继续展开一个 <w:p> 进行观察,如下图,发现内容虽多,其实文本都保存在 <w:t> 中间,因此思路就清晰了, 首先用正则表达式提取出所有<w:p>的内容,再针对每个<w:p>的内容,进行进一步正则提取,提取出其里面所有<w:t>的内容,并拼接在一起构成一个段落的总内容

nodejs实现一个word文档解析器思路详解 

具体代码

下面是具体的提取代码

//参数是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文档提取后的输出结果如下

nodejs实现一个word文档解析器思路详解 

最后我把这个工具写成了一个npm包,地址点这里

NodeJs 相关文章推荐
NodeJs中的非阻塞方法介绍
Jun 05 NodeJs
nodejs中简单实现Javascript Promise机制的实例
Dec 06 NodeJs
浅谈Nodejs中的作用域问题
Dec 26 NodeJs
NodeJS配置HTTPS服务实例分享
Feb 19 NodeJs
详谈Angular路由与Nodejs路由的区别
Mar 05 NodeJs
nodejs搭建本地http服务器教程
Mar 13 NodeJs
nodeJS微信分享
Dec 20 NodeJs
nodejs使用http模块发送get与post请求的方法示例
Jan 08 NodeJs
nodejs 最新版安装npm 的使用详解
Jan 18 NodeJs
详解redis在nodejs中的应用
May 02 NodeJs
nodejs对项目下所有空文件夹创建gitkeep的方法
Aug 02 NodeJs
NodeJs项目中关闭ESLint的方法
Aug 09 #NodeJs
nodejs之koa2请求示例(GET,POST)
Aug 07 #NodeJs
NodeJS实现自定义流的方法
Aug 01 #NodeJs
nodejs 生成和导出 word的实例代码
Jul 31 #NodeJs
nodejs(officegen)+vue(axios)在客户端导出word文档的方法
Jul 31 #NodeJs
nodejs 十六进制字符串型数据与btye型数据相互转换
Jul 30 #NodeJs
NodeJS 中Stream 的基本使用
Jul 30 #NodeJs
You might like
PHP关于IE下的iframe跨域导致session丢失问题解决方法
2013/10/10 PHP
详解 PHP加密解密字符串函数附源码下载
2015/12/18 PHP
动态创建的表格单元格中的事件实现代码
2008/12/30 Javascript
js onpropertychange输入框 事件获取属性
2009/03/26 Javascript
Lazy Load 延迟加载图片的 jQuery 插件
2010/02/06 Javascript
利用javascript的面向对象的特性实现限制试用期
2011/08/04 Javascript
javascript中自定义对象的属性方法分享
2013/07/12 Javascript
javascript制作2048游戏
2015/03/30 Javascript
AngularJS仿苹果滑屏删除控件
2016/01/18 Javascript
微信小程序侧边栏滑动特效(左右滑动)
2017/01/23 Javascript
详解Angular.js指令中scope类型的几种特殊情况
2017/02/21 Javascript
js获取一组日期中最近连续的天数
2017/05/25 Javascript
javascript浅层克隆、深度克隆对比及实例解析
2020/02/09 Javascript
解决vue中使用less/sass及使用中遇到无效的问题
2020/10/24 Javascript
[01:32]2016国际邀请赛中国区预选赛IG战队首日赛后采访
2016/06/27 DOTA
python字符串连接方式汇总
2014/08/21 Python
Python中格式化format()方法详解
2017/04/01 Python
Python可变参数用法实例分析
2017/04/02 Python
Python中工作日类库Busines Holiday的介绍与使用
2017/07/06 Python
详解Python sys.argv使用方法
2019/05/10 Python
python Django中models进行模糊查询的示例
2019/07/18 Python
使用python绘制温度变化雷达图
2019/10/18 Python
全网最细 Python 格式化输出用法讲解(推荐)
2021/01/18 Python
使用HTML5的File实现base64和图片的互转
2013/08/01 HTML / CSS
HTML5中使用postMessage实现两个网页间传递数据
2016/06/22 HTML / CSS
伦敦最著名的老字号百货公司:Selfridges(塞尔福里奇百货)
2016/07/25 全球购物
ECCO爱步加拿大官网:北欧丹麦鞋履及皮具品牌
2017/07/08 全球购物
纽约州一群才华横溢的金匠制作而成:Hearth Jewelry
2019/03/22 全球购物
澳大利亚个性化儿童礼品网站:Bright Star Kids
2019/06/14 全球购物
写好自荐信的技巧
2013/11/08 职场文书
毕业求职自荐信格式是什么
2013/11/19 职场文书
平民服装店创业计划书
2014/01/17 职场文书
会计专业毕业生求职信
2014/07/04 职场文书
幼儿园大班开学寄语(2016秋季)
2015/12/03 职场文书
银行求职信怎么写
2019/06/20 职场文书
2019已经过半,你知道年中工作总结该怎么写吗?
2019/07/03 职场文书