Node.js利用js-xlsx处理Excel文件的方法详解


Posted in Javascript onJuly 05, 2017

简介

本文介绍用 Node.js 中的 js-xlsx 库来处理 Excel 文件。

js-xlsx 库是目前 Github 上 star 数量最多的处理 Excel 的库,功能强大,但上手难度稍大。文档有些乱,不适合快速上手。

本文对 js-xlsx 库进行一定的总结,并提供几个实用的例子供读者测试,学习,交流。

安装

$ npm install xlsx

一些概念

在使用这个库之前,先介绍库中的一些概念。

  • workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。
  • worksheet 对象,指的是 Excel 文档中的表。我们知道一份 Excel 文档中可以包含很多张表,而每张表对应的就是 worksheet 对象。
  • cell 对象,指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象。

它们的关系如下:

// workbook
{
 SheetNames: ['sheet1', 'sheet2'],
 Sheets: {
 // worksheet
 'sheet1': {
  // cell
  'A1': { ... },
  // cell
  'A2': { ... },
  ...
 },
 // worksheet
 'sheet2': {
  // cell
  'A1': { ... },
  // cell
  'A2': { ... },
  ...
 }
 }
}

用法

基本用法

  • XLSX.readFile 打开 Excel 文件,返回 workbook
  • workbook.SheetNames 获取表名
  • workbook.Sheets[xxx] 通过表名获取表格
  • 按自己的需求去处理表格
  • 生成新的 Excel 文件

具体用法

读取 Excel 文件

import XLSX from 'xlsx';
const workbook = XLSX.readFile('someExcel.xlsx', opts);

获取 Excel 文件中的表

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

通过 worksheet[address] 来操作表格,以 ! 开头的 key 是特殊的字段。

// 获取 A1 单元格对象
let a1 = worksheet['A1']; // 返回 { v: 'hello', t: 's', ... }
// 获取 A1 中的值
a1.v // 返回 'hello'

// 获取表的有效范围
worksheet['!ref'] // 返回 'A1:B20'
worksheet['!range'] // 返回 range 对象,{ s: { r: 0, c: 0}, e: { r: 100, c: 2 } }

// 获取合并过的单元格
worksheet['!merges'] // 返回一个包含 range 对象的列表,[ {s: { r: 0, c: 0 }, c: { r: 2, c: 1 } } ]

实战

解析 Excel 生成 JSON

Tips 事实上,你可以直接通过 XLSX.utils.sheet_to_json(worksheet) 获得同样的结果

注意 本例子中假设表的第一行为字段名

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;
 });

console.log(data); // [ { '姓名': 'test1', '年龄': 20 }, { '姓名': 'test2', '年龄': 10 } ... ]

合并表格

步骤:

  • 读取多份表格
  • 合并数组

Tips: 其实合并表格跟 XLSX 没什么关系,只是处理几个数组而已。

sheet1

id name age
1 test1 30
2 test2 20
3 test3 18

sheet2

id country remark
1 China hello
2 America world
3 Unkonw ???
let sheet1 = XLSX.utils.sheet_to_json(sheet1);
let sheet2 = XLSX.utils.sheet_to_json(sheet2);

// 先合并 sheet1 和 sheet2,再对统一处理
const result = sheet1.concat(sheet2).reduce((prev, next) => {
 let index = prev.findIndex((elem, i) => elem.id === next.id);

 if (index === -1) {
  return prev.concat(next);
 } else {
  prev[index] = Object.assign({}, prev[index], next);
  return prev;
 }
}, []);
console.log(result);

// [ { id: '1',
// name: 'test1',
// age: '30',
// country: 'China',
// remark: 'hello' },
// { id: '2',
// name: 'test2',
// age: '20',
// country: 'America',
// remark: 'world' },
// { id: '3',
// name: 'test3',
// age: '18',
// country: 'Unkonw',
// remark: '???' } ]

导出表格

步骤:

  • 构建特定的数据结构,如下。
  • 调用 XLSX.writeFile(workbook, filename) 即可。
