read-excel-file
Version:
Read `*.xlsx` files in a browser or Node.js. Parse to JSON with a strict schema.
383 lines (330 loc) • 11.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports.default = function (data, schema, options) {
if (options) {
options = _extends({}, DEFAULT_OPTIONS, options);
} else {
options = DEFAULT_OPTIONS;
}
var _options = options,
isColumnOriented = _options.isColumnOriented,
rowMap = _options.rowMap;
validateSchema(schema);
if (isColumnOriented) {
data = transpose(data);
}
var columns = data[0];
var results = [];
var errors = [];
for (var i = 1; i < data.length; i++) {
var result = read(schema, data[i], i - 1, columns, errors, options);
if (result) {
results.push(result);
}
}
// Correct error rows.
if (rowMap) {
for (var _iterator = errors, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var error = _ref;
error.row = rowMap[error.row] + 1;
}
}
return {
rows: results,
errors: errors
};
};
exports.parseValue = parseValue;
exports.getBlock = getBlock;
exports.parseArray = parseArray;
var _parseDate = require('./parseDate');
var _parseDate2 = _interopRequireDefault(_parseDate);
var _Integer = require('./types/Integer');
var _Integer2 = _interopRequireDefault(_Integer);
var _URL = require('./types/URL');
var _URL2 = _interopRequireDefault(_URL);
var _Email = require('./types/Email');
var _Email2 = _interopRequireDefault(_Email);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_OPTIONS = {
isColumnOriented: false
/**
* Convert 2D array to nested objects.
* If row oriented data, row 0 is dotted key names.
* Column oriented data is transposed.
* @param {string[][]} data - An array of rows, each row being an array of cells.
* @param {object} schema
* @return {object[]}
*/
};
function read(schema, row, rowIndex, columns, errors, options) {
var object = {};
var _loop = function _loop() {
if (_isArray2) {
if (_i2 >= _iterator2.length) return 'break';
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) return 'break';
_ref2 = _i2.value;
}
var key = _ref2;
var schemaEntry = schema[key];
var isNestedSchema = _typeof(schemaEntry.type) === 'object' && !Array.isArray(schemaEntry.type);
var rawValue = row[columns.indexOf(key)];
if (rawValue === undefined) {
rawValue = null;
}
var value = void 0;
var error = void 0;
if (isNestedSchema) {
value = read(schemaEntry.type, row, rowIndex, columns, errors, options);
} else {
if (rawValue === null) {
value = null;
} else if (Array.isArray(schemaEntry.type)) {
var notEmpty = false;
var array = parseArray(rawValue).map(function (_value) {
var result = parseValue(_value, schemaEntry, options);
if (result.error) {
value = _value;
error = result.error;
}
if (result.value !== null) {
notEmpty = true;
}
return result.value;
});
if (!error) {
value = notEmpty ? array : null;
}
} else {
var result = parseValue(rawValue, schemaEntry, options);
error = result.error;
value = error ? rawValue : result.value;
}
}
if (!error && value === null && schemaEntry.required) {
error = 'required';
}
if (error) {
error = {
error: error,
row: rowIndex + 1,
column: key,
value: value
};
if (schemaEntry.type) {
error.type = schemaEntry.type;
}
errors.push(error);
} else if (value !== null) {
object[schemaEntry.prop] = value;
}
};
for (var _iterator2 = Object.keys(schema), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
var _ref2;
var _ret = _loop();
if (_ret === 'break') break;
}
if (Object.keys(object).length > 0) {
return object;
}
return null;
}
/**
* Converts textual value to a javascript typed value.
* @param {string} value
* @param {object} schemaEntry
* @return {{ value: any, error: string }}
*/
function parseValue(value, schemaEntry, options) {
if (value === null) {
return { value: null };
}
var result = void 0;
if (schemaEntry.parse) {
result = parseCustomValue(value, schemaEntry.parse);
} else if (schemaEntry.type) {
result = parseValueOfType(value, Array.isArray(schemaEntry.type) ? schemaEntry.type[0] : schemaEntry.type, options);
} else {
throw new Error('Invalid schema entry: no .type and no .parse():\n\n' + JSON.stringify(schemaEntry, null, 2));
}
// If errored then return the error.
if (result.error) {
return result;
}
if (result.value !== null) {
try {
if (schemaEntry.oneOf && schemaEntry.oneOf.indexOf(result.value) < 0) {
return { error: 'invalid' };
}
if (schemaEntry.validate) {
schemaEntry.validate(result.value);
}
} catch (error) {
return { error: error.message };
}
}
return result;
}
/**
* Converts textual value to a custom value using supplied `.parse()`.
* @param {string} value
* @param {function} parse
* @return {{ value: any, error: string }}
*/
function parseCustomValue(value, parse) {
try {
var parsed = parse(value);
if (parsed === undefined) {
return { value: null };
}
return { value: parsed };
} catch (error) {
return { error: error.message };
}
}
/**
* Converts textual value to a javascript typed value.
* @param {string} value
* @param {} type
* @return {{ value: (string|number|Date|boolean), error: string }}
*/
function parseValueOfType(value, type, options) {
switch (type) {
case String:
return { value: value };
case Number:
case 'Integer':
case _Integer2.default:
// The global isFinite() function determines
// whether the passed value is a finite number.
// If needed, the parameter is first converted to a number.
if (!isFinite(value)) {
return { error: 'invalid' };
}
if (type === _Integer2.default && !(0, _Integer.isInteger)(value)) {
return { error: 'invalid' };
}
// Convert strings to numbers.
// Just an additional feature.
// Won't happen when called from `readXlsx()`.
if (typeof value === 'string') {
value = parseFloat(value);
}
return { value: value };
case 'URL':
case _URL2.default:
if (!(0, _URL.isURL)(value)) {
return { error: 'invalid' };
}
return { value: value };
case 'Email':
case _Email2.default:
if (!(0, _Email.isEmail)(value)) {
return { error: 'invalid' };
}
return { value: value };
case Date:
// XLSX has no specific format for dates.
// Sometimes a date can be heuristically detected.
// https://github.com/catamphetamine/read-excel-file/issues/3#issuecomment-395770777
if (value instanceof Date) {
return { value: value };
}
if (typeof value === 'number') {
if (!isFinite(value)) {
return { error: 'invalid' };
}
value = parseInt(value);
var date = (0, _parseDate2.default)(value, options.properties);
if (!date) {
return { error: 'invalid' };
}
return { value: date };
}
return { error: 'invalid' };
case Boolean:
if (typeof value === 'boolean') {
return { value: value };
}
return { error: 'invalid' };
default:
throw new Error('Unknown schema type: ' + (type && type.name || type));
}
}
function getBlock(string, endCharacter, startIndex) {
var i = 0;
var substring = '';
var character = void 0;
while (startIndex + i < string.length) {
var _character = string[startIndex + i];
if (_character === endCharacter) {
return [substring, i];
} else if (_character === '"') {
var block = getBlock(string, '"', startIndex + i + 1);
substring += block[0];
i += '"'.length + block[1] + '"'.length;
} else {
substring += _character;
i++;
}
}
return [substring, i];
}
function parseArray(string) {
var blocks = [];
var index = 0;
while (index < string.length) {
var _getBlock = getBlock(string, ',', index),
_getBlock2 = _slicedToArray(_getBlock, 2),
substring = _getBlock2[0],
length = _getBlock2[1];
index += length + ','.length;
blocks.push(substring.trim());
}
return blocks;
}
// Transpose a 2D array.
// https://stackoverflow.com/questions/17428587/transposing-a-2d-array-in-javascript
var transpose = function transpose(array) {
return array[0].map(function (_, i) {
return array.map(function (row) {
return row[i];
});
});
};
function validateSchema(schema) {
for (var _iterator3 = Object.keys(schema), _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var _key = _ref3;
var entry = schema[_key];
if (!entry.prop) {
throw new Error('"prop" not defined for schema entry "' + _key + '".');
}
}
}
//# sourceMappingURL=convertToJson.js.map
;