https://github.com/SheetJS/sheetjs
我写的是网页而不是node脚本,所以配合了Antd的Upload组件来做文件上传和文件的流式读取,如果你想看整个的结合方式可以在目录里面跳转到附1部分。
reader.readAsArrayBuffer(dataFileList[key].file);
reader.onload = (e)=>{
let data = e.target.result;
let wb = Xlsx.read(data);
console.log(wb);
};
reader.onerror = (e) => {
isReadFileError = true
};
看一下文件读取的输出结果,Sheets里面包含的是他的全部工作表(Sheet)的数据。里面A1,B1这些对应的都是每个格子的数据,唯独有一个key比较特殊,就是这个 !ref
。这个代表的是这个工作表的数据范围,然后使用数据的时候就可以这样用:
let range = Xlsx.utils.decode_range(wb.Sheets['数据表设定']["!ref"])
range经过解码后可以获得开始单元格(cell)和结束单元格的坐标(说实话这个库的api设计的还是比较随便,名字都是起的怪怪的)。
看下面这个图:
s(start_cell):{c(col):0 , r(row):0}
e(end_cell):{c(col):6 , r(row):42}
这个表的数据范围就很清楚了,左上角就是A1,0行0列单元格。右下角就是42行6列单元格(当然,数据下标从0开始是我们程序员的思维,如果你需要把这个设定数据传达给一般使用者,记得加一)。
那有了一个表的范围后我们就可以遍历这些个表格了。
现在你回想一下上面我们输出的工作表格式,你就会知道我们在读取单元格的时,需要输入他在Excel里面对应的编码,比如读取1行1列的格子,我们就需要输入’A1’这个key来读取。那一个字符串显然是难以进行遍历操作的,XLSX库里面就给我们提供了一个转换的工具类。
for(let C = range.s.c; C <= range.e.c; ++C) {
// c col r row
let cell_address = {c:C, r:1};
// 将坐标转换成EXCEL的坐标比如{1, 1}转为A1
let cell_ref = Xlsx.utils.encode_cell(cell_address);
let value = wb.Sheets['数据表设定'][cell_ref].v
}
搭配我们上面说到的!ref我们就可以循环遍历这个表了。
然后这里有一个你需要非常注意的点,我们来看一下cell的输出结构:
你会发现他是有一堆东西的,我现在只用到了v
和t
。
我这里要提醒你的是,读取完之后你只是获得了这个cell的对象,具体的数值你还需要往下调用一个.v,我这个记性不好,好几次都是这个问题。
t type: b Boolean, e Error, n Number, d Date, s Text, z Stub
v raw value (see Data Types section for more info)
r rich text encoding (if applicable)
h HTML rendering of the rich text (if applicable)
w formatted text (if applicable)
直接访问下面的链接可以看到全部的数据信息
https://github.com/SheetJS/sheetjs#cell-object
文档链接:
https://github.com/SheetJS/sheetjs#modifying-cell-values
这个我主要是用到XLSX.utils.sheet_add_aoa
这个函数。
当你修改单个单元格:
XLSX.utils.sheet_add_aoa(worksheet, [[new_value]], { origin: address });
修改多个单元格:
XLSX.utils.sheet_add_aoa(worksheet, aoa, opts);
说实话他这个文档写的我是真看不懂,我们直接来研究他给的代码实例:
XLSX.utils.sheet_add_aoa(worksheet, [
[1], // <-- 把1写入到B3里面
, // <-- 第4行啥也不做
[/*B5*/, /*C5*/, /*D5*/, "abc"] // <-- 把abc写到E5里面
], { origin: "B3" });
聪明的你一看这个代码就能看懂了吧,也明白了为什么单个单元格用的是 [[new_value]]
。
文档链接:
https://github.com/SheetJS/sheetjs#parsing-workbooks
保存文件目前我只测试了最简单的这个writeFile
就是直接将wb写成xlsx的,保存文件后会自动弹出下载窗口。
Xlsx.writeFile(configWb, 'test.xlsx')
后续在使用过程中遇到解决一些问题可能会持续更新。
毕竟用js来做数据分析的人比较少,所以库的使用比起pandas那是不方便多了。但是python的传播行在非IT公司还是比较麻烦的,环境打包运行也很慢,所以还是尝试用js来做这个,看看后续能不能将一些简答的pandas的功能稍微实现一下。
顺便在这里记录一个库,https://github.com/davidchambers/string-format。可以让js比较像python一样使用formate字符串拼接,这点也是我想不明白的,python的formate这么好用,为啥js的原生的这么难用,不学习一下吗?
写代码有所进步真的能令人开心呢,我是llsxily,你可以叫我橘子。
简单来说思路就是调用Upload的beforeUpload
方法,在文件上传前获取到文件,获取到文件后就可以将数据读取到Buffer然后调用Xlsx.read
解析xlsx文件。
const uploadConfigProps = {
name: 'file',
maxCount: 1,
beforeUpload: file => {
console.log(file)
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = (e) => {
console.log('read_file')
let data = e.target.result;
console.log(data)
let wb = Xlsx.read(data)
console.log(wb)
// !ref获取的是表格的规模大小
let range = Xlsx.utils.decode_range(wb.Sheets['数据表设定']["!ref"])
console.log(range)
...
}
}
return false;
}
}
这个是配合Dragger使用
<Dragger
style={{width: '100%'}}
{...uploadConfigProps}
>
<p className="ant-upload-drag-icon">
<InboxOutlined />
p>
<p className="ant-upload-text">点击或拖动文件至该区域上传p>
Dragger>
Upload的代码也是基本类似的,这里我加上了return Upload.LIST_IGNORE;
这个返回值可以使得上传的文件不会出现在Upload的上传队列里面同时showUploadList={false}
这个代码也是同样的效果,请注意甄别。
{
console.log(file)
return Upload.LIST_IGNORE;
}}
>
<a>重新上传a>
Upload>
这个功能可以查看我上一篇文章
React-js中等待多个回调全部完成后执行后续函数