// workbook
{
 SheetNames: ['mySheet'],
 Sheets: {
  'mySheet': {
   '!ref': 'A1:E4', // 必须要有这个范围才能输出,否则导出的 excel 会是一个空表
   A1: { v: 'id' },
   ...
  }
 }
}
var _headers = ['id', 'name', 'age', 'country', 'remark']
var _data = [ { id: '1',
    name: 'test1',
    age: '30',
    country: 'China',
    remark: 'hello' },
    { id: '2',
    name: 'test2',
    age: '20',
    country: 'America',
    remark: 'world' },
    { id: '3',
    name: 'test3',
    age: '18',
    country: 'Unkonw',
    remark: '???' } ];

var headers = _headers
    // 为 _headers 添加对应的单元格位置
    // [ { v: 'id', position: 'A1' },
    // { v: 'name', position: 'B1' },
    // { v: 'age', position: 'C1' },
    // { v: 'country', position: 'D1' },
    // { v: 'remark', position: 'E1' } ]
    .map((v, i) => Object.assign({}, {v: v, position: String.fromCharCode(65+i) + 1 }))
    // 转换成 worksheet 需要的结构
    // { A1: { v: 'id' },
    // B1: { v: 'name' },
    // C1: { v: 'age' },
    // D1: { v: 'country' },
    // E1: { v: 'remark' } }
    .reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});

var data = _data
    // 匹配 headers 的位置,生成对应的单元格数据
    // [ [ { v: '1', position: 'A2' },
    //  { v: 'test1', position: 'B2' },
    //  { v: '30', position: 'C2' },
    //  { v: 'China', position: 'D2' },
    //  { v: 'hello', position: 'E2' } ],
    // [ { v: '2', position: 'A3' },
    //  { v: 'test2', position: 'B3' },
    //  { v: '20', position: 'C3' },
    //  { v: 'America', position: 'D3' },
    //  { v: 'world', position: 'E3' } ],
    // [ { v: '3', position: 'A4' },
    //  { v: 'test3', position: 'B4' },
    //  { v: '18', position: 'C4' },
    //  { v: 'Unkonw', position: 'D4' },
    //  { v: '???', position: 'E4' } ] ]
    .map((v, i) => _headers.map((k, j) => Object.assign({}, { v: v[k], position: String.fromCharCode(65+j) + (i+2) })))
    // 对刚才的结果进行降维处理(二维数组变成一维数组)
    // [ { v: '1', position: 'A2' },
    // { v: 'test1', position: 'B2' },
    // { v: '30', position: 'C2' },
    // { v: 'China', position: 'D2' },
    // { v: 'hello', position: 'E2' },
    // { v: '2', position: 'A3' },
    // { v: 'test2', position: 'B3' },
    // { v: '20', position: 'C3' },
    // { v: 'America', position: 'D3' },
    // { v: 'world', position: 'E3' },
    // { v: '3', position: 'A4' },
    // { v: 'test3', position: 'B4' },
    // { v: '18', position: 'C4' },
    // { v: 'Unkonw', position: 'D4' },
    // { v: '???', position: 'E4' } ]
    .reduce((prev, next) => prev.concat(next))
    // 转换成 worksheet 需要的结构
    // { A2: { v: '1' },
    //  B2: { v: 'test1' },
    //  C2: { v: '30' },
    //  D2: { v: 'China' },
    //  E2: { v: 'hello' },
    //  A3: { v: '2' },
    //  B3: { v: 'test2' },
    //  C3: { v: '20' },
    //  D3: { v: 'America' },
    //  E3: { v: 'world' },
    //  A4: { v: '3' },
    //  B4: { v: 'test3' },
    //  C4: { v: '18' },
    //  D4: { v: 'Unkonw' },
    //  E4: { v: '???' } }
    .reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});

