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/README.md +33 -63
- package/debug/Harness.js +16 -2
- package/dist/fable.compatible.js +314 -272
- package/dist/fable.compatible.min.js +21 -21
- package/dist/fable.compatible.min.js.map +1 -1
- package/dist/fable.js +280 -238
- package/dist/fable.min.js +20 -20
- package/dist/fable.min.js.map +1 -1
- package/package.json +1 -1
- package/source/Fable-ServiceManager.js +7 -0
- package/source/Fable.js +3 -5
- package/source/services/Fable-Service-DataFormat.js +638 -0
- package/source/services/Fable-Service-RestClient.js +81 -3
- package/source/services/Fable-Service-Utility.js +70 -0
- package/test/DataFormat-StringDateFormatting_tests.js +109 -0
- package/test/{FableDataArithmatic_tests.js → DataFormat-StringManipulation_tests.js} +88 -46
- package/test/DataFormat-StringNumberFormatting_tests.js +110 -0
- package/test/DataFormat-StringTokenization_tests.js +167 -0
- package/test/RestClient_test.js +54 -0
- package/test/{FableUtility_tests.js → Utility_tests.js} +13 -0
- /package/test/{FableOperations_tests.js → FableOperation_tests.js} +0 -0
- /package/test/{FableMetaTemplating_tests.js → MetaTemplating_tests.js} +0 -0
package/package.json
CHANGED
|
@@ -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
|
|
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.
|
|
57
|
-
this.
|
|
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;
|