@aidc-toolkit/utility 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +32 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/runConfigurations/Test_all.xml +12 -0
- package/.idea/runConfigurations/Test_character_set.xml +12 -0
- package/.idea/runConfigurations/Test_iteration.xml +12 -0
- package/.idea/runConfigurations/Test_record.xml +12 -0
- package/.idea/runConfigurations/Test_regular_expression.xml +12 -0
- package/.idea/runConfigurations/Test_transformer.xml +12 -0
- package/.idea/runConfigurations/build.xml +12 -0
- package/.idea/runConfigurations/eslint.xml +12 -0
- package/.idea/utility.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +201 -0
- package/README.md +12 -0
- package/eslint.config.js +15 -0
- package/package.json +42 -0
- package/src/character_set.ts +766 -0
- package/src/index.ts +6 -0
- package/src/iteration.ts +343 -0
- package/src/locale/en/locale_strings.ts +30 -0
- package/src/locale/i18n.ts +8 -0
- package/src/locale/i18next.d.ts +10 -0
- package/src/record.ts +64 -0
- package/src/reg_exp.ts +58 -0
- package/src/string.ts +22 -0
- package/src/transformer.ts +654 -0
- package/test/character_set.test.ts +170 -0
- package/test/iteration.test.ts +282 -0
- package/test/record.test.ts +41 -0
- package/test/reg_exp.test.ts +43 -0
- package/test/transformer.test.ts +143 -0
- package/tsconfig.json +3 -0
- package/typedoc.json +10 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import { IterationHelper, type IterationSource } from "./iteration.js";
|
|
2
|
+
import i18next, { utilityNS } from "./locale/i18n.js";
|
|
3
|
+
import { RegExpValidator } from "./reg_exp.js";
|
|
4
|
+
import type { StringValidation, StringValidator } from "./string.js";
|
|
5
|
+
import { type TransformationCallback, Transformer } from "./transformer.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Exclusion options for validating and creating strings based on character sets.
|
|
9
|
+
*/
|
|
10
|
+
export enum Exclusion {
|
|
11
|
+
/**
|
|
12
|
+
* No strings excluded.
|
|
13
|
+
*/
|
|
14
|
+
None,
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Strings that start with zero ('0') excluded.
|
|
18
|
+
*/
|
|
19
|
+
FirstZero,
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Strings that are all-numeric (e.g., "123456") excluded.
|
|
23
|
+
*/
|
|
24
|
+
AllNumeric
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Character set validation parameters.
|
|
29
|
+
*/
|
|
30
|
+
export interface CharacterSetValidation extends StringValidation {
|
|
31
|
+
/**
|
|
32
|
+
* Minimum length. If defined and the string is less than this length, an exception is thrown.
|
|
33
|
+
*/
|
|
34
|
+
minimumLength?: number | undefined;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Maximum length. If defined and the string is greater than this length, an exception is thrown.
|
|
38
|
+
*/
|
|
39
|
+
maximumLength?: number | undefined;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Exclusion from the string. If defined and the string is within the exclusion range, an exception is thrown.
|
|
43
|
+
*/
|
|
44
|
+
exclusion?: Exclusion | undefined;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Position offset within a larger string. Strings are sometimes composed of multiple substrings; this parameter
|
|
48
|
+
* ensures that the exception notes the proper position in the string.
|
|
49
|
+
*/
|
|
50
|
+
positionOffset?: number | undefined;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Name of component, typically but not exclusively within a larger string. This parameter ensure that the
|
|
54
|
+
* exception notes the component that triggered it. Value may be a string or a callback that returns a string, the
|
|
55
|
+
* latter allowing for localization changes.
|
|
56
|
+
*/
|
|
57
|
+
component?: string | (() => string) | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Character set validator. Validates a string against a specified character set.
|
|
62
|
+
*/
|
|
63
|
+
export class CharacterSetValidator implements StringValidator {
|
|
64
|
+
private static readonly NOT_ALL_NUMERIC_VALIDATOR = new class extends RegExpValidator {
|
|
65
|
+
protected override createErrorMessage(_s: string): string {
|
|
66
|
+
return i18next.t("CharacterSetValidator.stringMustNotBeAllNumeric", {
|
|
67
|
+
ns: utilityNS
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}(/\D/);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Character set.
|
|
74
|
+
*/
|
|
75
|
+
private readonly _characterSet: readonly string[];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Character set map, mapping each character in the character set to its index such that
|
|
79
|
+
* `_characterSetMap.get(_characterSet[index]) === index`.
|
|
80
|
+
*/
|
|
81
|
+
private readonly _characterSetMap: Map<string, number>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Exclusions supported by the character set.
|
|
85
|
+
*/
|
|
86
|
+
private readonly _exclusionSupport: readonly Exclusion[];
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Constructor.
|
|
90
|
+
*
|
|
91
|
+
* @param characterSet
|
|
92
|
+
* Character set. Each element is a single-character string, unique within the array, that defines the character
|
|
93
|
+
* set.
|
|
94
|
+
*
|
|
95
|
+
* @param exclusionSupport
|
|
96
|
+
* Exclusions supported by the character set. All character sets implicitly support {@link Exclusion.None}.
|
|
97
|
+
*/
|
|
98
|
+
constructor(characterSet: readonly string[], ...exclusionSupport: readonly Exclusion[]) {
|
|
99
|
+
this._characterSet = characterSet;
|
|
100
|
+
|
|
101
|
+
this._characterSetMap = new Map(characterSet.map((c, index) => [c, index]));
|
|
102
|
+
|
|
103
|
+
this._exclusionSupport = exclusionSupport;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the character set.
|
|
108
|
+
*/
|
|
109
|
+
get characterSet(): readonly string[] {
|
|
110
|
+
return this._characterSet;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the character set size.
|
|
115
|
+
*/
|
|
116
|
+
get characterSetSize(): number {
|
|
117
|
+
return this._characterSet.length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the exclusions supported by the character set.
|
|
122
|
+
*/
|
|
123
|
+
get exclusionSupport(): readonly Exclusion[] {
|
|
124
|
+
return this._exclusionSupport;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get the character at an index.
|
|
129
|
+
*
|
|
130
|
+
* @param index
|
|
131
|
+
* Index into the character set.
|
|
132
|
+
*
|
|
133
|
+
* @returns
|
|
134
|
+
* Character at the index.
|
|
135
|
+
*/
|
|
136
|
+
character(index: number): string {
|
|
137
|
+
return this._characterSet[index];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get the index for a character.
|
|
142
|
+
*
|
|
143
|
+
* @param c
|
|
144
|
+
* Character.
|
|
145
|
+
*
|
|
146
|
+
* @returns
|
|
147
|
+
* Index for the character or undefined if the character is not in the character set.
|
|
148
|
+
*/
|
|
149
|
+
characterIndex(c: string): number | undefined {
|
|
150
|
+
return this._characterSetMap.get(c);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get the indexes for all characters in a string.
|
|
155
|
+
*
|
|
156
|
+
* @param s
|
|
157
|
+
* String.
|
|
158
|
+
*
|
|
159
|
+
* @returns
|
|
160
|
+
* Array of indexes for each character or undefined if the character is not in the character set.
|
|
161
|
+
*/
|
|
162
|
+
characterIndexes(s: string): ReadonlyArray<number | undefined> {
|
|
163
|
+
return [...s].map(c => this._characterSetMap.get(c));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Convert a component definition to a string or undefined. Checks the type of the component and makes the callback
|
|
168
|
+
* if required.
|
|
169
|
+
*
|
|
170
|
+
* @param component
|
|
171
|
+
* Component definition as a string, callback, or undefined.
|
|
172
|
+
*
|
|
173
|
+
* @returns
|
|
174
|
+
* Component as a string or undefined.
|
|
175
|
+
*/
|
|
176
|
+
private static componentToString(component: string | (() => string) | undefined): string | undefined {
|
|
177
|
+
return component === undefined || typeof component === "string" ? component : component();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Validate that an exclusion is supported. If not, an exception is thrown.
|
|
182
|
+
*
|
|
183
|
+
* @param exclusion
|
|
184
|
+
* Exclusion.
|
|
185
|
+
*
|
|
186
|
+
* @throws RangeError
|
|
187
|
+
*/
|
|
188
|
+
protected validateExclusion(exclusion: Exclusion): void {
|
|
189
|
+
if (exclusion !== Exclusion.None && !this._exclusionSupport.includes(exclusion)) {
|
|
190
|
+
throw new RangeError(i18next.t("CharacterSetValidator.exclusionNotSupported", {
|
|
191
|
+
ns: utilityNS,
|
|
192
|
+
exclusion
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Validate a string. If the string violates the character set or any of the character set validation parameters, an
|
|
199
|
+
* exception is thrown.
|
|
200
|
+
*
|
|
201
|
+
* @param s
|
|
202
|
+
* String.
|
|
203
|
+
*
|
|
204
|
+
* @param validation
|
|
205
|
+
* Character set validation parameters.
|
|
206
|
+
*
|
|
207
|
+
* @throws RangeError
|
|
208
|
+
*/
|
|
209
|
+
validate(s: string, validation?: CharacterSetValidation): void {
|
|
210
|
+
const length = s.length;
|
|
211
|
+
|
|
212
|
+
const minimumLength = validation?.minimumLength;
|
|
213
|
+
const maximumLength = validation?.maximumLength;
|
|
214
|
+
|
|
215
|
+
if (minimumLength !== undefined && length < minimumLength) {
|
|
216
|
+
let errorMessage: string;
|
|
217
|
+
|
|
218
|
+
if (maximumLength !== undefined && maximumLength === minimumLength) {
|
|
219
|
+
errorMessage = i18next.t(validation?.component === undefined ? "CharacterSetValidator.lengthMustBeEqualTo" : "CharacterSetValidator.lengthOfComponentMustBeEqualTo", {
|
|
220
|
+
ns: utilityNS,
|
|
221
|
+
component: CharacterSetValidator.componentToString(validation?.component),
|
|
222
|
+
length,
|
|
223
|
+
exactLength: minimumLength
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
errorMessage = i18next.t(validation?.component === undefined ? "CharacterSetValidator.lengthMustBeGreaterThanOrEqualTo" : "CharacterSetValidator.lengthOfComponentMustBeGreaterThanOrEqualTo", {
|
|
227
|
+
ns: utilityNS,
|
|
228
|
+
component: CharacterSetValidator.componentToString(validation?.component),
|
|
229
|
+
length,
|
|
230
|
+
minimumLength
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
throw new RangeError(errorMessage);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (maximumLength !== undefined && length > maximumLength) {
|
|
238
|
+
throw new RangeError(i18next.t(validation?.component === undefined ? "CharacterSetValidator.lengthMustBeLessThanOrEqualTo" : "CharacterSetValidator.lengthOfComponentMustBeLessThanOrEqualTo", {
|
|
239
|
+
ns: utilityNS,
|
|
240
|
+
component: CharacterSetValidator.componentToString(validation?.component),
|
|
241
|
+
length,
|
|
242
|
+
maximumLength
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Find the index of the first character that is not in the character set.
|
|
247
|
+
const index = this.characterIndexes(s).findIndex(characterIndex => characterIndex === undefined);
|
|
248
|
+
|
|
249
|
+
if (index !== -1) {
|
|
250
|
+
throw new RangeError(i18next.t(validation?.component === undefined ? "CharacterSetValidator.invalidCharacterAtPosition" : "CharacterSetValidator.invalidCharacterAtPositionOfComponent", {
|
|
251
|
+
ns: utilityNS,
|
|
252
|
+
component: CharacterSetValidator.componentToString(validation?.component),
|
|
253
|
+
c: s.charAt(index),
|
|
254
|
+
position: index + (validation?.positionOffset ?? 0) + 1
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (validation?.exclusion !== undefined) {
|
|
259
|
+
this.validateExclusion(validation.exclusion);
|
|
260
|
+
|
|
261
|
+
switch (validation.exclusion) {
|
|
262
|
+
case Exclusion.FirstZero:
|
|
263
|
+
if (s.startsWith("0")) {
|
|
264
|
+
throw new RangeError(i18next.t(validation.component === undefined ? "CharacterSetValidator.invalidCharacterAtPosition" : "CharacterSetValidator.invalidCharacterAtPositionOfComponent", {
|
|
265
|
+
ns: utilityNS,
|
|
266
|
+
component: CharacterSetValidator.componentToString(validation.component),
|
|
267
|
+
c: "0",
|
|
268
|
+
position: (validation.positionOffset ?? 0) + 1
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case Exclusion.AllNumeric:
|
|
274
|
+
CharacterSetValidator.NOT_ALL_NUMERIC_VALIDATOR.validate(s);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creation callback, used to convert created string to its final value.
|
|
283
|
+
*
|
|
284
|
+
* @param s
|
|
285
|
+
* Created string.
|
|
286
|
+
*
|
|
287
|
+
* @param index
|
|
288
|
+
* Index in sequence creation (0 for single creation).
|
|
289
|
+
*
|
|
290
|
+
* @returns
|
|
291
|
+
* Final value.
|
|
292
|
+
*/
|
|
293
|
+
export type CreationCallback = (s: string, index: number) => string;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Character set creator. Maps numeric values to strings using the character set as digits.
|
|
297
|
+
*/
|
|
298
|
+
export class CharacterSetCreator extends CharacterSetValidator {
|
|
299
|
+
/**
|
|
300
|
+
* Maximum string length supported.
|
|
301
|
+
*/
|
|
302
|
+
static readonly MAXIMUM_STRING_LENGTH = 40;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Powers of 10 from 1 (`10**0`) to `10**MAXIMUM_STRING_LENGTH`.
|
|
306
|
+
*/
|
|
307
|
+
private static readonly _powersOf10: readonly bigint[] = CharacterSetCreator.createPowersOf(10);
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create powers of a given base from 1 (`base**0`) to `base**MAXIMUM_STRING_LENGTH`.
|
|
311
|
+
*
|
|
312
|
+
* @param base
|
|
313
|
+
* Number base.
|
|
314
|
+
*
|
|
315
|
+
* @returns
|
|
316
|
+
* Array of powers of base.
|
|
317
|
+
*/
|
|
318
|
+
private static createPowersOf(base: number): readonly bigint[] {
|
|
319
|
+
const powersOf = new Array<bigint>(this.MAXIMUM_STRING_LENGTH + 1);
|
|
320
|
+
|
|
321
|
+
const baseN = BigInt(base);
|
|
322
|
+
|
|
323
|
+
for (let index = 0, powerOf = 1n; index <= this.MAXIMUM_STRING_LENGTH; index++, powerOf *= baseN) {
|
|
324
|
+
powersOf[index] = powerOf;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return powersOf;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get a power of 10.
|
|
332
|
+
*
|
|
333
|
+
* @param power
|
|
334
|
+
* Power.
|
|
335
|
+
*
|
|
336
|
+
* @returns
|
|
337
|
+
* `10**power`.
|
|
338
|
+
*/
|
|
339
|
+
static powerOf10(power: number): bigint {
|
|
340
|
+
return this._powersOf10[power];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Character set size as big integer, cached for performance purposes.
|
|
345
|
+
*/
|
|
346
|
+
private readonly _characterSetSizeN: bigint;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Character set size minus 1 as big integer, cached for performance purposes.
|
|
350
|
+
*/
|
|
351
|
+
private readonly _characterSetSizeMinusOneN: bigint;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Domains for every length for every supported {@link Exclusion}.
|
|
355
|
+
*/
|
|
356
|
+
private readonly _exclusionDomains: ReadonlyArray<readonly bigint[] | undefined>;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Values that would generate all zeros in the created string.
|
|
360
|
+
*/
|
|
361
|
+
private readonly _allZerosValues: readonly bigint[] | undefined;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Constructor.
|
|
365
|
+
*
|
|
366
|
+
* @param characterSet
|
|
367
|
+
* Character set. Each element is a single-character string, unique within the array, that defines the character
|
|
368
|
+
* set.
|
|
369
|
+
*
|
|
370
|
+
* @param exclusionSupport
|
|
371
|
+
* Exclusions supported by the character set. All character sets implicitly support {@link Exclusion.None}.
|
|
372
|
+
*/
|
|
373
|
+
constructor(characterSet: readonly string[], ...exclusionSupport: readonly Exclusion[]) {
|
|
374
|
+
super(characterSet, ...exclusionSupport);
|
|
375
|
+
|
|
376
|
+
this._characterSetSizeN = BigInt(this.characterSetSize);
|
|
377
|
+
this._characterSetSizeMinusOneN = BigInt(this.characterSetSize - 1);
|
|
378
|
+
|
|
379
|
+
const exclusionNoneDomains = CharacterSetCreator.createPowersOf(this.characterSetSize);
|
|
380
|
+
|
|
381
|
+
let exclusionFirstZeroDomains: bigint[] | undefined;
|
|
382
|
+
|
|
383
|
+
if (exclusionSupport.includes(Exclusion.FirstZero)) {
|
|
384
|
+
exclusionFirstZeroDomains = new Array<bigint>(CharacterSetCreator.MAXIMUM_STRING_LENGTH + 1);
|
|
385
|
+
|
|
386
|
+
// Exclusion of first zero mathematically prohibits length of 0.
|
|
387
|
+
exclusionFirstZeroDomains[0] = 0n;
|
|
388
|
+
|
|
389
|
+
for (let index = 1; index <= CharacterSetCreator.MAXIMUM_STRING_LENGTH; index++) {
|
|
390
|
+
// Domain excludes zero as the first character and so works with previous exclusion none domain.
|
|
391
|
+
exclusionFirstZeroDomains[index] = this._characterSetSizeMinusOneN * exclusionNoneDomains[index - 1];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let exclusionAllNumericDomains: bigint[] | undefined;
|
|
396
|
+
|
|
397
|
+
if (exclusionSupport.includes(Exclusion.AllNumeric)) {
|
|
398
|
+
exclusionAllNumericDomains = new Array<bigint>(CharacterSetCreator.MAXIMUM_STRING_LENGTH + 1);
|
|
399
|
+
|
|
400
|
+
// Zero index is the all-zero value for a single-character string.
|
|
401
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
402
|
+
const zeroIndex = BigInt(this.characterIndex("0")!);
|
|
403
|
+
|
|
404
|
+
const allZerosValues = new Array<bigint>(CharacterSetCreator.MAXIMUM_STRING_LENGTH + 1);
|
|
405
|
+
let allZerosValue = 0n;
|
|
406
|
+
|
|
407
|
+
// Each all-zero value is the previous all-zero value multiplied by the character set size plus the zero index.
|
|
408
|
+
for (let index = 0; index <= CharacterSetCreator.MAXIMUM_STRING_LENGTH; index++) {
|
|
409
|
+
// Domain excludes the number of permutations that would result in an all-numeric string.
|
|
410
|
+
exclusionAllNumericDomains[index] = exclusionNoneDomains[index] - CharacterSetCreator.powerOf10(index);
|
|
411
|
+
|
|
412
|
+
allZerosValues[index] = allZerosValue;
|
|
413
|
+
|
|
414
|
+
allZerosValue = allZerosValue * this._characterSetSizeN + zeroIndex;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this._allZerosValues = allZerosValues;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this._exclusionDomains = [
|
|
421
|
+
exclusionNoneDomains,
|
|
422
|
+
exclusionFirstZeroDomains,
|
|
423
|
+
exclusionAllNumericDomains
|
|
424
|
+
];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get a power of character set size.
|
|
429
|
+
*
|
|
430
|
+
* @param power
|
|
431
|
+
* Power.
|
|
432
|
+
*
|
|
433
|
+
* @returns
|
|
434
|
+
* `characterSetSize**power`.
|
|
435
|
+
*/
|
|
436
|
+
private powerOfSize(power: number): bigint {
|
|
437
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
438
|
+
return this._exclusionDomains[Exclusion.None]![power];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Determine the shift required to skip all all-numeric strings up to the value.
|
|
443
|
+
*
|
|
444
|
+
* @param shiftForward
|
|
445
|
+
* True to shift forward (value to string), false to shift backward (string to value).
|
|
446
|
+
*
|
|
447
|
+
* @param length
|
|
448
|
+
* Length of string for which to get the all-numeric shift.
|
|
449
|
+
*
|
|
450
|
+
* @param value
|
|
451
|
+
* Value for which to get the all-numeric shift.
|
|
452
|
+
*
|
|
453
|
+
* @returns
|
|
454
|
+
* Shift required to skip all all-numeric strings.
|
|
455
|
+
*
|
|
456
|
+
* @throws RangeError
|
|
457
|
+
*/
|
|
458
|
+
private allNumericShift(shiftForward: boolean, length: number, value: bigint): bigint {
|
|
459
|
+
let shift: bigint;
|
|
460
|
+
|
|
461
|
+
if (length === 0) {
|
|
462
|
+
if (!shiftForward && value < 10n) {
|
|
463
|
+
// If calculation gets this far, string is all-numeric.
|
|
464
|
+
throw new RangeError(i18next.t("CharacterSetValidator.stringMustNotBeAllNumeric", {
|
|
465
|
+
ns: utilityNS
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Now dealing with individual characters; shift by 10 to skip numeric characters.
|
|
470
|
+
shift = 10n;
|
|
471
|
+
} else {
|
|
472
|
+
const powerOfSize = this.powerOfSize(length);
|
|
473
|
+
const powerOf10 = CharacterSetCreator.powerOf10(length);
|
|
474
|
+
|
|
475
|
+
// Calculate the gap to the next numeric string of equal length with incremental first character.
|
|
476
|
+
const gap = shiftForward ? powerOfSize - powerOf10 : powerOfSize;
|
|
477
|
+
|
|
478
|
+
// Determine the number of gaps remaining in the value.
|
|
479
|
+
const gaps = value / gap;
|
|
480
|
+
|
|
481
|
+
if (gaps >= 10n) {
|
|
482
|
+
// Shift is the next power of 10.
|
|
483
|
+
shift = CharacterSetCreator.powerOf10(length + 1);
|
|
484
|
+
} else {
|
|
485
|
+
// Shift is the number of gaps times the current power of 10 plus the shift for the next length down with value adjusted by the number of gaps times the gap.
|
|
486
|
+
shift = gaps * powerOf10 + this.allNumericShift(shiftForward, length - 1, value - gaps * gap);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return shift;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Validate that a length is less than or equal to {@link MAXIMUM_STRING_LENGTH}. If not, an exception is thrown.
|
|
495
|
+
*
|
|
496
|
+
* @param length
|
|
497
|
+
* Length.
|
|
498
|
+
*
|
|
499
|
+
* @throws RangeError
|
|
500
|
+
*/
|
|
501
|
+
private validateLength(length: number): void {
|
|
502
|
+
if (length > CharacterSetCreator.MAXIMUM_STRING_LENGTH) {
|
|
503
|
+
throw new RangeError(i18next.t("CharacterSetValidator.lengthMustBeLessThanOrEqualTo", {
|
|
504
|
+
ns: utilityNS,
|
|
505
|
+
length,
|
|
506
|
+
maximumLength: CharacterSetCreator.MAXIMUM_STRING_LENGTH
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Do the work for the creation methods. Undocumented parameters are as defined in the public methods.
|
|
513
|
+
*
|
|
514
|
+
* @template T
|
|
515
|
+
* Type defined by creation callback to do the work of creation (string or string[]).
|
|
516
|
+
*
|
|
517
|
+
* @param length
|
|
518
|
+
* See public methods.
|
|
519
|
+
*
|
|
520
|
+
* @param exclusion
|
|
521
|
+
* See public methods.
|
|
522
|
+
*
|
|
523
|
+
* @param tweak
|
|
524
|
+
* See public methods.
|
|
525
|
+
*
|
|
526
|
+
* @param creationCallback
|
|
527
|
+
* See public methods.
|
|
528
|
+
*
|
|
529
|
+
* @param createCallback
|
|
530
|
+
* Callback to do the work of creation, whether for a single value or a sequence. Called with the appropriate
|
|
531
|
+
* transformer and a transformation callback method to map individual transformed values to strings.
|
|
532
|
+
*
|
|
533
|
+
* @returns
|
|
534
|
+
* Created string or iterable iterator over created strings.
|
|
535
|
+
*/
|
|
536
|
+
private doCreate<T>(length: number, exclusion: Exclusion, tweak: number | bigint | undefined, creationCallback: CreationCallback | undefined, createCallback: (transformer: Transformer, transformationCallback: TransformationCallback<string>) => T): T {
|
|
537
|
+
this.validateLength(length);
|
|
538
|
+
this.validateExclusion(exclusion);
|
|
539
|
+
|
|
540
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
541
|
+
const allZerosValue = exclusion === Exclusion.AllNumeric ? this._allZerosValues![length] : undefined;
|
|
542
|
+
|
|
543
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
544
|
+
const transformer = Transformer.get(this._exclusionDomains[exclusion]![length], tweak);
|
|
545
|
+
|
|
546
|
+
return createCallback(transformer, (transformedValue, index) => {
|
|
547
|
+
let s = "";
|
|
548
|
+
|
|
549
|
+
// Empty string is valid.
|
|
550
|
+
if (length !== 0) {
|
|
551
|
+
let convertValue = transformedValue;
|
|
552
|
+
|
|
553
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
554
|
+
if (exclusion === Exclusion.AllNumeric && convertValue >= allZerosValue!) {
|
|
555
|
+
// Value to convert is shifted by the number of all-numeric strings that occur at or prior to it.
|
|
556
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
557
|
+
convertValue = convertValue + this.allNumericShift(true, length, convertValue - allZerosValue!);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Build string from right to left excluding the first character.
|
|
561
|
+
for (let position = length - 1; position > 0; position--) {
|
|
562
|
+
const nextConvertValue = convertValue / this._characterSetSizeN;
|
|
563
|
+
|
|
564
|
+
// First step is effectively a modulus calculation.
|
|
565
|
+
s = this.character(Number(convertValue - nextConvertValue * this._characterSetSizeN)) + s;
|
|
566
|
+
|
|
567
|
+
convertValue = nextConvertValue;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Zero is first in the character set for those that support excluding first zero.
|
|
571
|
+
s = this.character(exclusion === Exclusion.FirstZero ? Number(convertValue % this._characterSetSizeMinusOneN) + 1 : Number(convertValue % this._characterSetSizeN)) + s;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return creationCallback !== undefined ? creationCallback(s, index) : s;
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Create a string by mapping a value to the equivalent characters in the character set across the length of the
|
|
580
|
+
* string.
|
|
581
|
+
*
|
|
582
|
+
* @param length
|
|
583
|
+
* Required string length.
|
|
584
|
+
*
|
|
585
|
+
* @param value
|
|
586
|
+
* Numeric value of the string.
|
|
587
|
+
*
|
|
588
|
+
* @param exclusion
|
|
589
|
+
* Strings to be excluded from the range of outputs. See {@link Exclusion} for possible values and their meaning.
|
|
590
|
+
*
|
|
591
|
+
* @param tweak
|
|
592
|
+
* If provided, the numerical value of the string is "tweaked" using an {@link EncryptionTransformer | encryption
|
|
593
|
+
* transformer}.
|
|
594
|
+
*
|
|
595
|
+
* @param creationCallback
|
|
596
|
+
* If provided, called after the string is constructed to create the final value.
|
|
597
|
+
*
|
|
598
|
+
* @returns
|
|
599
|
+
* String created from the value.
|
|
600
|
+
*/
|
|
601
|
+
create(length: number, value: number | bigint, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint, creationCallback?: CreationCallback): string {
|
|
602
|
+
return this.doCreate(length, exclusion, tweak, creationCallback, (transformer, transformationCallback) => transformer.forward(BigInt(value), transformationCallback));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Create a sequence of strings by mapping each value to the equivalent characters in the character set across the
|
|
607
|
+
* length of the string. Equivalent to calling {@link create} for `value = startValue + n` where `n` ranges from `0`
|
|
608
|
+
* to `count - 1`.
|
|
609
|
+
*
|
|
610
|
+
* The implementation uses {@link Transformer.forwardSequence}, so the values are created only as needed.
|
|
611
|
+
*
|
|
612
|
+
* @param length
|
|
613
|
+
* See {@link create}.
|
|
614
|
+
*
|
|
615
|
+
* @param startValue
|
|
616
|
+
* Numeric value of the first string. Strings are created from `startValue` to `startValue + count - 1`.
|
|
617
|
+
*
|
|
618
|
+
* @param count
|
|
619
|
+
* The number of strings to create.
|
|
620
|
+
*
|
|
621
|
+
* @param exclusion
|
|
622
|
+
* See {@link create}.
|
|
623
|
+
*
|
|
624
|
+
* @param tweak
|
|
625
|
+
* See {@link create}.
|
|
626
|
+
*
|
|
627
|
+
* @param creationCallback
|
|
628
|
+
* See {@link create}.
|
|
629
|
+
*
|
|
630
|
+
* @returns
|
|
631
|
+
* Iterable iterator over created strings.
|
|
632
|
+
*/
|
|
633
|
+
createSequence(length: number, startValue: number | bigint, count: number, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint, creationCallback?: CreationCallback): IterableIterator<string> {
|
|
634
|
+
return this.doCreate(length, exclusion, tweak, creationCallback, (transformer, transformationCallback) => transformer.forwardSequence(BigInt(startValue), count, transformationCallback));
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Create multiple strings by mapping each value to the equivalent characters in the character set across the length
|
|
639
|
+
* of the string. Equivalent to calling {@link create} for each value in the values source.
|
|
640
|
+
*
|
|
641
|
+
* The implementation uses {@link Transformer.forwardMultiple}, so the values are created only as needed.
|
|
642
|
+
*
|
|
643
|
+
* @param length
|
|
644
|
+
* See {@link create}.
|
|
645
|
+
*
|
|
646
|
+
* @param valuesSource
|
|
647
|
+
* Source of values.
|
|
648
|
+
*
|
|
649
|
+
* @param exclusion
|
|
650
|
+
* See {@link create}.
|
|
651
|
+
*
|
|
652
|
+
* @param tweak
|
|
653
|
+
* See {@link create}.
|
|
654
|
+
*
|
|
655
|
+
* @param creationCallback
|
|
656
|
+
* See {@link create}.
|
|
657
|
+
*
|
|
658
|
+
* @returns
|
|
659
|
+
* Iterable iterator over created strings.
|
|
660
|
+
*/
|
|
661
|
+
createMultiple(length: number, valuesSource: IterationSource<number | bigint>, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint, creationCallback?: CreationCallback): IterableIterator<string> {
|
|
662
|
+
return this.doCreate(length, exclusion, tweak, creationCallback, (transformer, transformationCallback) => transformer.forwardMultiple(IterationHelper.from(valuesSource).map(value => BigInt(value)), transformationCallback));
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Determine the value for a string.
|
|
667
|
+
*
|
|
668
|
+
* @param s
|
|
669
|
+
* String.
|
|
670
|
+
*
|
|
671
|
+
* @param exclusion
|
|
672
|
+
* Strings excluded from the range of inputs. See {@link Exclusion} for possible values and their meaning.
|
|
673
|
+
*
|
|
674
|
+
* @param tweak
|
|
675
|
+
* If provided, the numerical value of the string was "tweaked" using an {@link EncryptionTransformer | encryption
|
|
676
|
+
* transformer}.
|
|
677
|
+
*
|
|
678
|
+
* @returns
|
|
679
|
+
* Numeric value of the string.
|
|
680
|
+
*/
|
|
681
|
+
value(s: string, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint): bigint {
|
|
682
|
+
const length = s.length;
|
|
683
|
+
|
|
684
|
+
this.validateLength(length);
|
|
685
|
+
this.validateExclusion(exclusion);
|
|
686
|
+
|
|
687
|
+
const characterSetSizeN = BigInt(this.characterSetSize);
|
|
688
|
+
|
|
689
|
+
// Convert string to its value character by character.
|
|
690
|
+
let value = this.characterIndexes(s).reduce((accumulator, characterIndex, index) => {
|
|
691
|
+
if (characterIndex === undefined) {
|
|
692
|
+
throw new RangeError(i18next.t("CharacterSetValidator.invalidCharacterAtPosition", {
|
|
693
|
+
ns: utilityNS,
|
|
694
|
+
c: s.charAt(index),
|
|
695
|
+
position: index + 1
|
|
696
|
+
}));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
let value: bigint;
|
|
700
|
+
|
|
701
|
+
if (index === 0 && exclusion === Exclusion.FirstZero) {
|
|
702
|
+
if (characterIndex === 0) {
|
|
703
|
+
throw new RangeError(i18next.t("CharacterSetValidator.invalidCharacterAtPosition", {
|
|
704
|
+
ns: utilityNS,
|
|
705
|
+
c: "0",
|
|
706
|
+
position: 1
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Accumulator is known to be zero at this point.
|
|
711
|
+
value = BigInt(characterIndex - 1);
|
|
712
|
+
} else {
|
|
713
|
+
value = accumulator * characterSetSizeN + BigInt(characterIndex);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return value;
|
|
717
|
+
}, 0n);
|
|
718
|
+
|
|
719
|
+
if (exclusion === Exclusion.AllNumeric) {
|
|
720
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
721
|
+
const allZerosValue = this._allZerosValues![length];
|
|
722
|
+
|
|
723
|
+
if (value >= allZerosValue) {
|
|
724
|
+
// Call will ensure that string is not all-numeric.
|
|
725
|
+
value -= this.allNumericShift(false, length, value - allZerosValue);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
730
|
+
return Transformer.get(this._exclusionDomains[exclusion]![length], tweak).reverse(value);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Numeric creator. Character set is 0-9. Supports {@link Exclusion.FirstZero}.
|
|
736
|
+
*/
|
|
737
|
+
export const NUMERIC_CREATOR = new CharacterSetCreator([
|
|
738
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
|
739
|
+
], Exclusion.FirstZero);
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Hexadecimal creator. Character set is 0-9, A-F. Supports {@link Exclusion.FirstZero} and {@link
|
|
743
|
+
* Exclusion.AllNumeric}.
|
|
744
|
+
*/
|
|
745
|
+
export const HEXADECIMAL_CREATOR = new CharacterSetCreator([
|
|
746
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
|
747
|
+
"A", "B", "C", "D", "E", "F"
|
|
748
|
+
], Exclusion.FirstZero, Exclusion.AllNumeric);
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Alphabetic creator. Character set is A-Z.
|
|
752
|
+
*/
|
|
753
|
+
export const ALPHABETIC_CREATOR = new CharacterSetCreator([
|
|
754
|
+
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
|
755
|
+
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
|
756
|
+
]);
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Alphanumeric creator. Character set is 0-9, A-Z. Supports {@link Exclusion.FirstZero} and {@link
|
|
760
|
+
* Exclusion.AllNumeric}.
|
|
761
|
+
*/
|
|
762
|
+
export const ALPHANUMERIC_CREATOR = new CharacterSetCreator([
|
|
763
|
+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
|
764
|
+
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
|
765
|
+
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
|
766
|
+
], Exclusion.FirstZero, Exclusion.AllNumeric);
|