前几日,一朋友给我发来了一个文档,说是让我帮忙把文本内容复制到一个新的表格内容中。当我做完第一份后,才知道还有很多文档需要处理。所以就想着做一个工具来批量处理。
👉 踩坑记录
起初是这样想的:
- 先拿到文档的内容:因为给我的文档是
.doc
后缀的文件,然后通过一通readFile
操作,发现读出来一堆文字乱码。索性先停掉了这部分工作。 - 获取表格数据:最开始的想法是把表格转成
HTML
,然后通过设定模板的方法将内容导入。但是各种工具转成的HTML
都不尽人意,没样式、文字乱码。
然后发现两条路都堵死了,那不行啊。文档实在是太多了,难道真要一个一个的去复制吗?
又找了很久,发现了一个原本忽视的内容:.docx
文件,作为取代.doc
的格式,他的本质是一个zip文件。
更多
docx是微软Word的文件扩展名,Microsoft Office2007之后版本使用,其基于Office Open XML标准的压缩文件格式取代了其以前专有的默认文件格式,在传统的文件名扩展名后面添加了字母“x”(即“.docx”取代“.doc”、“.xlsx”取代“.xls”、“.pptx”取代“.ppt”)。任何能够打开DOC文件的文字处理软件都可以将该文档转换为DOCX文件,docx文件比doc文件所占用空间更小,docx格式的文件本质上是一个XML文件
。
docx格式的文件本质上是一个ZIP文件
。将一个docx文件的后缀改为ZIP后是可以用解压工具打开或是解压的。事实上,Word2007的基本文件就是ZIP格式的,他可以算作是docx文件的容器。
docx 格式文件的主要内容是保存为XML格式的,但文件并非直接保存于磁盘。它是保存在一个ZIP文件中,然后取扩展名为docx。将 .docx 格式的文件后缀改为ZIP后解压, 可以看到解压出来的文件夹中有word这样一个文件夹,它包含了Word文档的大部分内容。而其中的document.xml
文件则包含了文档的主要文本内容。
有了这个信息后,我觉得应该有希望了。第一步先被搁置了,我们从第二步开始。既然有了xml文件,那么就可以使用模板来进行xml的填充了。那就开始处理表格模板。
我们先把想要的字段都用标识字符进行占位(切记使用完整且准确的英文,不然会自动切割字符)
有了模板以后,通过npm包adm-zip
来直接解压表格模板数据。发现解压出来的document.xml
里面已经包含了之前定义的标识占位符。
那么填充就显得很简单了。通过readFile
读取到xml文件,然后替换的内容就完美填充到各个字段了。
到这里看起来后面的步骤已经完成了,但是第一步如何获取基础文档的内容呢。随后我在互联网的海洋中翻找了很久,找到了一个npm包@gmr-fms/word-extractor
。可以直接读取到doc文档内容(在此感谢大佬)
通过@gmr-fms/word-extractor
的支持,可以拿到整个文档,通过正则筛选出了我想要的字段内容,然后将内容替换给document.xml
。完成内容的更改。
最后一步就是将第二步解压出来的文档文件再压缩成一个.docx
文件,这里我使用的是archiver
,可以直接将文件压缩为.docx
。
👉 代码实现
先装依赖
npm i @gmr-fms/word-extractor adm-zip archiver
const fs = require('fs')
const path = require('path')
// 读取doc文档工具
const extract = require('@gmr-fms/word-extractor');
// 直接解压docx文件
var admZip = require('adm-zip');
// 压缩文件
const archiver = require('archiver');
// 在doc文件夹下存放的是将要处理的文档
var files = fs.readdirSync(path.resolve(__dirname, './doc'))
files.forEach(i => {
var fileName = i.split("_")[0];
extract.fromFile(path.resolve(__dirname, './doc/' + i)).then(doc => {
var body = doc.getBody();
var number = body.match(/第.*单元/g)[0].split('第')[1].split('单元')[0];
var name = body.match(/第.*课 .*/g)[0].split(' ')[1]
var time = body.split('三、教学时间')[1].split('四、教学过程')[0].replace('\n', '').replace('\n', '');
var target = body.split('一、教学目标')[1].split('二、教学准备')[0].replace('\n', '');
var have = body.split('二、教学准备')[1].split('三、教学时间')[0].replace('\n', '')
var procedure = body.split('四、教学过程')[1].replace('\n', '')
// template下存放的是表格模板文件 index.docx 为表格模板
var zip = new admZip(`./template/index.docx`);
// result文件夹存放的是解压出来的表格模板文件夹
zip.extractAllTo(`./result/${fileName}`, true);
var xml = fs.readFileSync(`./result/${fileName}/word/document.xml`) // 读取document.xml
var xmlStr = xml.toString()
// 开始替换文档
xmlStr = xmlStr.replace(/number/g, number);
xmlStr = xmlStr.replace(/name/g, name);
xmlStr = xmlStr.replace(/time/g, time);
xmlStr = xmlStr.replace(/target/g, target);
xmlStr = xmlStr.replace(/have/g, have);
xmlStr = xmlStr.replace(/procedure/g, procedure)
// 重写文档
fs.writeFileSync(`./result/${name}/word/document.xml`, xmlStr)
var archive = archiver('zip', {
zlib: {
level: 8
}
}).on('error', function (err) {
throw err;
});
// 压缩到 zip 文件夹下的是最终生成的带表格的文档
var output = fs.createWriteStream(__dirname + `/zip/${name}.docx`)
.on('close', function (err) {
if (err) {
console.log('关闭archiver异常:', err);
return;
}
console.log(`已生成${name}文件`);
});
archive.pipe(output);
archive.directory(`./result/${name}/`, '');
archive.finalize();
});
})
这里主要是提供了一个思路,供大家参考。合理的转变思路可能问题解决的会更方便点。