// 合并 headers 和 data
var output = Object.assign({}, headers, 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');

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

出处

http://scarletsky.github.io/2016/01/30/nodejs-process-excel/

参考资料

https://github.com/SheetJS/js-xlsx

http://stackoverflow.com/questions/30859901/parse-xlsx-with-node-and-create-json

Javascript 相关文章推荐
dwz 如何去掉ajaxloading具体代码
May 22 Javascript
让网页跳转到指定位置的jquery代码非书签
Sep 06 Javascript
JS动态增加删除UL节点LI及相关内容示例
May 21 Javascript
Javascript中Array用法实例分析
Jun 13 Javascript
JavaScript实现的圆形浮动标签云效果实例
Aug 06 Javascript
JS实现浏览器状态栏文字闪烁效果的方法
Oct 27 Javascript
AngularJs 弹出模态框(model)
Apr 07 Javascript
原生js实现ajax方法(超简单)
Sep 20 Javascript
JavaScript给每一个li节点绑定点击事件的实现方法
Dec 01 Javascript
Vue.js实现分页查询功能
Nov 15 Javascript
使用jQuery mobile NuGet让你的网站在移动设备上同样精彩
Jun 18 jQuery
在antd4.0中Form使用initialValue操作
Nov 02 Javascript
jQuery使用ajax_动力节点Java学院整理
Jul 05 #jQuery
jQuery扩展_动力节点Java学院整理
Jul 05 #jQuery
jQuery选择器_动力节点Java学院整理
Jul 05 #jQuery
jQuery事件_动力节点Java学院整理
Jul 05 #jQuery
jQuery修改DOM结构_动力节点Java学院整理
Jul 05 #jQuery
详解vue express启动数据服务
Jul 05 #Javascript
解决ztree搜索中多级菜单展示不全问题
Jul 05 #Javascript
You might like
PHP中常用的输出函数总结
2014/09/22 PHP
PHP5.6.8连接SQL Server 2008 R2数据库常用技巧分析总结
2019/05/06 PHP
JavaScript实现页面滚动图片加载(仿lazyload效果)
2011/07/22 Javascript
JS跨域总结
2012/08/30 Javascript
js实现的点击div区域外隐藏div区域
2014/06/30 Javascript
AngularJS中的模块详解
2015/01/29 Javascript
jquery带动画效果幻灯片特效代码
2015/08/27 Javascript
js不间断滚动的简单实现
2016/06/03 Javascript
AngularJS Ajax详解及示例代码
2016/08/17 Javascript
Vue+axios 实现http拦截及路由拦截实例
2017/04/25 Javascript
结合Vue控制字符和字节的显示个数的示例
2018/05/17 Javascript
Cocos2d实现刮刮卡效果
2018/12/20 Javascript
angular4中引入echarts的方法示例
2019/01/29 Javascript
[01:04]DOTA2:伟大的Roshan雕塑震撼来临
2015/01/30 DOTA
Python3实现的腾讯微博自动发帖小工具
2013/11/11 Python
简要讲解Python编程中线程的创建与锁的使用
2016/02/28 Python
查看python下OpenCV版本的方法
2018/08/03 Python
使用python的pexpect模块,实现远程免密登录的示例
2019/02/14 Python
使用python3调用wxpy模块监控linux日志并定时发送消息给群组或好友
2019/06/05 Python
利用Python绘制Jazz网络图的例子
2019/11/21 Python
Python requests.post方法中data与json参数区别详解
2020/04/30 Python
python3+opencv 使用灰度直方图来判断图片的亮暗操作
2020/06/02 Python
海蓝之谜(LA MER)澳大利亚官方商城:全球高端奢华护肤品牌
2017/10/27 全球购物
法国一家芭蕾舞鞋公司:Repetto
2018/11/12 全球购物
如何用Lucene索引数据库
2016/02/23 面试题
关键字final的用法
2013/10/02 面试题
JavaScript实现前端网页版倒计时
2021/03/24 Javascript
获奖的大学生创业计划书
2014/01/05 职场文书
村委会贫困证明
2014/01/14 职场文书
《骆驼和羊》教学反思
2014/02/27 职场文书
群众路线剖析材料范文
2014/10/09 职场文书
党的群众路线教育实践活动个人对照检查材料(企业)
2014/11/05 职场文书
员工手册董事长致辞
2015/07/29 职场文书
新郎父母婚礼答谢词
2015/09/29 职场文书
九不准学习心得体会
2016/01/23 职场文书
jquery插件实现悬浮的菜单
2021/04/24 jQuery