fable 3.0.31 → 3.0.33

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