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 相关文章推荐
基于promise.js实现nodejs的promises库
Jul 06 NodeJs
基于 Docker 开发 NodeJS 应用
Jul 30 NodeJs
NodeJS制作爬虫全过程(续)
Dec 22 NodeJs
nodejs中实现路由功能
Dec 29 NodeJs
nodejs入门教程二:创建一个简单应用示例
Apr 24 NodeJs
nodejs入门教程五:连接数据库的方法分析
Apr 24 NodeJs
详解NODEJS基于FFMPEG视频推流测试
Nov 17 NodeJs
nodejs acl的用户权限管理详解
Mar 14 NodeJs
Nodejs异步流程框架async的方法
Jun 07 NodeJs
nodejs一个简单的文件服务器的创建方法
Sep 13 NodeJs
nodejs环境使用Typeorm连接查询Oracle数据
Dec 05 NodeJs
基于NodeJS开发钉钉回调接口实现AES-CBC加解密
Aug 20 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
基于mysql的论坛(2)
2006/10/09 PHP
用mysql内存表来代替php session的类
2009/02/01 PHP
使用laravel的Eloquent模型如何获取数据库的指定列
2019/10/17 PHP
thinkphp5 框架结合plupload实现图片批量上传功能示例
2020/04/04 PHP
php7 参数、整形及字符串处理机制修改实例分析
2020/05/25 PHP
Mootools 1.2教程(21)——类(二)
2009/09/15 Javascript
JavaScript高级程序设计 事件学习笔记
2011/09/10 Javascript
NodeJS url验证(url-valid)的使用方法
2013/11/18 NodeJs
一些老手都不一定知道的JavaScript技巧
2014/05/06 Javascript
原生js实现jquery函数animate()动画效果的简单实例
2016/08/21 Javascript
BootStrap实现带有增删改查功能的表格(DEMO详解)
2016/10/26 Javascript
浅谈React 属性和状态的一些总结
2016/11/21 Javascript
js控制台输出的方法(详解)
2016/11/26 Javascript
原生JS京东轮播图代码
2017/03/22 Javascript
Vuex之理解Store的用法
2017/04/19 Javascript
JavaScript实现简单评论功能
2017/08/17 Javascript
vue使用监听实现全选反选功能
2018/07/06 Javascript
解决vue语法会有延迟加载显现{{xxx}}的问题
2019/11/14 Javascript
jquery检测上传文件大小示例
2020/04/26 jQuery
Openlayers显示地理位置坐标的方法
2020/09/28 Javascript
Javascript表单序列化原理及实现代码详解
2020/10/30 Javascript
[03:56]显微镜下的DOTA2第十一期——鬼畜的死亡先知播音员
2014/06/23 DOTA
[04:40]2016国际邀请赛中国区预选赛全程TOP10镜头集锦
2016/07/01 DOTA
[49:21]2018DOTA2亚洲邀请赛3月30日 小组赛B组 Effect VS iG
2018/03/31 DOTA
Python version 2.7 required, which was not found in the registry
2014/08/26 Python
Python实现的网页截图功能【PyQt4与selenium组件】
2018/07/12 Python
Python实现iOS自动化打包详解步骤
2018/10/03 Python
python正则表达式去除两个特殊字符间的内容方法
2018/12/24 Python
python读取与处理netcdf数据方式
2020/02/14 Python
Python换行与不换行的输出实例
2020/02/19 Python
python如何查看网页代码
2020/06/07 Python
css3实现动画的三种方式
2020/08/24 HTML / CSS
使用phonegap操作数据库的实现方法
2017/03/31 HTML / CSS
入党积极分子党小组意见
2015/06/02 职场文书
物资采购管理制度
2015/08/06 职场文书
Java实现带图形界面的聊天程序
2022/06/10 Java/Android