NODE 读写 EXCEL 文件探究实践

avatar
全栈开发工程师 @京东
原文链接: aotu.io

Node读写Excel文件探究实践

本文介绍用 Node.js 中的依赖库来处理 Excel 文件,主要阐述用js-xlsx、excel-export 库来处理 Excel 文件。

[1]

本文介绍用 Node.js 中的依赖库来处理 Excel 文件,深入分析对比常见npm库处理Excel 文件存在的优缺点,主要阐述用js-xlsx、excel-export 库来处理 Excel 文件。

  • 有哪些外部模块支持读写Excel
  • 引入依赖模块
  • 编写业务逻辑函数

支持读写Excel的node.js模块

通过npm搜索,支持读写excel文件的模块有很多,但是都各有忧缺点,有些仅支持xls/xlsx的一种格式,有些仅支持读取数据,有些仅支持导出文件,有些需要依赖python解析。常见的npm依赖模块如下:

  • js-xlsx[2]: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格XLSX / XLSM / XLSB / XLS / CSV,解析采用纯js实现,写入需要依赖nodejs或者FileSaver[3].js实现生成写入Excel,可以生成子表Excel,功能强大,但上手难度稍大。不提供基础设置Excel表格api例单元格宽度,文档有些乱,不适合快速上手;
  • node-xlsx[4]: 基于Node.js解析excel文件数据及生成excel文件,仅支持xlsx格式文件;
  • excel-parser[5]: 基于Node.js解析excel文件数据,支持xls及xlsx格式文件,需要依赖python,太重不太实用;
  • excel-export[6] : 基于Node.js将数据生成导出excel文件,生成文件格式为xlsx,可以设置单元格宽度,API容易上手,无法生成worksheet字表,比较单一,基本功能可以基本满足;
  • node-xlrd[7]: 基于node.js从excel文件中提取数据,仅支持xls格式文件,不支持xlsx,有点过时,常用的都是XLSX 格式。

通过以上分析对比,本人比较推崇js-xlsxexcel-export来读写Excel文件,可以结合使用js-xlsx解析Excel、excel-export生成,效果更加,接下来分别实践js-xlsxexcel-export

第一讲:利用 js-xlsx 处理 Excel 文件

node中使用通过npm:

$ npm install xlsx

浏览器使用:

<script ="javascript" ="dist/xlsx.core.min.js"></script>

通过bower安装:

bower install js-xlsx

注意,在客户端使用时,建议使用dist/xlsx.full.min.js,包含了js-xlsx所有模块。

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

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

它们的关系如下:

// workbook

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


        // worksheet
        'sheet2': {
            // cell
            : { ... },
            // cell
            : { ... },

基本用法

1.用 XLSX.read 读取获取到的 Excel 数据,返回 workbook
2.用 XLSX.readFile 打开 Excel 文件,返回 workbook
3.用 workbook.SheetNames 获取表名
4.用 workbook.Sheets[xxx] 通过表名获取表格
5.用 worksheet[address]操作单元格
6.用XLSX.utils.sheet_to_json针对单个表获取表格数据转换为json格式
7.用XLSX.writeFile(wb, 'output.xlsx')生成新的 Excel 文件

具体用法
读取 Excel 文件

workbook = .read(excelData, {: 'base64'});
workbook = .writeFile('someExcel.xlsx', opts);

获取 Excel 文件中的表

// 获取 Excel 中所有表名
 sheetNames = workbook.SheetNames; // 返回 ['sheet1', 'sheet2',……]
// 根据表名获取对应某张表
 worksheet = workbook.Sheets[sheetNames[]];
通过 worksheet[address] 来操作表格,以 ! 开头的 key 是特殊的字段。

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

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

// 获取合并过的单元格
worksheet['!merges'] // 返回一个包含 range 对象的列表,[ {s: { r: , c:  }, c: { r: , c:  } } ]
获取 Excel 文件中的表转换为json数据

XLSX.utils.sheet_to_json(worksheet)  //针对单个表,返回序列化json数据
生成新的 Excel 文件

//服务端通过XLSX.writeFile
XLSX = require("xlsx");
XLSX.writeFile(wb, 'output.xlsx')   

//客服端,只能通过XLSX.write(wb, write_opts) 写入 表格数据,借助FileSaver生成,且只支持在高版本浏览器。
 wopts = { bookType:'xlsx', bookSST:false, type:'binary' };

wbout = XLSX.write(wb,wopts);

function () {
   buf =  ArrayBuffer(s.length);
   view =  Uint8Array(buf);
   ( i=; i!=s.length; ++i) view[i] = s.charCodeAt(i) & ;
  return buf;


/* the saveAs call downloads a file on the local machine */
saveAs( Blob([s2ab(wbout)],{:}), "test.xlsx")

js-xlsx实战

解析 Excel 生成 JSON

function to_json(workbook) {
	 result = {};

// 获取 Excel 中所有表名
	 sheetNames = workbook.SheetNames; // 返回 ['sheet1', 'sheet2']

workbook.SheetNames.forEach(function(sheetName) {
		 worksheet = workbook.Sheets[sheetName];
		result[sheetName] = .utils.sheet_to_json(worksheet);


	console.log("打印表信息",.stringify(result, , ));  //显示格式{:[],:[]}
	return result;

导出表格
1.构建特定的数据结构,通过new Blob如下。

// workbook

SheetNames: ['mySheet'],
    Sheets: {
        'mySheet': {
            '!ref': 'A1:E4', // 必须要有这个范围才能输出,否则导出的 excel 会是一个空表
            : { :  },

2.调用 XLSX.write, 借助FileSaver中new Blob生成即可。

var _headers = [, 'name', 'age', 'country', 'remark']
var _data = [ { id: ,
                name: 'test1',
                age: ,
                country: 'China',
                remark: 'hello' },
              { id: ,
                name: 'test2',
                age: ,
                country: 'America',
                remark: 'world' },
              { id: ,
                name: 'test3',
                age: ,
                country: 'Unkonw',
                remark: '???' } ];

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

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

// 合并 headers 和 data
var output = Object.assign({}, headers, data);
// 获取所有单元格的位置
var outputPos = Object.keys(output);
// 计算出范围
var ref = outputPos[] +  + outputPos[outputPos.length - ];

// 构建 workbook 对象
var wb = {
    SheetNames: ['mySheet'],
    Sheets: {
        'mySheet': Object.assign({}, output, { '!ref': ref })

// 导出 Excel
//XLSX.writeFile(wb, 'output.xlsx');

var wopts = { bookType:'xlsx', bookSST:false, type:'binary' };

var wbout = XLSX.write(wb,wopts);

function s2ab(s) {
  var buf = new ArrayBuffer(s.length);
  var view = new Uint8Array(buf);
  for (var i=; i!=s.length; ++i) view[i] = s.charCodeAt(i) & ;
  return buf;


/* the saveAs call downloads a file on the local machine */
saveAs(new Blob([s2ab(wbout)],{type:}), "test.xlsx")

实践Demo:RD快速生成excel表[8]

第二讲:利用 excel-export 生成 Excel 文件

excel-export模块,上手起来就比较容易了,其中原理是通过修改,修改header 信息、拼接字符串、修改字符集、输出字符串的形式实现的,在部分firefox低版本下载中文名会出现乱码情况。我们只需要按照API设置好数据参数,通过nodeExcel.execute调用执行,系统调用模版”styles.xml”就可以生成Excel文件,比较好的就是,它可以设置单元格的宽度,类型。
我们先看看,官方提供的例子:

 express = require('express');
 nodeExcel = require('excel-export');
 app = express();

app.get('/Excel', function(req, res){
     conf ={};
    conf.stylesXmlFile = "styles.xml";
    conf.name = "mysheet";
    conf.cols = [{
        caption:'string',
        type:'string',
        beforeCellWrite:function(row, cellData){
             return cellData.toUpperCase();

width:28.7109375

caption:'date',
        type:'date',
        beforeCellWrite:function(){
             originDate =  (.UTC(,,));
            return function(row, cellData, eOpt){
                 (eOpt.rowNum%){
                    eOpt.styleIndex = ;


                    eOpt.styleIndex = ;

(cellData === ){
                  eOpt.cellType = 'string';
                  return 'N/A';

return (cellData - originDate) / ( *  *  * );

caption:'bool',
        type:'bool'

caption:'number',
         type:'number'              

conf.rows = [
        [,  (.UTC(, , )), , ],
        [,  (, , ), false, 2.7182],
        ["M&M<>'",  (.UTC(, , )), false, 1.61803],
        ["null date", , , 1.414]  

result = nodeExcel.execute(conf);
    res.setHeader('Content-Type', 'application/vnd.openxmlformats');
    res.setHeader("Content-Disposition", "attachment; filename=" + "Report.xlsx");
    res.end(result, 'binary');


app.listen();
console.log('Listening on port 3000');

分析生成excel流程:
1.配置excel文件名conf.name
2.设置表caption,每列单元格数据类型,宽度
3.填充表中每行数据conf.rows,nodeExcel.execute生成数据结构,设置头部,拼接生成表

写在最后,以上仅为个人观点,如有纰漏之处,欢迎各位大侠拍砖!