read-excel-file 8.0.3 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports["default"] = parseData;
7
7
  exports.getNextSubstring = getNextSubstring;
8
+ exports.parseDataWithPerRowErrors = parseDataWithPerRowErrors;
8
9
  exports.parseSeparatedSubstrings = parseSeparatedSubstrings;
9
10
  exports.parseValue = parseValue;
10
11
  var _Number = _interopRequireDefault(require("./types/Number.js"));
@@ -12,20 +13,21 @@ var _String = _interopRequireDefault(require("./types/String.js"));
12
13
  var _Boolean = _interopRequireDefault(require("./types/Boolean.js"));
13
14
  var _Date = _interopRequireDefault(require("./types/Date.js"));
14
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
15
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
16
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
17
- function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
18
- function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
19
- function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
20
16
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
21
17
  function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
22
18
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
23
19
  function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); }
24
20
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
25
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
26
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
27
21
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
28
22
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
23
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
24
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
25
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
26
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
27
+ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
28
+ function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
29
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
30
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
29
31
  /**
30
32
  * Converts spreadsheet-alike data structure into an array of JSON objects.
31
33
  *
@@ -39,7 +41,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
39
41
  * // * `shouldSkipRequiredValidationWhenColumnIsMissing: (column: string, { object }) => boolean` — By default, it does apply `required` validation to `schema` properties for which columns are missing in the input `data`. One could pass a custom `shouldSkipRequiredValidationWhenColumnIsMissing(column, { object })` to disable `required` validation for missing columns in some or all cases.
40
42
  * * `transformEmptyObject(object, { path? })` — By default, it returns `null` for "empty" objects. One could override that value using `transformEmptyObject(object, { path })` parameter. The value applies to both top-level object and any nested sub-objects in case of a nested schema, hence the additional (optional) `path?: string` parameter.
41
43
  * * `transformEmptyArray(array, { path })` — By default, it returns `null` for an "empty" array value. One could override that value using `transformEmptyArray(array, { path })` parameter.
42
- * * `arrayValueSeparator` — By default, it splits array-type cell values by a comma character.
44
+ * * `separatorCharacter` — By default, it splits array-type cell values by a comma character.
43
45
  *
44
46
  * When parsing a property value, in case of an error, the value of that property is gonna be `undefined`.
45
47
  *
@@ -51,301 +53,403 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
51
53
  * // @param {boolean} [options.shouldSkipRequiredValidationWhenColumnIsMissing(column: string, { object })] — By default, it does apply `required` validation to `schema` properties for which columns are missing in the input `data`. One could pass a custom `shouldSkipRequiredValidationWhenColumnIsMissing(column, { object })` to disable `required` validation for missing columns in some or all cases.
52
54
  * @param {function} [options.transformEmptyObject(object, { path })] — By default, it returns `null` for an "empty" resulting object. One could override that value using `transformEmptyObject(object, { path })` parameter. The value applies to both top-level object and any nested sub-objects in case of a nested schema, hence the additional `path?: string` parameter.
53
55
  * @param {function} [options.transformEmptyArray(array, { path })] — By default, it returns `null` for an "empty" array value. One could override that value using `transformEmptyArray(array, { path })` parameter.
54
- * @param {string} [options.arrayValueSeparator] — When specified, string values will be split by this separator to get the array.
55
- * @return {object[]} — An array of objects of shape `{ object, errors }`. Either `object` or `errors` is going to be `undefined`.
56
+ * @param {string} [options.separatorCharacter] — When specified, string values will be split by this separator to get the array.
57
+ * @return {object} — An object of shape `{ objects, errors }`. Either `objects` or `errors` is going to be `undefined`.
56
58
  */
