今天给大家介绍一个超级好用的Excel解析工具,做Excel解析的初衷是看见PM们辛苦的配置阿拉丁,感到于心不忍。听她们说什么六个人从早上八点配到晚上10点,简直吓尿。这种重复劳动的工作就应该让机器帮忙解决好么。

首先阿拉丁卡片的配置需要按照格式填写XML文件,PM首先将需要填写的信息整理成了Excel,有十几张表…每张表几十个信息要配…再把Excel的信息人工写入XML,令人发指…

Excel

于是,我们的目的很明确,找到一个工具解析Excel,得到JSON,拿到JSON数据后再生成XML。

首先在npm上找解析Excel的工具包,有一大堆,功能都大同小异。于是选了一个自己觉得比较好用的,具有API简单、数据结构清晰的特点,就是 xlsx。

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"use strict";
var XLSX = require('xlsx');
var fs = require('fs');

// 生成时间戳
var timeStamp = new Date();

// 获取Excel
const workbook = XLSX.readFile('3.xlsx');
// 获取 Excel 中所有表名
const sheetNames = workbook.SheetNames; // 返回 ['sheet1', 'sheet2']
// 根据表名获取对应某张表
const worksheet = workbook.Sheets[sheetNames[0]];

const headers = {};
const data = [];
const keys = Object.keys(worksheet);

keys
// 过滤以 ! 开头的 key
.filter(k => k[0] !== '!')
// 遍历所有单元格
.forEach(k => {
// 如 A11 中的 A
let col = k.substring(0, 1);
// 如 A11 中的 11
let row = parseInt(k.substring(1));
// 当前单元格的值
let value = worksheet[k].v;
// 保存字段名
if (row === 1) {
headers[col] = value;
return;
}
// 解析成 JSON
if (!data[row]) {
data[row] = {};
}
data[row][headers[col]] = value;
});

var str = '';
var year = '';
for (var i = 0; i < data.length; i++) {
var item = data[i];
if (item instanceof Object) {
var array = [];
array.push('<tr>');
if (item['状元']) {
array.push('\t<col><info text="' + item['状元'] + '" /></col>');
}
if (item['姓名'] && item['个人主页URL']) {
array.push('\t<col><info text="' + item['姓名'] + '" link="' + item['个人主页URL'] + '" /></col>');
}
if (item['毕业学校']) {
array.push('\t<col><info text="' + item['毕业学校'] + '" /></col>');
}
if (item['分数']) {
array.push('\t<col><info text="' + item['分数'] + '" /></col>');
}
if (item['状元笔记(命名)'] && item['笔记URL']) {
array.push('\t<col><info text="' + item['状元笔记(命名)'] + '" link="' + item['笔记URL'] + '" /></col>');
}
if (item['年份']) {
year = item['年份'];
}
if (year !== '') {
array.push('\t<tab>' + year + '年</tab>');
}
array.push('</tr>\n');
str += array.join('\n');
}
}

fs.writeFile('test.xml', str, function (e) {
if (!e) {
console.log('成功生成XML!耗时' + (new Date() - timeStamp)/1000 + 's');
} else {
console.log(e);
}
});

直接运行该 JS 文件就会根据读取的Excel生成如下格式的XML

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
<tr>
<col><info text="湖南文科状元" /></col>
<col><info text="李丹" link="http://wenku.baidu.com/p/%E6%9D%8E%E4%B8%B9_2016" /></col>
<col><info text="湘西永顺一中" /></col>
<col><info text="666" /></col>
<col><info text="语文知识点笔记" link="http://wenku.baidu.com/view/c764a9ca58fb770bf68a551d.html" /></col>
<tab>2016年</tab>
</tr>
<tr>
<col><info text="河北理科状元" /></col>
<col><info text="孟祥熙" link="http://wenku.baidu.com/p/%E5%AD%9F%E7%A5%A5%E7%86%99_2016?from=wenku" /></col>
<col><info text="衡水中学" /></col>
<col><info text="724" /></col>
<col><info text="生物知识点笔记" link="http://wenku.baidu.com/view/129c74f56bd97f192379e981.html" /></col>
<tab>2016年</tab>
</tr>
<tr>
<col><info text="河北文科状元" /></col>
<col><info text="袁嘉玮" link="http://wenku.baidu.com/p/%E8%A2%81%E5%98%89%E7%8E%AE2016" /></col>
<col><info text="衡水中学" /></col>
<col><info text="706" /></col>
<col><info text="政治错题集笔记" link="http://wenku.baidu.com/view/3ab7442ad5bbfd0a7856735e.html" /></col>
<tab>2016年</tab>
</tr>
...

