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 相关文章推荐
网站页面自动跳转实现方法PHP、JSP(上)
Aug 01 Javascript
Javascript调用C#代码
Jan 17 Javascript
js使用ajax读博客rss示例
May 06 Javascript
apply和call方法定义及apply和call方法的区别
Nov 15 Javascript
第一次接触Bootstrap框架
Oct 24 Javascript
Vue input控件通过value绑定动态属性及修饰符的方法
May 03 Javascript
Vue Cli3 创建项目的方法步骤
Oct 15 Javascript
react中Suspense的使用详解
Sep 01 Javascript
原生js实现的金山打字小游戏(实例代码详解)
Mar 16 Javascript
vuex中遇到的坑,vuex数据改变,组件中页面不渲染操作
Nov 16 Javascript
基于jQuery拖拽事件的封装
Nov 29 jQuery
JavaScript如何实现防止重复的网络请求的示例
Jan 28 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默认安装产生系统漏洞
2006/10/09 PHP
PHP批量查询WordPress留言者E-mail地址实现方法
2015/02/15 PHP
PHP Opcache安装和配置方法介绍
2015/05/28 PHP
form表单传递数组数据、php脚本接收的实例
2017/02/09 PHP
php表单文件iframe异步上传实例讲解
2017/07/26 PHP
php求斐波那契数的两种实现方式【递归与递推】
2019/09/09 PHP
IE6背景图片不缓存问题解决方案及图片使用策略多个方法小结
2012/05/14 Javascript
Javascript中查找不以XX字符结尾的单词示例代码
2013/10/15 Javascript
对比分析AngularJS中的$http.post与jQuery.post的区别
2015/02/27 Javascript
JavaScript如何实现对数字保留两位小数一位自动补零
2015/12/18 Javascript
JavaScript在form表单中使用button按钮实现submit提交方法
2017/01/23 Javascript
原生js实现瀑布流布局
2017/03/08 Javascript
vue自定义底部导航栏Tabbar的实现代码
2018/09/03 Javascript
node和vue实现商城用户地址模块
2018/12/05 Javascript
JS实现点击发送验证码 xx秒后重新发送功能
2019/07/30 Javascript
vue实现倒计时获取验证码效果
2020/04/17 Javascript
vue.js中使用微信扫一扫解决invalid signature问题(完美解决)
2020/04/11 Javascript
js函数柯里化的方法和作用实例分析
2020/04/11 Javascript
python 读取.csv文件数据到数组(矩阵)的实例讲解
2018/06/14 Python
tensorflow实现加载mnist数据集
2018/09/08 Python
Python3爬虫全国地址信息
2019/01/05 Python
Python 3.8中实现functools.cached_property功能
2019/05/29 Python
Python学习笔记之读取文件、OS模块、异常处理、with as语法示例
2019/06/04 Python
Pytorch修改ResNet模型全连接层进行直接训练实例
2019/09/10 Python
Python面向对象程序设计之继承、多态原理与用法详解
2020/03/23 Python
Tensorflow加载Vgg预训练模型操作
2020/05/26 Python
Python如何在windows环境安装pip及rarfile
2020/06/15 Python
使用Python将语音转换为文本的方法
2020/08/10 Python
目标责任书范本
2014/04/16 职场文书
期末学生评语大全
2014/04/24 职场文书
模特大赛策划方案
2014/05/28 职场文书
应届大专生自荐书
2014/06/16 职场文书
php中pcntl_fork详解
2021/04/01 PHP
redis配置文件中常用配置详解
2021/04/14 Redis
在Docker容器中部署SQL Server
2022/04/11 Servers
LyScript实现绕过反调试保护的示例详解
2022/08/14 Python