57
59
  function parseData(data, schema, optionsCustom) {
60
+ var objects = [];
61
+ var errors = [];
62
+ var parsedRows = parseDataWithPerRowErrors(data, schema, optionsCustom);
63
+ var parsedRowIndex = 0;
64
+ for (var _iterator = _createForOfIteratorHelperLoose(parsedRows), _step; !(_step = _iterator()).done;) {
65
+ var _step$value = _step.value,
66
+ object = _step$value.object,
67
+ rowErrors = _step$value.errors;
68
+ if (rowErrors) {
69
+ errors = errors.concat(rowErrors.map(function (rowError) {
70
+ return _objectSpread(_objectSpread({}, rowError), {}, {
71
+ row: parsedRowIndex + 1
72
+ });
73
+ }));
74
+ } else {
75
+ objects.push(object);
76
+ }
77
+ }
78
+ if (errors.length > 0) {
79
+ return {
80
+ errors: errors
81
+ };
82
+ }
83
+ return {
84
+ objects: objects
85
+ };
86
+ }
87
+
88
+ // This one is only used in tests.
89
+ function parseDataWithPerRowErrors(data, schema, optionsCustom) {
58
90
  validateSchema(schema);
59
91
  var options = applyDefaultOptions(optionsCustom);
60
92
  var _data = _toArray(data),
61
93
  columns = _data[0],
62
94
  dataRows = _data.slice(1);
63
- return dataRows.map(function (dataRow) {
64
- return parseDataRow(dataRow, schema, undefined, columns, options);
95
+ return dataRows.map(function (row) {
96
+ return parseDataRow(row, schema, columns, options);
65
97
  });
66
98
  }
67
- function parseDataRow(row, schema, path, columns, options) {
99
+ function parseDataRow(dataRow, schema, columns, options) {
100
+ // Create a `schemaEntry` for the top-level object.
101
+ var schemaEntry = {
102
+ schema: schema
103
+ };
104
+
105
+ // Parse the values in the given data row into an object.
106
+ var _parseProperty = parseProperty(dataRow, schemaEntry, undefined, columns, options),
107
+ value = _parseProperty.value,
108
+ isEmptyValue = _parseProperty.isEmptyValue,
109
+ errors = _parseProperty.errors,
110
+ children = _parseProperty.children;
111
+
112
+ // Simulate a "dummy" parent object for the top-level object.
113
+ // It will be used when running `required` validations.
114
+ var dummyParentObject = {
115
+ // The "dummy" parent object has a "dummy" value.
116
+ // This value is irrelevant because it won't be read anywhere.
117
+ value: PARSED_OBJECT_TREE_START,
118
+ // The "dummy" parent object is empty if the parsed row is empty.
119
+ isEmptyValue: isEmptyValue,
120
+ // The "dummy" object has the same errors as the parsed row.
121
+ errors: errors,
122
+ // The parsed object by default is not required to have any data
123
+ // so the "dummy" object is not required.
124
+ isRequired: undefined
125
+ };
126
+
127
+ // Run any `required` validations.
128
+ //
129
+ // `required` validations should be run after the entire data row has been parsed,
130
+ // i.e. when the entire object structure has been parsed.
131
+ // The reason is that a `required` validation could be either a simple boolean or a "complex" function.
132
+ // In the latter case, the result of a `required()` function may depend on any other property of the object,
133
+ // hence the actual `required` flag value could only be obtained after the entire data row has been parsed.
134
+ //
135
+ // For example, consider a top-level object:
136
+ //
137
+ // {
138
+ // firstName: string,
139
+ // lastName: string,
140
+ // pet?: { name: string }
141
+ // }
142
+ //
143
+ // A corresponding schema would be:
144
+ //
145
+ // {
146
+ // firstName: {
147
+ // required: true
148
+ // },
149
+ // lastName: {
150
+ // required: true
151
+ // },
152
+ // pet: {
153
+ // required: false,
154
+ // schema: {
155
+ // name: {
156
+ // required: true
157
+ // }
158
+ // }
159
+ // }
160
+ // }
161
+ //
162
+ // I.e. when a `pet` exists, it must have a `name`.
163
+ //
164
+ // In such case, the `required: true` check of the `pet`'s `name` property
165
+ // should not be performed if the `pet` is not present, because the `pet` nested object
166
+ // is marked as `required: false`, meaning that `pet` data is not required to be present.
167
+ //
168
+ var requiredErrors = runPendingRequiredValidations(schemaEntry, value, isEmptyValue, errors, children,
169
+ // Simulate a "dummy" parent object for the top-level object.
170
+ dummyParentObject.isRequired, dummyParentObject.value, dummyParentObject.isEmptyValue, dummyParentObject.errors);
171
+
172
+ // If there were any errors, whether caused by `required`
173
+ // or occured while parsing the values, return those errors.
174
+ if (errors || requiredErrors) {
175
+ return {
176
+ errors: (errors || []).concat(requiredErrors || [])
177
+ };
178
+ }
179
+
180
+ // Return the parsed object.
181
+ return {
182
+ object: transformValue(value, isEmptyValue, undefined, options)
183
+ };
184
+ }
185
+ function parseObject(row, schema, path, columns, options) {
68
186
  var object = {};
69
- var errors = [];
70
187
  var isEmptyObject = true;
71
- var pendingRequiredValidations = [];
188
+ var errors = [];
189
+ var children = [];
72
190
 
73
191
  // For each property of the object.
74
192
  for (var _i = 0, _Object$keys = Object.keys(schema); _i < _Object$keys.length; _i++) {
75
193
  var key = _Object$keys[_i];
76
- var _parseProperty = parseProperty(key, row, path, schema, columns, options),
77
- propertyErrors = _parseProperty.errors,
78
- pendingRequiredValidation = _parseProperty.pendingRequiredValidation,
79
- value = _parseProperty.value;
80
- if (propertyErrors) {
81
- errors = errors.concat(propertyErrors);
194
+ var child = parseProperty(row, schema[key], getPropertyPath(key, path), columns, options);
195
+ if (child.errors) {
196
+ errors = errors.concat(child.errors);
82
197
  } else {
83
- object[key] = value;
84
- // Will perform `required` validation later, when all properties have been parsed.
85
- if (pendingRequiredValidation) {
86
- pendingRequiredValidations.push(pendingRequiredValidation);
87
- }
198
+ object[key] = transformValue(child.value, child.isEmptyValue, getPropertyPath(key, path), options);
88
199
  // Potentially unmark the object as "empty".
89
- if (isEmptyObject && !isEmptyValue(value)) {
200
+ if (isEmptyObject && !child.isEmptyValue) {
90
201
  isEmptyObject = false;
91
202
  }
92
203
  }
93
- }
94
-
95
- // Perform basic `required` validations (i.e. when `required` property is a boolean).
96
- for (var _i2 = 0, _pendingRequiredValid = pendingRequiredValidations; _i2 < _pendingRequiredValid.length; _i2++) {
97
- var _pendingRequiredValid2 = _pendingRequiredValid[_i2],
98
- required = _pendingRequiredValid2.required,
99
- schemaEntry = _pendingRequiredValid2.schemaEntry,
100
- _value = _pendingRequiredValid2.value;
101
- if (required === true) {
102
- errors.push(createError({
103
- error: 'required',
104
- schemaEntry: schemaEntry,
105
- value: _value
106
- }));
107
- }
204
+ children.push(_objectSpread(_objectSpread({}, child), {}, {
205
+ // `schemaEntry` will be used when running `required` validation of this property (later),
206
+ schemaEntry: schema[key]
207
+ }));
108
208
  }
109
209
 
110
210
  // If there were any errors, return them.
111
211
  if (errors.length > 0) {
112
212
  return {
113
- errors: errors
114
- };
115
- }
116
-
117
- // Perform "complex" `required` validations (i.e. when `required` property is a function).
118
- // These "complex" `required` validations should only be performed when all properties
119
- // of an object have been parsed correctly because these validations rely on the values
120
- // of other properties.
121
- for (var _i3 = 0, _pendingRequiredValid3 = pendingRequiredValidations; _i3 < _pendingRequiredValid3.length; _i3++) {
122
- var _pendingRequiredValid4 = _pendingRequiredValid3[_i3],
123
- _required = _pendingRequiredValid4.required,
124
- _schemaEntry = _pendingRequiredValid4.schemaEntry,
125
- _value2 = _pendingRequiredValid4.value;
126
- if (typeof _required !== 'boolean' && _required(object)) {
127
- errors.push(createError({
128
- error: 'required',
129
- schemaEntry: _schemaEntry,
130
- value: _value2
131
- }));
132
- }
133
- }
134
-
135
- // If there were any "complex" `required` errors, return them.
136
- if (errors.length > 0) {
137
- return {
138
- errors: errors
139
- };
140
- }
141
-
142
- // Return `null` for an "empty" mapped object.
143
- if (isEmptyObject) {
144
- return {
145
- object: options.transformEmptyObject(object, {
146
- path: path
147
- })
213
+ // Return the errors.
214
+ errors: errors,
215
+ // Return the `children` because `required` validations still have to be run (later).
216
+ children: children
148
217
  };
149
218
  }
150
219
  return {
151
- object: object
220
+ value: object,
221
+ isEmptyValue: isEmptyObject,
222
+ // Return the `children` because `required` validations still have to be run (later).
223
+ children: children
152
224
  };
153
225
  }
154
- function parseProperty(key, row, path, schema, columns, options) {
155
- var schemaEntry = schema[key];
226
+ function parseProperty(row, schemaEntry, path, columns, options) {
156
227
  var columnIndex = schemaEntry.column ? columns.indexOf(schemaEntry.column) : undefined;
157
- var isMissingColumn = columnIndex < 0;
158
-
159
- // The path of this property inside the top-level object.
160
- var propertyPath = "".concat(path ? path + '.' : '').concat(key);
161
- var _ref = schemaEntry.schema ? parseNestedObject(row, schemaEntry.schema, propertyPath, columns, options) : isMissingColumn ? {
162
- value: options.propertyValueWhenColumnIsMissing
163
- } : parseDataCellValue(row[columnIndex], schemaEntry, propertyPath, options),
228
+ var isMissingColumn = schemaEntry.column ? columnIndex < 0 : undefined;
229
+ var _ref = schemaEntry.column ? isMissingColumn ? {
230
+ value: options.propertyValueWhenColumnIsMissing,
231
+ isEmptyValue: true
232
+ } : parseCellValueWithPossibleErrors(row[columnIndex], schemaEntry, options) : parseObject(row, schemaEntry.schema, path, columns, options),
233
+ value = _ref.value,
234
+ isEmptyValue = _ref.isEmptyValue,
164
235
  errors = _ref.errors,
165
- value = _ref.value;
236
+ children = _ref.children;
237
+
238
+ // If there were any errors, return them.
166
239
  if (errors) {
167
240
  return {
168
- errors: errors
169
- };
170
- }
171
-
172
- // Should apply `required` validation if the value is "empty".
173
- var pendingRequiredValidation;
174
- if (schemaEntry.required && isEmptyValue(value)) {
175
- // // Can optionally skip `required` validation for certain missing columns.
176
- // const skipRequiredValidation = isMissingColumn && options.shouldSkipRequiredValidationWhenColumnIsMissing(schemaEntry.column, { object: ... })
177
- // if (!skipRequiredValidation) { ... }
178
-
179
- // Will perform `required` validation in the end,
180
- // when all properties of the object have been parsed.
181
- // This is because `required` could also be a function of `object`.
182
- pendingRequiredValidation = {
183
- required: schemaEntry.required,
184
- schemaEntry: schemaEntry,
185
- value: value
241
+ // Return the errors.
242
+ errors: errors,
243
+ // Return the `children` because `required` validations still have to be run (later).
244
+ children: children
186
245
  };
187
246
  }
188
247
  return {
189
248
  value: value,
190
- pendingRequiredValidation: pendingRequiredValidation
249
+ isEmptyValue: isEmptyValue,
250
+ // Return the `children` because `required` validations still have to be run (later).
251
+ children: children
191
252
  };
192
253
  }
193
- function parseNestedObject(row, schema, propertyPath, columns, options) {
194
- var _parseDataRow = parseDataRow(row, schema, propertyPath, columns, options),
195
- object = _parseDataRow.object,
196
- errors = _parseDataRow.errors;
197
- return {
198
- value: object,
199
- errors: errors
200
- };
201
- }
202
- function parseDataCellValue(cellValue, schemaEntry, propertyPath, options) {
203
- var _parseDataCellValue_ = parseDataCellValue_(cellValue, schemaEntry, propertyPath, options),
204
- propertyValue = _parseDataCellValue_.value,
205
- errorMessage = _parseDataCellValue_.error,
206
- reason = _parseDataCellValue_.reason;
254
+ function parseCellValueWithPossibleErrors(cellValue, schemaEntry, options) {
255
+ var _parseCellValue = parseCellValue(cellValue, schemaEntry, options),
256
+ value = _parseCellValue.value,
257
+ isEmptyValue = _parseCellValue.isEmptyValue,
258
+ errorMessage = _parseCellValue.error,
259
+ errorReason = _parseCellValue.reason;
207
260
  if (errorMessage) {
208
261
  var error = createError({
209
- schemaEntry: schemaEntry,
210
- value: cellValue,
211
262
  error: errorMessage,
212
- reason: reason
263
+ reason: errorReason,
264
+ column: schemaEntry.column,
265
+ valueType: schemaEntry.type,
266
+ value: cellValue
213
267
  });
214
268
  return {
215
269
  errors: [error]
216
270
  };
217
- } else {
218
- return {
219
- value: propertyValue
220
- };
221
271
  }
272
+ return {
273
+ value: value,
274
+ isEmptyValue: isEmptyValue
275
+ };
222
276
  }
223
277
 
224
278
  /**
225
279
  * Converts a cell value value to a javascript typed value.
226
- * @param {any} value
280
+ * @param {any} cellValue
227
281
  * @param {object} schemaEntry
228
282
  * @param {string} propertyPath
229
283
  * @param {object} options
230
- * @return {{ value?: any, error?: string, reason?: string }}
284
+ * @return {{ value?: any, isEmptyValue: boolean } | { error: string, reason?: string }}
231
285
  */
232
- function parseDataCellValue_(cellValue, schemaEntry, propertyPath, options) {
286
+ function parseCellValue(cellValue, schemaEntry, options) {
233
287
  if (cellValue === undefined) {
234
288
  // This isn't supposed to be possible when reading spreadsheet data:
235
289
  // cell values are always read as `null` when those cells are empty.
236
290
  // It's currently impossible for `read-excel-file` to return `undefined` cell value.
237
291
  // Here it uses some "sensible default" fallback by treating `undefined` as "column missing".
238
292
  return {
239
- value: options.propertyValueWhenColumnIsMissing
293
+ value: options.propertyValueWhenColumnIsMissing,
294
+ isEmptyValue: true
240
295
  };
241
296
  }
242
297
  if (cellValue === null) {
243
298
  return {
244
- value: options.propertyValueWhenCellIsEmpty
299
+ value: options.propertyValueWhenCellIsEmpty,
300
+ isEmptyValue: true
245
301
  };
246
302
  }
303
+
304
+ // Parse comma-separated cell value.
247
305
  if (Array.isArray(schemaEntry.type)) {
248
- var errors = [];
249
- var reasons = [];
250
- var values = parseSeparatedSubstrings(cellValue, options.arrayValueSeparator).map(function (substring) {
251
- // If any substring was already detected to be invalid
252
- // don't attempt to parse any other substrings.
253
- if (errors.length > 0) {
254
- return;
255
- }
256
- // If an empty substring was extracted, it means that there was an out-of-place separator.
257
- if (!substring) {
258
- errors.push('invalid');
259
- reasons.push('syntax');
260
- return;
261
- }
262
- var _parseValue = parseValue(substring, schemaEntry, options),
263
- value = _parseValue.value,
264
- error = _parseValue.error,
265
- reason = _parseValue.reason;
266
- if (error) {
267
- errors.push(error);
268
- reasons.push(reason);
269
- return;
270
- }
271
- return value;
272
- });
306
+ return parseArrayValue(cellValue, schemaEntry, options);
307
+ }
308
+ return parseValue(cellValue, schemaEntry, options);
309
+ }
310
+
311
+ /**
312
+ * Converts textual value to a javascript typed array value.
313
+ * @param {any} value
314
+ * @param {object} schemaEntry
315
+ * @param {object} options
316
+ * @return {{ value?: any, isEmptyValue: boolean } | { error: string, reason?: string }}
317
+ */
318
+ function parseArrayValue(value, schemaEntry, options) {
319
+ // If the cell value is not a string — i.e. a number, a boolean, a Date —
320
+ // then throw an error.
321
+ if (typeof value !== 'string') {
322
+ return {
323
+ error: 'not_a_string'
324
+ };
325
+ }
326
+ var isEmptyArray = true;
327
+ var errors = [];
328
+ var reasons = [];
329
+ var values = parseSeparatedSubstrings(value, options.separatorCharacter).map(function (substring) {
330
+ // If any substring was already detected to be invalid
331
+ // don't attempt to parse any other substrings.
273
332
  if (errors.length > 0) {
274
- return {
275
- error: errors[0],
276
- reason: reasons[0]
277
- };
333
+ return;
278
334
  }
279
- var isEmpty = values.every(isEmptyValue);
280
- if (isEmpty) {
281
- return {
282
- value: options.transformEmptyArray(values, {
283
- path: propertyPath
284
- })
285
- };
335
+
336
+ // If an empty substring was extracted, it means that there was an out-of-place separator.
337
+ if (!substring) {
338
+ errors.push('invalid');
339
+ reasons.push('syntax');
340
+ return;
286
341
  }
342
+ var _parseValue = parseValue(substring, schemaEntry, options),
343
+ value = _parseValue.value,
344
+ isEmptyValue = _parseValue.isEmptyValue,
345
+ error = _parseValue.error,
346
+ reason = _parseValue.reason;
347
+ if (error) {
348
+ errors.push(error);
349
+ reasons.push(reason);
350
+ return;
351
+ }
352
+ if (isEmptyArray && !isEmptyValue) {
353
+ isEmptyArray = false;
354
+ }
355
+ return value;
356
+ });
357
+ if (errors.length > 0) {
287
358
  return {
288
- value: values
359
+ error: errors[0],
360
+ reason: reasons[0]
289
361
  };
290
362
  }
291
- return parseValue(cellValue, schemaEntry, options);
363
+ return {
364
+ value: values,
365
+ isEmptyValue: isEmptyArray
366
+ };
292
367
  }
293
368
 
294
369
  /**
295
370
  * Converts textual value to a javascript typed value.
296
371
  * @param {any} value
297
372
  * @param {object} schemaEntry
298
- * @return {{ value: any, error: string }}
373
+ * @param {object} options
374
+ * @return {{ value?: any, isEmptyValue: boolean } | { error: string }}
299
375
  */
300
376
  function parseValue(value, schemaEntry, options) {
377
+ // `null` values (i.e. empty cells) don't get parsed.
301
378
  if (value === null) {
302
379
  return {
303
- value: null
380
+ value: null,
381
+ isEmptyValue: true
304
382
  };
305
383
  }
384
+
385
+ // Parse the value according to the `type` that is specified in the schema entry.
306
386
  var result;
307
387
  if (schemaEntry.type) {
308
388
  result = parseValueOfType(value,
309
- // Supports parsing array types.
310
- // See `parseSeparatedSubstrings()` function for more details.
389
+ // Get the type of the value.
390
+ //
391
+ // Handle the case if it's a comma-separated value.
311
392
  // Example `type`: String[]
312
- // Input: 'Barack Obama, "String, with, colons", Donald Trump'
313
- // Output: ['Barack Obama', 'String, with, colons', 'Donald Trump']
393
+ // Example Input Value: 'Barack Obama, "String, with, colons", Donald Trump'
394
+ // Example Parsed Value: ['Barack Obama', 'String, with, colons', 'Donald Trump']
395
+ //
314
396
  Array.isArray(schemaEntry.type) ? schemaEntry.type[0] : schemaEntry.type, options);
315
397
  } else {
316
- // The default `type` is `String`.
398
+ // If the `type` is not specified for a given schema entry, the default one is `String`.
317
399
  result = {
318
400
  value: value
319
401
  };
320
402
  // throw new Error('Invalid schema entry: no `type` specified:\n\n' + JSON.stringify(schemaEntry, null, 2))
321
403
  }
322
404
 
323
- // If errored then return the error.
405
+ // If there was an error when parsing the value then return the error.
324
406
  if (result.error) {
325
407
  return result;
326
408
  }
327
409
 
328
- // Validate the value.
329
- if (result.value !== null) {
330
- // Perform `oneOf` validation.
331
- if (schemaEntry.oneOf && schemaEntry.oneOf.indexOf(result.value) < 0) {
410
+ // If the parsed value is empty, return it.
411
+ if (value === null) {
412
+ return {
413
+ value: null,
414
+ isEmptyValue: true
415
+ };
416
+ }
417
+
418
+ // Value is not empty.
419
+ // Validate it and return.
420
+
421
+ // Perform `oneOf` validation.
422
+ if (schemaEntry.oneOf) {
423
+ var errorAndReason = validateOneOf(result.value, schemaEntry.oneOf);
424
+ if (errorAndReason) {
425
+ return errorAndReason;
426
+ }
427
+ }
428
+
429
+ // Perform `validate()` validation.
430
+ if (schemaEntry.validate) {
431
+ try {
432
+ schemaEntry.validate(result.value);
433
+ } catch (error) {
332
434
  return {
333
- error: 'invalid',
334
- reason: 'unknown'
435
+ error: error.message
335
436
  };
336
437
  }
337
- // Perform `validate()` validation.
338
- if (schemaEntry.validate) {
339
- try {
340
- schemaEntry.validate(result.value);
341
- } catch (error) {
342
- return {
343
- error: error.message
344
- };
345
- }
346
- }
347
438
  }
348
- return result;
439
+
440
+ // Return the value.
441
+ return {
442
+ value: result.value,
443
+ isEmptyValue: isEmptyValue(result.value)
444
+ };
445
+ }
446
+ function validateOneOf(value, oneOf) {
447
+ if (oneOf.indexOf(value) < 0) {
448
+ return {
449
+ error: 'invalid',
450
+ reason: 'unknown'
451
+ };
452
+ }
349
453
  }
350
454
 
351
455
  /**
@@ -395,18 +499,22 @@ function parseValueUsingTypeParser(value, type) {
395
499
  var result = {
396
500
  error: error.message
397
501
  };
502
+ // Built-in types such as `Number` or `Date` may also report
503
+ // a specific `reason` of the error.
398
504
  if (error.reason) {
399
505
  result.reason = error.reason;
400
506
  }
401
507
  return result;
402
508
  }
403
509
  }
404
- function getNextSubstring(string, endCharacter, startIndex) {
510
+
511
+ // Extracts a substring from a string.
512
+ function getNextSubstring(string, separatorCharacter, startIndex) {
405
513
  var i = 0;
406
514
  var substring = '';
407
515
  while (startIndex + i < string.length) {
408
516
  var character = string[startIndex + i];
409
- if (character === endCharacter) {
517
+ if (character === separatorCharacter) {
410
518
  return [substring, i];
411
519
  }
412
520
  // Previously, it used to treat `"` character similar to how it's treated in `.csv` files:
@@ -433,27 +541,110 @@ function getNextSubstring(string, endCharacter, startIndex) {
433
541
  * @param {string} string — A string of comma-separated substrings.
434
542
  * @return {string[]} An array of substrings.
435
543
  */
436
- function parseSeparatedSubstrings(string, arrayValueSeparator) {
544
+ function parseSeparatedSubstrings(string, separatorCharacter) {
437
545
  var elements = [];
438
546
  var index = 0;
439
547
  while (index < string.length) {
440
- var _getNextSubstring = getNextSubstring(string, arrayValueSeparator, index),
548
+ var _getNextSubstring = getNextSubstring(string, separatorCharacter, index),
441
549
  _getNextSubstring2 = _slicedToArray(_getNextSubstring, 2),
442
550
  substring = _getNextSubstring2[0],
443
551
  length = _getNextSubstring2[1];
444
- index += length + arrayValueSeparator.length;
552
+ index += length + separatorCharacter.length;
445
553
  elements.push(substring.trim());
446
554
  }
447
555
  return elements;
448
556
  }
557
+ function transformValue(value, isEmptyValue, path, options) {
558
+ if (isEmptyValue) {
559
+ if (isObject(value)) {
560
+ return options.transformEmptyObject(value, {
561
+ path: path
562
+ });
563
+ } else if (Array.isArray(value)) {
564
+ return options.transformEmptyArray(value, {
565
+ path: path
566
+ });
567
+ }
568
+ }
569
+ return value;
570
+ }
571
+ function getPropertyPath(propertyName, parentObjectPath) {
572
+ return "".concat(parentObjectPath ? parentObjectPath + '.' : '').concat(propertyName);
573
+ }
574
+
575
+ // Recursively runs `required` validations for the parsed data row tree.
576
+ function runPendingRequiredValidations(schemaEntry, value, isEmptyValue, errors, children, parentObjectIsRequired, parentObjectValue, parentObjectValueIsEmpty, parentObjectErrors) {
577
+ var requiredErrors = [];
578
+
579
+ // See if this property is required.
580
+ var isRequired = isPropertyRequired(schemaEntry, parentObjectIsRequired, parentObjectValue, parentObjectValueIsEmpty, parentObjectErrors);
581
+
582
+ // If this property is required and empty, create a "required" error.
583
+ if (isRequired && isEmptyValue) {
584
+ requiredErrors.push(createError({
585
+ error: 'required',
586
+ column: schemaEntry.column,
587
+ valueType: schemaEntry.type,
588
+ value: value
589
+ }));
590
+ }
591
+
592
+ // Run `required` validations of the children.
593
+ if (children) {
594
+ for (var _iterator2 = _createForOfIteratorHelperLoose(children), _step2; !(_step2 = _iterator2()).done;) {
595
+ var child = _step2.value;
596
+ var requiredErrorsOfChild = runPendingRequiredValidations(child.schemaEntry, child.value, child.isEmptyValue, child.errors, child.children,
597
+ // The following properties describe the parent object of the `child`,
598
+ // i.e. the current (iterated) object.
599
+ isRequired, value, isEmptyValue, errors);
600
+ if (requiredErrorsOfChild) {
601
+ requiredErrors = requiredErrors.concat(requiredErrorsOfChild);
602
+ }
603
+ }
604
+ }
605
+ if (requiredErrors.length > 0) {
606
+ return requiredErrors;
607
+ }
608
+ }
609
+ function isPropertyRequired(schemaEntry, parentObjectIsRequired, parentObjectValue, parentObjectValueIsEmpty, parentObjectErrors) {
610
+ // If the parent object is marked as `required: false` then it's allowed
611
+ // to be absent entirely from the input data. If that's the case,
612
+ // i.e. if the parent object is absent entirely from the input data,
613
+ // then any descendant properties of such object are allowed to be absent too,
614
+ // which means that they should also be considered being `required: false`.
615
+ //
616
+ // Also, if the parent object couldn't be parsed due to some non-`required` errors,
617
+ // it can't be known whether it's actually empty or not. In case of such uncertainty,
618
+ // the code shouldn't attempt to be overly smart and do things that might not be necessary,
619
+ // so such parent object is just assumed to be empty in order to not falsly trigger
620
+ // any `required` validations that otherwise wouldn't have been run.
621
+ // In other words, skipping some `required` validations is better than
622
+ // running `required` validations that shouldn't have been run.
623
+ //
624
+ if (parentObjectIsRequired === false && (parentObjectValueIsEmpty || parentObjectErrors)) {
625
+ return false;
626
+ }
627
+ return schemaEntry.required && (typeof schemaEntry.required === 'boolean' ? schemaEntry.required :
628
+ // If there were any non-`required` errors when parsing the parent object,
629
+ // the `parentObject` will be `undefined`. In that case, "complex" `required()`
630
+ // validations — the ones where `required` is a function — can't really be run
631
+ // because those validations assume a fully and correctly parsed parent object
632
+ // be passed as an argument, and the thing is that the `parentObject` is unknown.
633
+ // As a result, only "basic" `required` validations could be run,
634
+ // i.e. the ones where `required` is just a boolean, and "complex" `required`
635
+ // validations, i.e. the ones where `required` is a functions, should be skipped,
636
+ // because it's better to skip some `required` errors than to trigger falsy ones.
637
+ parentObjectErrors ? false : schemaEntry.required(parentObjectValue));
638
+ }
449
639
  function createError(_ref2) {
450
- var schemaEntry = _ref2.schemaEntry,
640
+ var column = _ref2.column,
641
+ valueType = _ref2.valueType,
451
642
  value = _ref2.value,
452
643
  errorMessage = _ref2.error,
453
644
  reason = _ref2.reason;
454
645
  var error = {
455
646
  error: errorMessage,
456
- column: schemaEntry.column,
647
+ column: column,
457
648
  value: value
458
649
  };
459
650
  if (reason) {
@@ -461,14 +652,14 @@ function createError(_ref2) {
461
652
  }
462
653
  // * Regular values specify a `type?` property, which is included in the `error` object.
463
654
  // * Nested objects specify a `schema` property, which is not included in the `error` object.
464
- if (schemaEntry.type) {
465
- error.type = schemaEntry.type;
655
+ if (valueType) {
656
+ error.type = valueType;
466
657
  }
467
658
  return error;
468
659
  }
469
660
  function validateSchema(schema) {
470
- for (var _i4 = 0, _Object$keys2 = Object.keys(schema); _i4 < _Object$keys2.length; _i4++) {
471
- var key = _Object$keys2[_i4];
661
+ for (var _i2 = 0, _Object$keys2 = Object.keys(schema); _i2 < _Object$keys2.length; _i2++) {
662
+ var key = _Object$keys2[_i2];
472
663
  var schemaEntry = schema[key];
473
664
  // Validate that the `schema` is not using a deprecated `type: nestedSchema` format.
474
665
  if (_typeof(schemaEntry.type) === 'object' && !Array.isArray(schemaEntry.type)) {
@@ -481,6 +672,30 @@ function validateSchema(schema) {
481
672
  }
482
673
  }
483
674
  }
675
+
676
+ // A nested object could have a `required` property but the only allowed value is `false`.
677
+ // The reason why `true` value is not allowed is because in case of a "required" error
678
+ // there's no single column title corresponding to such nested object, and column title
679
+ // is required to create a "required" error.
680
+ validateObjectSchemaRequiredProperty(schema, undefined);
681
+ }
682
+ function validateObjectSchemaRequiredProperty(schema, required) {
683
+ if (required !== undefined && required !== false) {
684
+ throw new Error("In a schema, a nested object can have a `required` property but the only allowed value is `undefined` or `false`. Otherwise, a \"required\" error for a nested object would have to include a specific `column` title and a nested object doesn't have one. You've specified the following `required`: ".concat(required));
685
+ }
686
+ // For each property of the described object.
687
+ for (var _i3 = 0, _Object$keys3 = Object.keys(schema); _i3 < _Object$keys3.length; _i3++) {
688
+ var key = _Object$keys3[_i3];
689
+ // If this property is itself an object.
690
+ if (isObject(schema[key].schema)) {
691
+ // Validate that a `column` property can't coexist with a `schema` property.
692
+ if (schema[key].column) {
693
+ throw new Error("In a schema, `column` property is only allowed when describing a property value rather than a nested object. Key: ".concat(key, ". Schema:\n").concat(JSON.stringify(schema[key], null, 2)));
694
+ }
695
+ // Recurse into the child object.
696
+ validateObjectSchemaRequiredProperty(schema[key].schema, schema[key].required);
697
+ }
698
+ }
484
699
  }
485
700
  function isEmptyValue(value) {
486
701
  return value === undefined || value === null;
@@ -497,7 +712,7 @@ var DEFAULT_OPTIONS = {
497
712
  transformEmptyArray: function transformEmptyArray() {
498
713
  return null;
499
714
  },
500
- arrayValueSeparator: ','
715
+ separatorCharacter: ','
501
716
  };
502
717
  function applyDefaultOptions(options) {
503
718
  if (options) {
@@ -506,4 +721,11 @@ function applyDefaultOptions(options) {
506
721
  return DEFAULT_OPTIONS;
507
722
  }
508
723
  }
724
+
725
+ // This `value` marks the start of a tree structure that is parsed from a given data row.
726
+ var PARSED_OBJECT_TREE_START = {};
727
+ var objectConstructor = {}.constructor;
728
+ function isObject(object) {
729
+ return object !== undefined && object !== null && object.constructor === objectConstructor;
730
+ }
509
731
  //# sourceMappingURL=parseData.js.map