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