Doc纯文本迁移到Doc表格中

Caleb ... 2020-09-25
  • Node
  • Doc
大约 5 分钟

提示

前几日,一朋友给我发来了一个文档,说是让我帮忙把文本内容复制到一个新的表格内容中。当我做完第一份后,才知道还有很多文档需要处理。所以就想着做一个工具来批量处理。

示例

# 踩坑记录

起初是这样想的:

  • 先拿到文档的内容:因为给我的文档是.doc后缀的文件,然后通过一通readFile操作,发现读出来一堆文字乱码。索性先停掉了这部分工作。
  • 获取表格数据:最开始的想法是把表格转成HTML,然后通过设定模板的方法将内容导入。但是各种工具转成的HTML都不尽人意,没样式、文字乱码。

然后发现两条路都堵死了,那不行啊。文档实在是太多了,难道真要一个一个的去复制吗?

又找了很久,发现了一个原本忽视的内容:.docx文件,作为取代.doc的格式,他的本质是一个zip文件。

DETAILS

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
1
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();
    });
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

这里主要是提供了一个思路,供大家参考。合理的转变思路可能问题解决的会更方便点。

打赏