fable 3.0.30 → 3.0.32

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fable",
3
- "version": "3.0.30",
3
+ "version": "3.0.32",
4
4
  "description": "An entity behavior management and API bundling library.",
5
5
  "main": "source/Fable.js",
6
6
  "scripts": {
@@ -48,6 +48,13 @@ class FableService extends libFableServiceBase.CoreServiceProviderBase
48
48
  }
49
49
  }
50
50
 
51
+ // This is for the services that are meant to run mostly single-instance so need a default at initialization
52
+ addAndInstantiateServiceType(pServiceType, pServiceClass)
53
+ {
54
+ this.addServiceType(pServiceType, pServiceClass);
55
+ this.instantiateServiceProvider(pServiceType, {}, `${pServiceType}-Default`);
56
+ }
57
+
51
58
  instantiateServiceProvider(pServiceType, pOptions, pCustomServiceHash)
52
59
  {
53
60
  // Instantiate the service
package/source/Fable.js CHANGED
@@ -11,7 +11,7 @@ const libFableLog = require('fable-log');
11
11
  const libFableServiceManager = require('./Fable-ServiceManager.js');
12
12
 
13
13
  // Default Services
14
- const libFableServiceDataArithmatic = require('data-arithmatic');
14
+ const libFableServiceDataFormat = require('./services/Fable-Service-DataFormat.js');
15
15
  const libFableServiceMetaTemplate = require('./services/Fable-Service-MetaTemplate.js');
16
16
  const libFableServiceOperation = require('./services/Fable-Service-Operation.js');
17
17
  const libFableServiceRestClient = require('./services/Fable-Service-RestClient.js');
@@ -53,10 +53,8 @@ class Fable
53
53
  // Initialize and instantiate the default baked-in Data Arithmatic service
54
54
  this.serviceManager.addServiceType('Template', libFableServiceTemplate);
55
55
  this.serviceManager.addServiceType('MetaTemplate', libFableServiceMetaTemplate);
56
- this.serviceManager.addServiceType('DataArithmatic', libFableServiceDataArithmatic);
57
- this.fable.serviceManager.instantiateServiceProvider('DataArithmatic');
58
- this.serviceManager.addServiceType('Utility', libFableServiceUtility);
59
- this.fable.serviceManager.instantiateServiceProvider('Utility');
56
+ this.serviceManager.addAndInstantiateServiceType('DataFormat', libFableServiceDataFormat);
57
+ this.serviceManager.addAndInstantiateServiceType('Utility', libFableServiceUtility);
60
58
  this.serviceManager.addServiceType('Operation', libFableServiceOperation);
61
59
  this.serviceManager.addServiceType('RestClient', libFableServiceRestClient);
62
60
 
@@ -0,0 +1,638 @@
1
+ /**
2
+ * @license MIT
3
+ */
4
+
5
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
6
+ /**
7
+ * Data Formatting and Translation Functions
8
+ *
9
+ * @class DataFormat
10
+ */
11
+ class DataFormat extends libFableServiceProviderBase
12
+ {
13
+ constructor(pFable, pOptions, pServiceHash)
14
+ {
15
+ super(pFable, pOptions, pServiceHash)
16
+
17
+ this.serviceType = 'DataArithmatic';
18
+
19
+ // Regular Expressions (so they don't have to be recompiled every time)
20
+ // These could be defined as static, but I'm not sure if that will work with browserify ... and specifically the QT browser.
21
+ this._Regex_formatterInsertCommas = /.{1,3}/g;
22
+ // Match Function:
23
+ // function(pMatch, pSign, pZeros, pBefore, pDecimal, pAfter)
24
+ // Thoughts about below: /^([+-]?)(0*)(\d+)(\.(\d+))?$/;
25
+ this._Regex_formatterAddCommasToNumber = /^([-+]?)(0?)(\d+)(.?)(\d+)$/g;
26
+ this._Regex_formatterDollarsRemoveCommas = /,/gi;
27
+ this._Regex_formatterCleanNonAlpha = /[^a-z0-9]/gi;
28
+
29
+ // TODO: Potentially pull these in from a configuration.
30
+ // TODO: Use locale data for this if it's defaults all the way down.
31
+ this._Value_MoneySign_Currency = '$';
32
+ this._Value_NaN_Currency = '--';
33
+ this._Value_GroupSeparator_Number = ',';
34
+
35
+ this._Value_Prefix_StringHash = 'HSH';
36
+ this._Value_Clean_formatterCleanNonAlpha = '_';
37
+
38
+ this._UseEngineStringStartsWith = (typeof(String.prototype.startsWith) === 'function');
39
+ this._UseEngineStringEndsWith = (typeof(String.prototype.endsWith) === 'function');
40
+ }
41
+
42
+
43
+ /*************************************************************************
44
+ * String Manipulation and Comparison Functions
45
+ *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
46
+
47
+ /**
48
+ * Reverse a string
49
+ *
50
+ * @param {string} pString - The string to reverse
51
+ * @returns {string}
52
+ */
53
+ stringReverse (pString)
54
+ {
55
+ // TODO: Benchmark if there are faster ways we want to do this with all the newer JS stuff
56
+ // ... and if it will work with browserify in a clean way.
57
+ return pString.split('').reverse().join('');
58
+ }
59
+
60
+ /**
61
+ * Test if a string starts with a given substring.
62
+ *
63
+ * @param {*} pString
64
+ * @param {*} pSearchString
65
+ * @param {*} pStartIndex
66
+ * @returns {*}
67
+ */
68
+ stringStartsWith (pString, pSearchString, pStartIndex)
69
+ {
70
+ if (this._UseEngineStringStartsWith)
71
+ {
72
+ return pString.startsWith(pSearchString, pStartIndex);
73
+ }
74
+ else
75
+ {
76
+ return this.stringStartsWith_Polyfill.call(pString, pSearchString, pStartIndex);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Check if a string starts with a given substring. This is a safe polyfill for the ES6 string.startsWith() function.
82
+ *
83
+ * @param {*} pSearchString - The string to search for
84
+ * @param {*} pStartIndex - The index to start the search at
85
+ * @returns {boolean}
86
+ */
87
+ stringStartsWith_Polyfill (pSearchString, pStartIndex)
88
+ {
89
+ return this.slice(pStartIndex || 0, pSearchString.length) === pSearchString;
90
+ }
91
+
92
+ /**
93
+ * Test if a string starts with a given substring.
94
+ *
95
+ * @param {*} pString
96
+ * @param {*} pSearchString
97
+ * @param {*} pEndIndex
98
+ * @returns {*}
99
+ */
100
+ stringEndsWith (pString, pSearchString, pEndIndex)
101
+ {
102
+ if (this._UseEngineStringEndsWith)
103
+ {
104
+ return pString.endsWith(pSearchString, pEndIndex);
105
+ }
106
+ else
107
+ {
108
+ return this.stringEndsWith_Polyfill.call(pString, pSearchString, pEndIndex);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check if a string starts with a given substring. This is a safe polyfill for the ES6 string.startsWith() function.
114
+ *
115
+ * @param {*} pSearchString - The string to search for
116
+ * @param {*} pEndIndex - The index to end the search at
117
+ * @returns {boolean}
118
+ */
119
+ stringEndsWith_Polyfill (pSearchString, pEndIndex)
120
+ {
121
+ // This works much better than >= because
122
+ // it compensates for NaN:
123
+ if (!(pEndIndex < this.length))
124
+ {
125
+ pEndIndex = this.length;
126
+ }
127
+ else
128
+ {
129
+ pEndIndex |= 0; // round position
130
+ }
131
+ return this.substr(pEndIndex - pSearchString.length, pSearchString.length) === pSearchString;
132
+ }
133
+
134
+ /**
135
+ * Generate an insecure string hash. Not meant to be secure, just a quick way to generate a hash for a string. This is not a cryptographic hash. Additional warranty and disclaimer ... this is not for passwords!
136
+ *
137
+ * @param {string} pString
138
+ * @returns {string}
139
+ */
140
+ insecureStringHash (pString)
141
+ {
142
+ let tmpHash = 0;
143
+ let tmpStringLength = pString.length;
144
+ let tmpCharacterIndex = 0;
145
+
146
+ while (tmpCharacterIndex < tmpStringLength)
147
+ {
148
+ tmpHash = (tmpHash << 5) - tmpHash + pString.charCodeAt(tmpCharacterIndex++) | 0;
149
+ }
150
+
151
+ return `${this._Value_Prefix_StringHash}${tmpHash}`;
152
+ }
153
+
154
+ /**
155
+ * Clean wrapping characters if they exist consistently around the string. If they do not, the string is returned unchanged.
156
+ *
157
+ * @param {string} pWrapCharacter - The character expected as the wrapping character
158
+ * @param {string} pString - the string to clean
159
+ * @returns {string}
160
+ */
161
+ cleanEnclosureWrapCharacters (pWrapCharacter, pString)
162
+ {
163
+ // # Use case from ManyFest DSL:
164
+ //
165
+ // When a boxed property is passed in, it should have quotes of some
166
+ // kind around it.
167
+ //
168
+ // For instance:
169
+ // MyValues['Name']
170
+ // MyValues["Age"]
171
+ // MyValues[`Cost`]
172
+ //
173
+ // This function is necessary to remove the wrapping quotes before object
174
+ // resolution can occur.
175
+ if (pString.startsWith(pWrapCharacter) && pString.endsWith(pWrapCharacter))
176
+ {
177
+ return pString.substring(1, pString.length - 1);
178
+ }
179
+ else
180
+ {
181
+ return pString;
182
+ }
183
+ }
184
+
185
+ /**
186
+ *
187
+ * @param {*} pString
188
+ * @returns
189
+ */
190
+ cleanNonAlphaCharacters (pString)
191
+ {
192
+ if ((typeof(pString) == 'string') && (pString != ''))
193
+ {
194
+ return pString.replace(this._Regex_formatterCleanNonAlpha, this._Value_Clean_formatterCleanNonAlpha);
195
+ }
196
+ }
197
+
198
+
199
+ /*************************************************************************
200
+ * Number Formatting Functions
201
+ *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
202
+
203
+ /**
204
+ * Insert commas every 3 characters from the right. Used by formatterAddCommasToNumber().
205
+ *
206
+ * @param {*} pString
207
+ * @returns {*}
208
+ */
209
+ formatterInsertCommas (pString)
210
+ {
211
+ // Reverse, because it's easier to do things from the left, given arbitrary digit counts
212
+ let tmpReversed = this.stringReverse(pString);
213
+ // Add commas every three characters
214
+ let tmpReversedWithCommas = tmpReversed.match(this._Regex_formatterInsertCommas).join(',');
215
+ // Reverse again (back to normal direction)
216
+ return this.stringReverse(tmpReversedWithCommas);
217
+ }
218
+
219
+ processAddCommasToNumberRegex(pMatch, pSign, pZeros, pBefore, pDecimal, pAfter)
220
+ {
221
+ // If there was no decimal, the last capture grabs the final digit, so
222
+ // we have to put it back together with the 'before' substring
223
+ return pSign + (pDecimal ? this.formatterInsertCommas(pBefore) + pDecimal + pAfter : this.formatterInsertCommas(pBefore + pAfter));
224
+ }
225
+
226
+ /**
227
+ * Add Commas to a Number for readability.
228
+ *
229
+ * @param {*} pNumber
230
+ * @returns {string}
231
+ */
232
+ formatterAddCommasToNumber (pNumber)
233
+ {
234
+ // If the regex doesn't match, `replace` returns the string unmodified
235
+ return (pNumber.toString()).replace
236
+ (
237
+ this._Regex_formatterAddCommasToNumber,
238
+ this.processAddCommasToNumberRegex.bind(this)
239
+ );
240
+ }
241
+
242
+ /**
243
+ * This will take a number and format it as a dollar string. It will also add commas to the number. If the number is not a number, it will return '--'.
244
+ *
245
+ * @param {*} pValue
246
+ * @returns {string}
247
+ */
248
+ formatterDollars (pValue)
249
+ {
250
+ let tmpDollarAmount = parseFloat(pValue).toFixed(2);
251
+
252
+ if (isNaN(tmpDollarAmount))
253
+ {
254
+ // Try again and see if what was passed in was a dollars string.
255
+ if (typeof(pValue) == 'string')
256
+ {
257
+ // TODO: Better rounding function? This is a hack to get rid of the currency symbol and commas.
258
+ tmpDollarAmount = parseFloat(pValue.replace(this._Value_MoneySign_Currency,'').replace(this._Regex_formatterDollarsRemoveCommas,'')).toFixed(2);
259
+ }
260
+ // If we didn't get a number, return the "not a number" string.
261
+ if (isNaN(tmpDollarAmount))
262
+ {
263
+ return this._Value_NaN_Currency;
264
+ }
265
+ }
266
+
267
+ // TODO: Get locale data and use that for this stuff.
268
+ return `$${this.formatterAddCommasToNumber(tmpDollarAmount)}`;
269
+ }
270
+
271
+ /**
272
+ * Round a number to a certain number of digits. If the number is not a number, it will return 0. If no digits are specified, it will default to 2 significant digits.
273
+ *
274
+ * @param {*} pValue
275
+ * @param {number} pDigits
276
+ * @returns {string}
277
+ */
278
+ formatterRoundNumber (pValue, pDigits)
279
+ {
280
+ let tmpDigits = (typeof(pDigits) == 'undefined') ? 2 : pDigits;
281
+
282
+ let tmpValue = parseFloat(pValue).toFixed(tmpDigits);
283
+ if (isNaN(tmpValue))
284
+ {
285
+ let tmpZed = 0;
286
+ return tmpZed.toFixed(tmpDigits);
287
+ }
288
+ else
289
+ {
290
+ return tmpValue;
291
+ }
292
+ }
293
+
294
+
295
+ /**
296
+ * Generate a reapeating padding string to be appended before or after depending on
297
+ * which padding function it uses.
298
+ *
299
+ * @param {*} pString
300
+ * @param {number} pTargetLength
301
+ * @returns {string} pPadString
302
+ */
303
+ stringGeneratePaddingString(pString, pTargetLength, pPadString)
304
+ {
305
+ let tmpTargetLength = pTargetLength >> 0;
306
+ let tmpPadString = String((typeof pPadString !== 'undefined' ? pPadString : ' '));
307
+ if (pString.length > pTargetLength)
308
+ {
309
+ // No padding string if the source string is already longer than the target length, return an empty string
310
+ return '';
311
+ }
312
+ else
313
+ {
314
+ let tmpPadLength = pTargetLength - pString.length;
315
+ if (tmpPadLength > pPadString.length)
316
+ {
317
+ pPadString += pPadString.repeat(tmpTargetLength / pPadString.length);
318
+ }
319
+ return pPadString.slice(0,tmpPadLength);
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Pad the start of a string.
325
+ *
326
+ * @param {*} pString
327
+ * @param {number} pTargetLength
328
+ * @returns {string} pPadString
329
+ */
330
+ stringPadStart = function(pString, pTargetLength, pPadString)
331
+ {
332
+ let tmpString = pString.toString();
333
+ return this.stringGeneratePaddingString(tmpString, pTargetLength, pPadString) + tmpString;
334
+ }
335
+
336
+ /**
337
+ * Pad the end of a string.
338
+ *
339
+ * @param {*} pString
340
+ * @param {number} pTargetLength
341
+ * @returns {string} pPadString
342
+ */
343
+ stringPadEnd = function(pString, pTargetLength, pPadString)
344
+ {
345
+ let tmpString = pString.toString();
346
+ return tmpString + this.stringGeneratePaddingString(tmpString, pTargetLength, pPadString);
347
+ }
348
+
349
+ /*************************************************************************
350
+ * Time Formatting Functions (milliseconds)
351
+ *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
352
+
353
+ /**
354
+ * Format a time length in milliseconds into a human readable string.
355
+ * @param {number} pTimeSpan
356
+ * @returns {string} - HH:MM:SS.mmm
357
+ */
358
+ formatTimeSpan(pTimeSpan)
359
+ {
360
+ if (typeof(pTimeSpan) != 'number')
361
+ {
362
+ return '';
363
+ }
364
+
365
+ let tmpMs = parseInt(pTimeSpan%1000);
366
+ let tmpSeconds = parseInt((pTimeSpan/1000)%60);
367
+ let tmpMinutes = parseInt((pTimeSpan/(1000*60))%60);
368
+ let tmpHours = parseInt(pTimeSpan/(1000*60*60));
369
+
370
+ return `${this.stringPadStart(tmpHours,2,'0')}:${this.stringPadStart(tmpMinutes,2,'0')}:${this.stringPadStart(tmpSeconds,2,'0')}.${this.stringPadStart(tmpMs,3,'0')}`;
371
+ }
372
+
373
+ /**
374
+ * Format the time delta between two times in milliseconds into a human readable string.
375
+ *
376
+ * @param {number} pTimeStart
377
+ * @param {number} pTimeEnd
378
+ * @returns {string} - HH:MM:SS.mmm
379
+ */
380
+ formatTimeDelta(pTimeStart, pTimeEnd)
381
+ {
382
+ if ((typeof(pTimeStart) != 'number') || (typeof(pTimeEnd) != 'number'))
383
+ {
384
+ return '';
385
+ }
386
+
387
+ return this.formatTimeSpan(pTimeEnd-pTimeStart);
388
+ }
389
+
390
+ // THE FOLLOWING TERRIBLE FUNCTIONS ARE FOR QT / WKHTMLTOPDF when luxon and moment don't work so well
391
+ getMonthFromDate (pJavascriptDate)
392
+ {
393
+ var tmpMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
394
+ return tmpMonths[pJavascriptDate.getMonth()];
395
+ }
396
+
397
+ getMonthAbbreviatedFromDate (pJavascriptDate)
398
+ {
399
+ var tmpMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
400
+ return tmpMonths[pJavascriptDate.getMonth()];
401
+ }
402
+
403
+ formatSortableStringFromDate (pDate)
404
+ {
405
+ return pDate.getFullYear()+this.stringPadStart(pDate.getMonth(),2,'0')+this.stringPadStart(pDate.getDate(),2,'0');
406
+ }
407
+
408
+ /*************************************************************************
409
+ * String Tokenization Functions
410
+ *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
411
+
412
+ /**
413
+ * Return the string before the matched substring.
414
+ *
415
+ * If the substring is not found, the entire string is returned. This only deals with the *first* match.
416
+ *
417
+ * @param {string} pString
418
+ * @param {string} pMatch
419
+ * @returns {string}
420
+ */
421
+ stringBeforeMatch (pString, pMatch)
422
+ {
423
+ return pString.split(pMatch)[0];
424
+ }
425
+
426
+ /**
427
+ * Return the string after the matched substring.
428
+ *
429
+ * If the substring is not found, an empty string is returned. This only deals with the *first* match.
430
+ *
431
+ * @param {string} pString
432
+ * @param {string} pMatch
433
+ * @returns {string}
434
+ */
435
+ stringAfterMatch (pString, pMatch)
436
+ {
437
+ let tmpStringSplitLocation = pString.indexOf(pMatch);
438
+
439
+ if ((tmpStringSplitLocation < 0) || ((tmpStringSplitLocation + pMatch.length) >= pString.length))
440
+ {
441
+ return '';
442
+ }
443
+
444
+ return pString.substring(tmpStringSplitLocation + pMatch.length);
445
+ }
446
+
447
+ /**
448
+ * Count the number of enclosures in a string based on the start and end characters.
449
+ *
450
+ * If no start or end characters are specified, it will default to parentheses. If the string is not a string, it will return 0.
451
+ *
452
+ * @param {string} pString
453
+ * @param {string} pEnclosureStart
454
+ * @param {string} pEnclosureEnd
455
+ * @returns the count of full in the string
456
+ */
457
+ stringCountEnclosures (pString, pEnclosureStart, pEnclosureEnd)
458
+ {
459
+ let tmpString = (typeof(pString) == 'string') ? pString : '';
460
+ let tmpEnclosureStart = (typeof(pEnclosureStart) == 'string') ? pEnclosureStart : '(';
461
+ let tmpEnclosureEnd = (typeof(pEnclosureEnd) == 'string') ? pEnclosureEnd : ')';
462
+
463
+ let tmpEnclosureCount = 0;
464
+ let tmpEnclosureDepth = 0;
465
+ for (let i = 0; i < tmpString.length; i++)
466
+ {
467
+ // This is the start of an enclosure
468
+ if (tmpString[i] == tmpEnclosureStart)
469
+ {
470
+ if (tmpEnclosureDepth == 0)
471
+ {
472
+ tmpEnclosureCount++;
473
+ }
474
+ tmpEnclosureDepth++;
475
+ }
476
+ else if (tmpString[i] == tmpEnclosureEnd)
477
+ {
478
+ tmpEnclosureDepth--;
479
+ }
480
+ }
481
+
482
+ return tmpEnclosureCount;
483
+ }
484
+
485
+
486
+ /**
487
+ * Get the value of the enclosure at the specified index.
488
+ *
489
+ * If the index is not a number, it will default to 0. If the string is not a string, it will return an empty string. If the enclosure is not found, it will return an empty string. If the enclosure
490
+ *
491
+ * @param {string} pString
492
+ * @param {number} pEnclosureIndexToGet
493
+ * @param {string} pEnclosureStart
494
+ * @param {string}} pEnclosureEnd
495
+ * @returns {string}
496
+ */
497
+ stringGetEnclosureValueByIndex (pString, pEnclosureIndexToGet, pEnclosureStart, pEnclosureEnd)
498
+ {
499
+ let tmpString = (typeof(pString) == 'string') ? pString : '';
500
+ let tmpEnclosureIndexToGet = (typeof(pEnclosureIndexToGet) == 'number') ? pEnclosureIndexToGet : 0;
501
+ let tmpEnclosureStart = (typeof(pEnclosureStart) == 'string') ? pEnclosureStart : '(';
502
+ let tmpEnclosureEnd = (typeof(pEnclosureEnd) == 'string') ? pEnclosureEnd : ')';
503
+
504
+ let tmpEnclosureCount = 0;
505
+ let tmpEnclosureDepth = 0;
506
+
507
+ let tmpMatchedEnclosureIndex = false;
508
+ let tmpEnclosedValueStartIndex = 0;
509
+ let tmpEnclosedValueEndIndex = 0;
510
+
511
+ for (let i = 0; i < tmpString.length; i++)
512
+ {
513
+ // This is the start of an enclosure
514
+ if (tmpString[i] == tmpEnclosureStart)
515
+ {
516
+ tmpEnclosureDepth++;
517
+
518
+ // Only count enclosures at depth 1, but still this parses both pairs of all of them.
519
+ if (tmpEnclosureDepth == 1)
520
+ {
521
+ tmpEnclosureCount++;
522
+ if (tmpEnclosureIndexToGet == (tmpEnclosureCount - 1))
523
+ {
524
+ // This is the start of *the* enclosure
525
+ tmpMatchedEnclosureIndex = true;
526
+ tmpEnclosedValueStartIndex = i;
527
+ }
528
+ }
529
+ }
530
+ // This is the end of an enclosure
531
+ else if (tmpString[i] == tmpEnclosureEnd)
532
+ {
533
+ tmpEnclosureDepth--;
534
+
535
+ // Again, only count enclosures at depth 1, but still this parses both pairs of all of them.
536
+ if ((tmpEnclosureDepth == 0) &&
537
+ tmpMatchedEnclosureIndex &&
538
+ (tmpEnclosedValueEndIndex <= tmpEnclosedValueStartIndex))
539
+ {
540
+ tmpEnclosedValueEndIndex = i;
541
+ tmpMatchedEnclosureIndex = false;
542
+ }
543
+ }
544
+ }
545
+
546
+ if (tmpEnclosureCount <= tmpEnclosureIndexToGet)
547
+ {
548
+ // Return an empty string if the enclosure is not found
549
+ return '';
550
+ }
551
+
552
+ if ((tmpEnclosedValueEndIndex > 0) && (tmpEnclosedValueEndIndex > tmpEnclosedValueStartIndex))
553
+ {
554
+ return tmpString.substring(tmpEnclosedValueStartIndex+1, tmpEnclosedValueEndIndex);
555
+ }
556
+ else
557
+ {
558
+ return tmpString.substring(tmpEnclosedValueStartIndex+1);
559
+ }
560
+ }
561
+
562
+
563
+ /**
564
+ * Remove an enclosure from a string based on the index of the enclosure.
565
+ *
566
+ * @param {string} pString
567
+ * @param {number} pEnclosureIndexToRemove
568
+ * @param {number} pEnclosureStart
569
+ * @param {number} pEnclosureEnd
570
+ * @returns {string}
571
+ */
572
+ stringRemoveEnclosureByIndex (pString, pEnclosureIndexToRemove, pEnclosureStart, pEnclosureEnd)
573
+ {
574
+ let tmpString = (typeof(pString) == 'string') ? pString : '';
575
+ let tmpEnclosureIndexToRemove = (typeof(pEnclosureIndexToRemove) == 'number') ? pEnclosureIndexToRemove : 0;
576
+ let tmpEnclosureStart = (typeof(pEnclosureStart) == 'string') ? pEnclosureStart : '(';
577
+ let tmpEnclosureEnd = (typeof(pEnclosureEnd) == 'string') ? pEnclosureEnd : ')';
578
+
579
+ let tmpEnclosureCount = 0;
580
+ let tmpEnclosureDepth = 0;
581
+
582
+ let tmpMatchedEnclosureIndex = false;
583
+ let tmpEnclosureStartIndex = 0;
584
+ let tmpEnclosureEndIndex = 0;
585
+
586
+ for (let i = 0; i < tmpString.length; i++)
587
+ {
588
+ // This is the start of an enclosure
589
+ if (tmpString[i] == tmpEnclosureStart)
590
+ {
591
+ tmpEnclosureDepth++;
592
+
593
+ if (tmpEnclosureDepth == 1)
594
+ {
595
+ tmpEnclosureCount++;
596
+ if (tmpEnclosureIndexToRemove == (tmpEnclosureCount - 1))
597
+ {
598
+ tmpMatchedEnclosureIndex = true;
599
+ tmpEnclosureStartIndex = i;
600
+ }
601
+ }
602
+ }
603
+ else if (tmpString[i] == tmpEnclosureEnd)
604
+ {
605
+ tmpEnclosureDepth--;
606
+
607
+ if ((tmpEnclosureDepth == 0) &&
608
+ tmpMatchedEnclosureIndex &&
609
+ (tmpEnclosureEndIndex <= tmpEnclosureStartIndex))
610
+ {
611
+ tmpEnclosureEndIndex = i;
612
+ tmpMatchedEnclosureIndex = false;
613
+ }
614
+ }
615
+ }
616
+
617
+ if (tmpEnclosureCount <= tmpEnclosureIndexToRemove)
618
+ {
619
+ return tmpString;
620
+ }
621
+
622
+ let tmpReturnString = '';
623
+
624
+ if (tmpEnclosureStartIndex > 1)
625
+ {
626
+ tmpReturnString = tmpString.substring(0, tmpEnclosureStartIndex);
627
+ }
628
+
629
+ if ((tmpString.length > (tmpEnclosureEndIndex + 1)) && (tmpEnclosureEndIndex > tmpEnclosureStartIndex))
630
+ {
631
+ tmpReturnString += tmpString.substring(tmpEnclosureEndIndex+1);
632
+ }
633
+
634
+ return tmpReturnString;
635
+ }
636
+ }
637
+
638
+ module.exports = DataFormat;