manyfest 1.0.2 → 1.0.3

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.
@@ -0,0 +1,585 @@
1
+ /**
2
+ * @license MIT
3
+ * @author <steven@velozo.com>
4
+ */
5
+ let libSimpleLog = require('./Manyfest-LogToConsole.js');
6
+
7
+ /**
8
+ * Object Address Resolver
9
+ *
10
+ * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
11
+ * be extremely clear what is going on in the recursion for
12
+ * each of the three address resolution functions.
13
+ *
14
+ * Although there is some opportunity to repeat ourselves a
15
+ * bit less in this codebase (e.g. with detection of arrays
16
+ * versus objects versus direct properties), it can make
17
+ * debugging.. challenging. The minified version of the code
18
+ * optimizes out almost anything repeated in here. So please
19
+ * be kind and rewind... meaning please keep the codebase less
20
+ * terse and more verbose so humans can comprehend it.
21
+ *
22
+ *
23
+ * @class ManyfestObjectAddressResolver
24
+ */
25
+ class ManyfestObjectAddressResolver
26
+ {
27
+ constructor(pInfoLog, pErrorLog)
28
+ {
29
+ // Wire in logging
30
+ this.logInfo = (typeof(pInfoLog) === 'function') ? pInfoLog : libSimpleLog;
31
+ this.logError = (typeof(pErrorLog) === 'function') ? pErrorLog : libSimpleLog;
32
+ }
33
+
34
+ // When a boxed property is passed in, it should have quotes of some
35
+ // kind around it.
36
+ //
37
+ // For instance:
38
+ // MyValues['Name']
39
+ // MyValues["Age"]
40
+ // MyValues[`Cost`]
41
+ //
42
+ // This function removes the wrapping quotes.
43
+ //
44
+ // Please note it *DOES NOT PARSE* template literals, so backticks just
45
+ // end up doing the same thing as other quote types.
46
+ //
47
+ // TODO: Should template literals be processed? If so what state do they have access to?
48
+ cleanWrapCharacters (pCharacter, pString)
49
+ {
50
+ if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter))
51
+ {
52
+ return pString.substring(1, pString.length - 1);
53
+ }
54
+ else
55
+ {
56
+ return pString;
57
+ }
58
+ }
59
+
60
+ // Check if an address exists.
61
+ //
62
+ // This is necessary because the getValueAtAddress function is ambiguous on
63
+ // whether the element/property is actually there or not (it returns
64
+ // undefined whether the property exists or not). This function checks for
65
+ // existance and returns true or false dependent.
66
+ checkAddressExists (pObject, pAddress)
67
+ {
68
+ // TODO: Should these throw an error?
69
+ // Make sure pObject is an object
70
+ if (!typeof(pObject) === 'object') return false;
71
+ // Make sure pAddress is a string
72
+ if (!typeof(pAddress) === 'string') return false;
73
+
74
+ // TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
75
+ let tmpSeparatorIndex = pAddress.indexOf('.');
76
+
77
+ // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
78
+ if (tmpSeparatorIndex === -1)
79
+ {
80
+ // Check if the address refers to a boxed property
81
+ let tmpBracketStartIndex = pAddress.indexOf('[');
82
+ let tmpBracketStopIndex = pAddress.indexOf(']');
83
+ // Boxed elements look like this:
84
+ // MyValues[10]
85
+ // MyValues['Name']
86
+ // MyValues["Age"]
87
+ // MyValues[`Cost`]
88
+ //
89
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
90
+ // The requirements to detect a boxed element are:
91
+ // 1) The start bracket is after character 0
92
+ if ((tmpBracketStartIndex > 0)
93
+ // 2) The end bracket has something between them
94
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
95
+ // 3) There is data
96
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
97
+ {
98
+ // The "Name" of the Object contained too the left of the bracket
99
+ let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
100
+
101
+ // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
102
+ // This is a rare case where Arrays testing as Objects is useful
103
+ if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
104
+ {
105
+ return false;
106
+ }
107
+
108
+ // The "Reference" to the property within it, either an array element or object property
109
+ let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
110
+ // Attempt to parse the reference as a number, which will be used as an array element
111
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
112
+
113
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
114
+ // This seems confusing to me at first read, so explaination:
115
+ // Is the Boxed Object an Array? TRUE
116
+ // And is the Reference inside the boxed Object not a number? TRUE
117
+ // --> So when these are in agreement, it's an impossible access state
118
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
119
+ {
120
+ return false;
121
+ }
122
+
123
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
124
+ // otherwise we will try to treat it as a dynamic object property.
125
+ if (isNaN(tmpBoxedPropertyNumber))
126
+ {
127
+ // This isn't a number ... let's treat it as a dynamic object property.
128
+ // We would expect the property to be wrapped in some kind of quotes so strip them
129
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
130
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
131
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
132
+
133
+ // Check if the property exists.
134
+ return pObject[tmpBoxedPropertyName].hasOwnProperty(tmpBoxedPropertyReference);
135
+ }
136
+ else
137
+ {
138
+ // Use the new in operator to see if the element is in the array
139
+ return (tmpBoxedPropertyNumber in pObject[tmpBoxedPropertyName]);
140
+ }
141
+ }
142
+ else
143
+ {
144
+ // Check if the property exists
145
+ return pObject.hasOwnProperty(pAddress);
146
+ }
147
+ }
148
+ else
149
+ {
150
+ let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
151
+ let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
152
+
153
+ // Test if the tmpNewAddress is an array or object
154
+ // Check if it's a boxed property
155
+ let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
156
+ let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
157
+ // Boxed elements look like this:
158
+ // MyValues[42]
159
+ // MyValues['Color']
160
+ // MyValues["Weight"]
161
+ // MyValues[`Diameter`]
162
+ //
163
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
164
+ // The requirements to detect a boxed element are:
165
+ // 1) The start bracket is after character 0
166
+ if ((tmpBracketStartIndex > 0)
167
+ // 2) The end bracket has something between them
168
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
169
+ // 3) There is data
170
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
171
+ {
172
+ let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
173
+
174
+ let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
175
+
176
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
177
+
178
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
179
+ // This seems confusing to me at first read, so explaination:
180
+ // Is the Boxed Object an Array? TRUE
181
+ // And is the Reference inside the boxed Object not a number? TRUE
182
+ // --> So when these are in agreement, it's an impossible access state
183
+ // This could be a failure in the recursion chain because they passed something like this in:
184
+ // StudentData.Sections.Algebra.Students[1].Tardy
185
+ // BUT
186
+ // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
187
+ // This could be a failure in the recursion chain because they passed something like this in:
188
+ // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
189
+ // BUT
190
+ // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
191
+ // TODO: Should this be an error or something? Should we keep a log of failures like this?
192
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
193
+ {
194
+ // Because this is an impossible address, the property doesn't exist
195
+ // TODO: Should we throw an error in this condition?
196
+ return false;
197
+ }
198
+
199
+ //This is a bracketed value
200
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
201
+ // otherwise we will try to reat it as a dynamic object property.
202
+ if (isNaN(tmpBoxedPropertyNumber))
203
+ {
204
+ // This isn't a number ... let's treat it as a dynanmic object property.
205
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
206
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
207
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
208
+
209
+ // Recurse directly into the subobject
210
+ return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
211
+ }
212
+ else
213
+ {
214
+ // We parsed a valid number out of the boxed property name, so recurse into the array
215
+ return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
216
+ }
217
+ }
218
+
219
+ // If there is an object property already named for the sub object, but it isn't an object
220
+ // then the system can't set the value in there. Error and abort!
221
+ if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
222
+ {
223
+ return false;
224
+ }
225
+ else if (pObject.hasOwnProperty(tmpSubObjectName))
226
+ {
227
+ // If there is already a subobject pass that to the recursive thingy
228
+ return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress);
229
+ }
230
+ else
231
+ {
232
+ // Create a subobject and then pass that
233
+ pObject[tmpSubObjectName] = {};
234
+ return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress);
235
+ }
236
+ }
237
+ }
238
+
239
+ // Get the value of an element at an address
240
+ getValueAtAddress (pObject, pAddress)
241
+ {
242
+ // Make sure pObject is an object
243
+ if (!typeof(pObject) === 'object') return undefined;
244
+ // Make sure pAddress is a string
245
+ if (!typeof(pAddress) === 'string') return undefined;
246
+
247
+ // TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
248
+ let tmpSeparatorIndex = pAddress.indexOf('.');
249
+
250
+ // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
251
+ if (tmpSeparatorIndex === -1)
252
+ {
253
+ // Check if the address refers to a boxed property
254
+ let tmpBracketStartIndex = pAddress.indexOf('[');
255
+ let tmpBracketStopIndex = pAddress.indexOf(']');
256
+ // Boxed elements look like this:
257
+ // MyValues[10]
258
+ // MyValues['Name']
259
+ // MyValues["Age"]
260
+ // MyValues[`Cost`]
261
+ //
262
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
263
+ // The requirements to detect a boxed element are:
264
+ // 1) The start bracket is after character 0
265
+ if ((tmpBracketStartIndex > 0)
266
+ // 2) The end bracket has something between them
267
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
268
+ // 3) There is data
269
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
270
+ {
271
+ // The "Name" of the Object contained too the left of the bracket
272
+ let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
273
+
274
+ // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
275
+ // This is a rare case where Arrays testing as Objects is useful
276
+ if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
277
+ {
278
+ return undefined;
279
+ }
280
+
281
+ // The "Reference" to the property within it, either an array element or object property
282
+ let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
283
+ // Attempt to parse the reference as a number, which will be used as an array element
284
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
285
+
286
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
287
+ // This seems confusing to me at first read, so explaination:
288
+ // Is the Boxed Object an Array? TRUE
289
+ // And is the Reference inside the boxed Object not a number? TRUE
290
+ // --> So when these are in agreement, it's an impossible access state
291
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
292
+ {
293
+ return undefined;
294
+ }
295
+
296
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
297
+ // otherwise we will try to treat it as a dynamic object property.
298
+ if (isNaN(tmpBoxedPropertyNumber))
299
+ {
300
+ // This isn't a number ... let's treat it as a dynamic object property.
301
+ // We would expect the property to be wrapped in some kind of quotes so strip them
302
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
303
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
304
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
305
+
306
+ // Return the value in the property
307
+ return pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference];
308
+ }
309
+ else
310
+ {
311
+ return pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber];
312
+ }
313
+ }
314
+ else
315
+ {
316
+ // Now is the point in recursion to return the value in the address
317
+ return pObject[pAddress];
318
+ }
319
+ }
320
+ else
321
+ {
322
+ let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
323
+ let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
324
+
325
+ // Test if the tmpNewAddress is an array or object
326
+ // Check if it's a boxed property
327
+ let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
328
+ let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
329
+ // Boxed elements look like this:
330
+ // MyValues[42]
331
+ // MyValues['Color']
332
+ // MyValues["Weight"]
333
+ // MyValues[`Diameter`]
334
+ //
335
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
336
+ // The requirements to detect a boxed element are:
337
+ // 1) The start bracket is after character 0
338
+ if ((tmpBracketStartIndex > 0)
339
+ // 2) The end bracket has something between them
340
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
341
+ // 3) There is data
342
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
343
+ {
344
+ let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
345
+
346
+ let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
347
+
348
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
349
+
350
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
351
+ // This seems confusing to me at first read, so explaination:
352
+ // Is the Boxed Object an Array? TRUE
353
+ // And is the Reference inside the boxed Object not a number? TRUE
354
+ // --> So when these are in agreement, it's an impossible access state
355
+ // This could be a failure in the recursion chain because they passed something like this in:
356
+ // StudentData.Sections.Algebra.Students[1].Tardy
357
+ // BUT
358
+ // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
359
+ // This could be a failure in the recursion chain because they passed something like this in:
360
+ // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
361
+ // BUT
362
+ // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
363
+ // TODO: Should this be an error or something? Should we keep a log of failures like this?
364
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
365
+ {
366
+ return undefined;
367
+ }
368
+
369
+ //This is a bracketed value
370
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
371
+ // otherwise we will try to reat it as a dynamic object property.
372
+ if (isNaN(tmpBoxedPropertyNumber))
373
+ {
374
+ // This isn't a number ... let's treat it as a dynanmic object property.
375
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
376
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
377
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
378
+
379
+ // Recurse directly into the subobject
380
+ return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
381
+ }
382
+ else
383
+ {
384
+ // We parsed a valid number out of the boxed property name, so recurse into the array
385
+ return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
386
+ }
387
+ }
388
+
389
+ // If there is an object property already named for the sub object, but it isn't an object
390
+ // then the system can't set the value in there. Error and abort!
391
+ if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
392
+ {
393
+ return undefined;
394
+ }
395
+ else if (pObject.hasOwnProperty(tmpSubObjectName))
396
+ {
397
+ // If there is already a subobject pass that to the recursive thingy
398
+ return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
399
+ }
400
+ else
401
+ {
402
+ // Create a subobject and then pass that
403
+ pObject[tmpSubObjectName] = {};
404
+ return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
405
+ }
406
+ }
407
+ }
408
+
409
+ // Set the value of an element at an address
410
+ setValueAtAddress (pObject, pAddress, pValue)
411
+ {
412
+ // Make sure pObject is an object
413
+ if (!typeof(pObject) === 'object') return false;
414
+ // Make sure pAddress is a string
415
+ if (!typeof(pAddress) === 'string') return false;
416
+
417
+ let tmpSeparatorIndex = pAddress.indexOf('.');
418
+
419
+ if (tmpSeparatorIndex === -1)
420
+ {
421
+ // Check if it's a boxed property
422
+ let tmpBracketStartIndex = pAddress.indexOf('[');
423
+ let tmpBracketStopIndex = pAddress.indexOf(']');
424
+ // Boxed elements look like this:
425
+ // MyValues[10]
426
+ // MyValues['Name']
427
+ // MyValues["Age"]
428
+ // MyValues[`Cost`]
429
+ //
430
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
431
+ // The requirements to detect a boxed element are:
432
+ // 1) The start bracket is after character 0
433
+ if ((tmpBracketStartIndex > 0)
434
+ // 2) The end bracket has something between them
435
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
436
+ // 3) There is data
437
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
438
+ {
439
+ // The "Name" of the Object contained too the left of the bracket
440
+ let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
441
+
442
+ // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
443
+ // This is a rare case where Arrays testing as Objects is useful
444
+ if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
445
+ {
446
+ return false;
447
+ }
448
+
449
+ // The "Reference" to the property within it, either an array element or object property
450
+ let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
451
+ // Attempt to parse the reference as a number, which will be used as an array element
452
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
453
+
454
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
455
+ // This seems confusing to me at first read, so explaination:
456
+ // Is the Boxed Object an Array? TRUE
457
+ // And is the Reference inside the boxed Object not a number? TRUE
458
+ // --> So when these are in agreement, it's an impossible access state
459
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
460
+ {
461
+ return false;
462
+ }
463
+
464
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
465
+ // otherwise we will try to treat it as a dynamic object property.
466
+ if (isNaN(tmpBoxedPropertyNumber))
467
+ {
468
+ // This isn't a number ... let's treat it as a dynamic object property.
469
+ // We would expect the property to be wrapped in some kind of quotes so strip them
470
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
471
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
472
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
473
+
474
+ // Return the value in the property
475
+ pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference] = pValue;
476
+ return true;
477
+ }
478
+ else
479
+ {
480
+ pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber] = pValue;
481
+ return true;
482
+ }
483
+ }
484
+ else
485
+ {
486
+ // Now is the time in recursion to set the value in the object
487
+ pObject[pAddress] = pValue;
488
+ return true;
489
+ }
490
+ }
491
+ else
492
+ {
493
+ let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
494
+ let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
495
+
496
+ // Test if the tmpNewAddress is an array or object
497
+ // Check if it's a boxed property
498
+ let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
499
+ let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
500
+ // Boxed elements look like this:
501
+ // MyValues[42]
502
+ // MyValues['Color']
503
+ // MyValues["Weight"]
504
+ // MyValues[`Diameter`]
505
+ //
506
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
507
+ // The requirements to detect a boxed element are:
508
+ // 1) The start bracket is after character 0
509
+ if ((tmpBracketStartIndex > 0)
510
+ // 2) The end bracket has something between them
511
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
512
+ // 3) There is data
513
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
514
+ {
515
+ let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
516
+
517
+ let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
518
+
519
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
520
+
521
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
522
+ // This seems confusing to me at first read, so explaination:
523
+ // Is the Boxed Object an Array? TRUE
524
+ // And is the Reference inside the boxed Object not a number? TRUE
525
+ // --> So when these are in agreement, it's an impossible access state
526
+ // This could be a failure in the recursion chain because they passed something like this in:
527
+ // StudentData.Sections.Algebra.Students[1].Tardy
528
+ // BUT
529
+ // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
530
+ // This could be a failure in the recursion chain because they passed something like this in:
531
+ // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
532
+ // BUT
533
+ // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
534
+ // TODO: Should this be an error or something? Should we keep a log of failures like this?
535
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
536
+ {
537
+ return false;
538
+ }
539
+
540
+ //This is a bracketed value
541
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
542
+ // otherwise we will try to reat it as a dynamic object property.
543
+ if (isNaN(tmpBoxedPropertyNumber))
544
+ {
545
+ // This isn't a number ... let's treat it as a dynanmic object property.
546
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
547
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
548
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
549
+
550
+ // Recurse directly into the subobject
551
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress, pValue);
552
+ }
553
+ else
554
+ {
555
+ // We parsed a valid number out of the boxed property name, so recurse into the array
556
+ return this.setValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress, pValue);
557
+ }
558
+ }
559
+
560
+ // If there is an object property already named for the sub object, but it isn't an object
561
+ // then the system can't set the value in there. Error and abort!
562
+ if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
563
+ {
564
+ if (!pObject.hasOwnProperty('__ERROR'))
565
+ pObject['__ERROR'] = {};
566
+ // Put it in an error object so data isn't lost
567
+ pObject['__ERROR'][pAddress] = pValue;
568
+ return false;
569
+ }
570
+ else if (pObject.hasOwnProperty(tmpSubObjectName))
571
+ {
572
+ // If there is already a subobject pass that to the recursive thingy
573
+ return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
574
+ }
575
+ else
576
+ {
577
+ // Create a subobject and then pass that
578
+ pObject[tmpSubObjectName] = {};
579
+ return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
580
+ }
581
+ }
582
+ }
583
+ };
584
+
585
+ module.exports = ManyfestObjectAddressResolver;