excel2json
Version:
excel2json
360 lines (359 loc) • 12.4 kB
JavaScript
;
/**
* @fileOverview excel2json main
* @name index
* @author Yuhei Aihara
* https://github.com/iyu/excel2json
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = __importDefault(require("lodash"));
const parser_1 = __importDefault(require("./excel/parser"));
const logger_1 = __importDefault(require("./logger"));
class Excel2Json {
constructor() {
this.opts = {
option_cell: 'A1',
attr_line: 2,
data_line: 4,
ref_key: '_id',
};
this.logger = logger_1.default;
this._parser = {
number: (d) => {
if (d.length >= 18) {
// IEEE754
return Number(Number(d).toFixed(8));
}
return Number(d);
},
num: (d) => {
return this._parser.number(d);
},
boolean: (d) => {
return !!d && d.toLowerCase() !== 'false' && d !== '0';
},
bool: (d) => {
return this._parser.boolean(d);
},
date: (d) => {
return Math.round((((Number(d) - 25569) * 24)
+ (new Date().getTimezoneOffset() / 60)) * 3600000);
},
auto: (d) => {
return Number.isFinite(Number(d)) ? d : this._parser.number(d);
},
};
}
/**
* setup
* @param {Object} options
* @example
* var options = {
* option_cell: 'A1',
* attr_line: 2,
* data_line: 4,
* ref_key: '_id',
* logger: CustomLogger
* };
*/
setup(options) {
lodash_1.default.assign(this.opts, options);
if (this.opts.logger) {
this.logger = this.opts.logger;
}
this.logger.info('excel2json setup');
return this;
}
/**
* format
* @param {Array} cells
* @private
* @example
* var cells = [
* { cell: 'A1', value: '{}' }, { cell: 'A4', value: '_id' },,,
* ]
*/
_format(cells) {
let beforeRow;
let idx = {};
const list = [];
const opts = {
attr_line: this.opts.attr_line,
data_line: this.opts.data_line,
ref_key: this.opts.ref_key,
format: undefined,
};
lodash_1.default.forEach(cells, (cell) => {
if (beforeRow !== cell.row) {
lodash_1.default.forEach(idx, (i) => {
if (i.type !== 'format') {
i.value += 1;
}
});
beforeRow = cell.row;
}
if (cell.cell === this.opts.option_cell) {
const _opts = JSON.parse(cell.value);
lodash_1.default.assign(opts, _opts);
return;
}
if (cell.row === opts.attr_line) {
const type = cell.value.match(/:(\w+)$/);
const key = cell.value.replace(/:\w+$/, '');
const keys = key.split('.');
opts.format = opts.format || {};
opts.format[cell.column] = {
type: type && type[1],
key,
keys,
};
return;
}
const format = opts.format && opts.format[cell.column];
let data;
let _idx;
if (cell.row < opts.data_line || !format) {
return;
}
if (format.type && format.type.toLowerCase() === 'index') {
_idx = parseInt(cell.value, 10);
if (!idx[format.key] || idx[format.key].value !== _idx) {
idx[format.key] = {
type: 'format',
value: _idx,
};
lodash_1.default.forEach(idx, (i, key) => {
if (new RegExp(`^${format.key}.+$`).test(key)) {
idx[key].value = 0;
}
});
}
return;
}
if (format.key === opts.ref_key || format.key === '__ref') {
idx = {};
list.push({});
}
data = lodash_1.default.last(list);
lodash_1.default.forEach(format.keys, (_key, i) => {
const isArray = /^#/.test(_key);
const isSplitArray = /^\$/.test(_key);
let __key;
if (isArray) {
_key = _key.replace(/^#/, '');
data[_key] = data[_key] || [];
}
if (isSplitArray) {
_key = _key.replace(/^\$/, '');
}
if (i + 1 !== format.keys.length) {
if (isArray) {
__key = format.keys.slice(0, i + 1).join('.');
_idx = idx[__key];
if (!_idx) {
idx[__key] = {
type: 'normal',
value: data[_key].length ? data[_key].length - 1 : 0,
};
_idx = idx[__key];
}
data[_key][_idx.value] = data[_key][_idx.value] || {};
data = data[_key][_idx.value];
return;
}
data[_key] = data[_key] || {};
data = data[_key];
return;
}
if (isArray) {
__key = format.keys.slice(0, i + 1).join('.');
_idx = idx[__key];
if (!_idx) {
idx[__key] = {
type: 'normal',
value: data[_key].length ? data[_key].length - 1 : 0,
};
_idx = idx[__key];
}
data = data[_key];
_key = _idx.value;
}
if (data[_key]) {
return;
}
const type = format.type && format.type.toLowerCase();
if (type && this._parser[type]) {
data[_key] = isSplitArray ? cell.value.split(',').map(this._parser[type]) : this._parser[type](cell.value);
}
else {
data[_key] = isSplitArray ? cell.value.split(',') : cell.value;
}
});
});
return {
opts,
list,
};
}
/**
* find origin data
* @param dataMap
* @param opts
* @param data
* @private
*/
_findOrigin(dataMap, opts, data) {
let origin = dataMap[data.__ref];
if (!origin || !opts.key) {
this.logger.error('not found origin.', JSON.stringify(data));
return;
}
const keys = opts.key.split('.');
const __in = data.__in ? data.__in.split('.') : [];
for (let i = 0; i < keys.length; i++) {
if (/^#/.test(keys[i])) {
const key = keys[i].replace(/^#/, '');
const index = __in[i] && __in[i].replace(/^#.+:(\d+)$/, '$1');
if (!index) {
this.logger.error('not found index.', JSON.stringify(data));
return;
}
origin[key] = origin[key] || [];
origin = origin[key];
origin[index] = origin[index] || {};
origin = origin[index];
}
else if (keys[i] === '$') {
origin = origin[__in[i]];
}
else if (i + 1 === keys.length) {
origin[keys[i]] = origin[keys[i]] || (opts.type === 'array' ? [] : {});
origin = origin[keys[i]];
}
else {
origin[keys[i]] = origin[keys[i]] || {};
origin = origin[keys[i]];
}
if (!origin) {
this.logger.error('not found origin parts.', JSON.stringify(data));
return;
}
}
if (opts.type === 'array') {
if (!Array.isArray(origin)) {
this.logger.error('is not Array.', JSON.stringify(data));
return;
}
origin.push({});
origin = origin[origin.length - 1];
}
else if (opts.type === 'map') {
if (!data.__key) {
this.logger.error('not found __key.', JSON.stringify(data));
return;
}
origin[data.__key] = {};
origin = origin[data.__key];
}
return origin;
}
/**
* excel parser main script
* @param {String} filepath
* @param {Array} sheets
* @param {Function} callback
*/
async parse(filepath, sheets, callback) {
let excelData;
try {
excelData = await parser_1.default.execute(filepath, sheets);
}
catch (e) {
return callback(e);
}
let errList;
const promises = lodash_1.default.map(excelData, (sheetData) => {
let result;
try {
result = this._format(sheetData.cells);
}
catch (e) {
this.logger.error('invalid sheet format.', sheetData.num, sheetData.name);
errList = errList || [];
errList.push({
num: sheetData.num,
name: sheetData.name,
error: e,
});
return Promise.resolve();
}
return new Promise((resolve) => {
setImmediate(() => {
resolve({
num: sheetData.num,
name: sheetData.name,
opts: result.opts,
list: result.list,
});
});
});
});
let result;
try {
result = await Promise.all(promises);
}
catch (e) {
return callback(e);
}
callback(null, lodash_1.default.compact(result), errList);
}
/**
* sheetDatas to json
* @param {Array} sheetDatas
* @param {Function} callback
*/
toJson(sheetDatas, callback) {
const collectionMap = {};
const optionMap = {};
const errors = {};
for (let i = 0; i < sheetDatas.length; i++) {
const sheetData = sheetDatas[i];
const { opts } = sheetData;
const name = opts.name || sheetData.name;
const refKey = opts.ref_key;
collectionMap[name] = collectionMap[name] || {};
const dataMap = collectionMap[name];
if (!optionMap[name]) {
optionMap[name] = opts;
}
else {
optionMap[name] = lodash_1.default.assign({}, opts, optionMap[name]);
lodash_1.default.assign(optionMap[name].format, opts.format);
}
for (let j = 0; j < sheetData.list.length; j++) {
const data = sheetData.list[j];
if (!opts.type || opts.type === 'origin') {
dataMap[data[refKey]] = data;
}
else {
const origin = this._findOrigin(dataMap, opts, data);
if (origin) {
delete data.__ref;
delete data.__in;
delete data.__key;
lodash_1.default.assign(origin, data);
}
else {
errors[name] = errors[name] || [];
errors[name].push(data);
}
}
}
}
callback(lodash_1.default.isEmpty(errors) ? null : errors, collectionMap, optionMap);
}
}
exports.default = new Excel2Json();