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 相关文章推荐
学习ExtJS Window常用方法
Oct 07 Javascript
jquery.boxy弹出框(后隔N秒后自动隐藏/自动跳转)
Jan 15 Javascript
JQuery入门——事件切换之hover()方法应用介绍
Feb 05 Javascript
精通JavaScript的this关键字
May 28 Javascript
jQuery绑定事件-多种实现方式总结
May 09 Javascript
jQuery展示表格点击变色、全选、删除
Jan 05 Javascript
ECMAScript6 新特性范例大全
Mar 24 Javascript
js利用递归与promise 按顺序请求数据的方法
Aug 30 Javascript
js实现单元格拖拽效果
Feb 10 Javascript
微信小程序使用GoEasy实现websocket实时通讯
May 19 Javascript
详解Vue之计算属性
Jun 20 Javascript
对vue生命周期的深入理解
Dec 03 Vue.js
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 5.0对象模型深度探索之类的静态成员
2008/03/27 PHP
php中file_get_contents与curl性能比较分析
2014/11/08 PHP
PHP中__FILE__、dirname与basename用法实例分析
2014/12/01 PHP
codeigniter显示所有脚本执行时间的方法
2015/03/21 PHP
两种php给图片加水印的实现代码
2020/04/18 PHP
Yii实现复选框批量操作实例代码
2017/03/15 PHP
半角全角相互转换的js函数
2009/10/16 Javascript
Javascript实现CheckBox的全选与取消全选的代码
2010/07/20 Javascript
分页栏的web标准实现
2011/11/01 Javascript
关于jQuery参考实例2.0 用jQuery选择元素
2013/04/07 Javascript
jQuery父级以及同级元素查找介绍
2013/09/04 Javascript
nodejs URL模块操作URL相关方法介绍
2015/03/03 NodeJs
JavaScript获取伪元素(Pseudo-Element)属性的方法技巧
2015/03/13 Javascript
jQuery Validation PlugIn的使用方法详解
2015/12/18 Javascript
JS读写CSS样式的方法汇总
2016/08/16 Javascript
解决使用layui对select append元素无效或者未及时更新的问题
2019/09/18 Javascript
JS XMLHttpRequest原理与使用方法深入详解
2020/04/30 Javascript
[47:21]Liquid vs TNC Supermajor 胜者组 BO3 第一场 6.4
2018/06/05 DOTA
在MAC上搭建python数据分析开发环境
2016/01/26 Python
python运行时间的几种方法
2016/06/17 Python
python中如何使用正则表达式的非贪婪模式示例
2017/10/09 Python
Flask Web开发入门之文件上传(八)
2018/08/17 Python
python实现植物大战僵尸游戏实例代码
2019/06/10 Python
python同时遍历两个list用法说明
2020/05/02 Python
解决python 虚拟环境删除包无法加载的问题
2020/07/13 Python
python实现无边框进度条的实例代码
2020/12/30 Python
html5 兼容IE6结构的实现代码
2012/05/14 HTML / CSS
Ticketmaster德国票务网站:购买音乐会和体育等门票
2016/11/14 全球购物
网络工程师个人的自我评价范文
2013/10/01 职场文书
建筑项目策划书
2014/01/13 职场文书
招聘与培训专员岗位职责
2014/01/30 职场文书
环保倡议书100字
2014/05/15 职场文书
公司2014年度工作总结
2014/12/10 职场文书
辣妈辣妹观后感
2015/06/10 职场文书
SONY AN-LP1 短波有源天线放大器
2021/04/22 无线电
vue使用refs获取嵌套组件中的值过程
2022/03/31 Vue.js