用这个脚本处理一张worksheet的花费时间是0.142s,处理上述有十几张表的Excel花费了0.22s。相比之下,效率相比之前使用人力翻了 6 x 14 x 60 x 60 / 0.22 = 1 374 545 倍!PM 看到这里是不是很想死 : )

然而在将这个脚本用到配置XML之前,它被用来做了另一件事。前段时间需要将百度高考中高三三级知识点数据导入到夜莺系统作为语料,夜莺后台需要导入一份规定格式的 Excel 来生成语料,于是从RD哥哥那拿到这样一份数据。

三级知识点

将这份数据粘贴到Excel中就变成了一份有格式的Excel文件,接下来,嘿嘿嘿~

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
'use strict';
var XLSX = require('xlsx');
var parseQuestion = require('./parseQueSEOstem');
var request = require('requesst');

// 获取Excel
const workbook = XLSX.readFile('knowledge.xlsx');
// 获取 Excel 中所有表名
const sheetNames = workbook.SheetNames; // 返回 ['sheet1', 'sheet2']
// 根据表名获取对应某张表
const worksheet = workbook.Sheets[sheetNames[0]];

const headers = {};
const data = [];
const keys = Object.keys(worksheet);

keys
// 过滤以 ! 开头的 key
.filter(k => k[0] !== '!')
// 遍历所有单元格
.forEach(k => {
// 如 A11 中的 A
let col = k.substring(0, 1);
// 如 A11 中的 11
let row = parseInt(k.substring(1));
// 当前单元格的值
let value = worksheet[k].v;
// 保存字段名
if (row === 1) {
headers[col] = value;
return;
}
// 解析成 JSON
if (!data[row]) {
data[row] = {};
}
data[row][headers[col]] = value;
});

var _data = [];
var queParser = new parseQuestion();

for (var i = 0; i < data.length; i++) {
var item = data[i];
if (item instanceof Object) {
var bdjson = '';
if (item.info) {
bdjson = JSON.parse(item.info);
}
if (bdjson !== '' && bdjson.kp_info[0].content[0]) {
var knowledge = {};
knowledge['问题'] = bdjson.kp_name_3 + '是什么';
// queParser.parseBdjson(bdjson.kp_info[0].content).then(function (parseData) {
// knowledge['答案'] = parseData;
// });
knowledge['答案'] = queParser.parseQuestemContent(bdjson.kp_info[0].content);
knowledge['所属分类'] = '知识点';
_data.push(knowledge);
}
}
}

var _headers = ['问题', '答案', '所属分类']
var new_headers = _headers
// 为 _headers 添加对应的单元格位置
.map((v, i) => Object.assign({}, {v: v, position: String.fromCharCode(65+i) + 1 }))
// 转换成 worksheet 需要的结构
.reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});
var new_data = _data
// 匹配 headers 的位置,生成对应的单元格数据
.map((v, i) => _headers.map((k, j) => Object.assign({}, { v: v[k], position: String.fromCharCode(65+j) + (i+2) })))
// 对刚才的结果进行降维处理(二维数组变成一维数组)
.reduce((prev, next) => prev.concat(next))
// 转换成 worksheet 需要的结构
.reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});

// 合并 headers 和 data
var output = Object.assign({}, new_headers, new_data);
// 获取所有单元格的位置
var outputPos = Object.keys(output);
// 计算出范围
var ref = outputPos[0] + ':' + outputPos[outputPos.length - 1];
// 构建 workbook 对象
var wb = {
SheetNames: ['mySheet'],
Sheets: {
'mySheet': Object.assign({}, output, { '!ref': ref })
}
};

// 导出 Excel
XLSX.writeFile(wb, 'output.xlsx');

生成如下文件

三级知识点

这里用到了 writeFile 方法来生成 Excel,传入的第一个参数为 workbook 对象,因此需要对 JSON 格式进行处理,转化为 workbook 对象,第二个参数则是生成的文件名。

关于 xlsx 模块的 API 及实例,大家可以去 npm 上自行查看,传送门 xlsx