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 相关文章推荐
JavaScript Base64编码和解码,实现URL参数传递。
Sep 18 Javascript
jQuery Tools tab使用介绍
Jul 14 Javascript
jQuery之ajax技术的详细介绍
Jun 19 Javascript
jQuery插件jRumble实现网页元素抖动
Jun 05 Javascript
纯javascript实现图片延时加载方法
Aug 21 Javascript
JavaScript中Number对象的toFixed() 方法详解
Sep 02 Javascript
简单谈谈Javascript函数中的arguments
Feb 09 Javascript
Async Validator 异步验证使用说明
Jul 03 Javascript
Vue2.0基于vue-cli+webpack父子组件通信(实例讲解)
Sep 14 Javascript
JavaScript自执行函数和jQuery扩展方法详解
Oct 27 jQuery
vue自定义全局组件(自定义插件)的用法
Jan 30 Javascript
jquery选择器和属性对象的操作实例分析
Jan 10 jQuery
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 ob_start()控制浏览器cache、生成html实现代码
2010/02/16 PHP
php下使用SimpleXML 处理XML 文件
2010/02/27 PHP
使用ThinkPHP的自动完成实现无限级分类实例详解
2016/09/02 PHP
js自定义方法通过隐藏iframe实现文件下载
2013/02/21 Javascript
JavaScript中for..in循环陷阱介绍
2013/11/12 Javascript
JavaScript中关于iframe滚动条的去除和保留
2016/11/17 Javascript
jQuery 选择符详细介绍及整理
2016/12/02 Javascript
原生Aajax 和jQuery Ajax 写法个人总结
2017/03/24 jQuery
vue组件间通信子与父详解(二)
2017/11/07 Javascript
Vue官方文档梳理之全局配置
2017/11/22 Javascript
解决vue-cli项目打包出现空白页和路径错误的问题
2018/09/04 Javascript
修改vue源码实现动态路由缓存的方法
2020/01/21 Javascript
JS实现省市县三级下拉联动
2020/04/10 Javascript
[55:32]2018DOTA2亚洲邀请赛 4.4 淘汰赛 EG vs LGD 第二场
2018/04/05 DOTA
总结python爬虫抓站的实用技巧
2016/08/09 Python
利用python实现数据分析
2017/01/11 Python
深入浅出分析Python装饰器用法
2017/07/28 Python
Python基于PyGraphics包实现图片截取功能的方法
2017/12/21 Python
深入浅析Python获取对象信息的函数type()、isinstance()、dir()
2018/09/17 Python
Python格式化输出字符串方法小结【%与format】
2018/10/29 Python
利用python提取wav文件的mfcc方法
2019/01/09 Python
使用python来调用CAN通讯的DLL实现方法
2019/07/03 Python
python3 实现的对象与json相互转换操作示例
2019/08/17 Python
Python+OpenCV实现实时眼动追踪的示例代码
2019/11/11 Python
Pytorch通过保存为ONNX模型转TensorRT5的实现
2020/05/25 Python
最新PyCharm从安装到PyCharm永久激活再到PyCharm官方中文汉化详细教程
2020/11/17 Python
世界最大的票务市场:viagogo
2017/02/16 全球购物
Nili Lotan官网:Nili Lotan同名品牌
2018/01/07 全球购物
资深财务管理人员自我评价
2013/09/22 职场文书
秋季运动会加油稿200字
2014/01/11 职场文书
八年级生物教学反思
2014/01/22 职场文书
2014幼儿教师个人工作总结
2014/12/03 职场文书
环保建议书范文
2015/09/14 职场文书
Netty结合Protobuf进行编解码的方法
2021/06/26 Java/Android
Python面向对象编程之类的概念
2021/11/01 Python
SQL优化老出错,那是你没弄明白MySQL解释计划用法
2021/11/27 MySQL