• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FontoXML / fontoxpath / 13791205512

11 Mar 2025 02:57PM UTC coverage: 91.576% (+0.001%) from 91.575%
13791205512

push

github

DrRataplan
Move test matrix to exclude 16 and include 22

16 is EOL and 22 is LTS

5000 of 5809 branches covered (86.07%)

Branch coverage included in aggregate %.

10795 of 11439 relevant lines covered (94.37%)

96509.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.22
/src/expressions/functions/builtInFunctions_string.ts
1
import { compile, MatchFn } from 'xspattern';
15✔
2
import atomize, { atomizeSingleValue } from '../dataTypes/atomize';
15✔
3
import castToType from '../dataTypes/castToType';
15✔
4
import createAtomicValue from '../dataTypes/createAtomicValue';
15✔
5
import ISequence from '../dataTypes/ISequence';
6
import isSubtypeOf from '../dataTypes/isSubtypeOf';
15✔
7
import sequenceFactory from '../dataTypes/sequenceFactory';
15✔
8
import Value, { EllipsisType, SequenceMultiplicity, ValueType } from '../dataTypes/Value';
9
import DynamicContext from '../DynamicContext';
10
import ExecutionParameters from '../ExecutionParameters';
11
import { BUILT_IN_NAMESPACE_URIS } from '../staticallyKnownNamespaces';
12
import StaticContext from '../StaticContext';
13
import { DONE_TOKEN, ready } from '../util/iterators';
15✔
14
import zipSingleton from '../util/zipSingleton';
15✔
15
import { errXPDY0002 } from '../XPathErrors';
16
import { BuiltinDeclarationType } from './builtInFunctions';
17
import builtInNumericFunctions from './builtInFunctions_numeric';
15✔
18
import FunctionDefinitionType from './FunctionDefinitionType';
19
import { contextItemAsFirstArgument } from './argumentHelper';
15✔
20

21
const fnRound = builtInNumericFunctions.functions.round;
15✔
22

23
function collationError(): ISequence {
24
        throw new Error('FOCH0002: No collations are supported');
3✔
25
}
26

27
const fnCompare: FunctionDefinitionType = (
15✔
28
        _dynamicContext,
29
        _executionParameters,
30
        _staticContext,
31
        arg1,
32
        arg2,
33
) => {
34
        if (arg1.isEmpty() || arg2.isEmpty()) {
45✔
35
                return sequenceFactory.empty();
6✔
36
        }
37

38
        const arg1Value = arg1.first().value;
39✔
39
        const arg2Value = arg2.first().value;
39✔
40

41
        if (arg1Value > arg2Value) {
39✔
42
                return sequenceFactory.singleton(createAtomicValue(1, ValueType.XSINTEGER));
15✔
43
        }
44

45
        if (arg1Value < arg2Value) {
24✔
46
                return sequenceFactory.singleton(createAtomicValue(-1, ValueType.XSINTEGER));
15✔
47
        }
48

49
        return sequenceFactory.singleton(createAtomicValue(0, ValueType.XSINTEGER));
9✔
50
};
51

52
const fnConcat: FunctionDefinitionType = (
15✔
53
        _dynamicContext,
54
        executionParameters,
55
        _staticContext,
56
        ...stringSequences: ISequence[]
57
) => {
58
        stringSequences = stringSequences.map((sequence) => {
12,552✔
59
                return atomize(sequence, executionParameters).mapAll((allValues) => {
30,177✔
60
                        return sequenceFactory.singleton(
30,174✔
61
                                createAtomicValue(
62
                                        allValues
63
                                                .map((stringValue) =>
64
                                                        stringValue === null
30,174!
65
                                                                ? ''
66
                                                                : castToType(stringValue, ValueType.XSSTRING).value,
67
                                                )
68
                                                .join(''),
69
                                        ValueType.XSSTRING,
70
                                ),
71
                        );
72
                });
73
        });
74

75
        return zipSingleton(stringSequences, (stringValues) => {
12,549✔
76
                return sequenceFactory.singleton(
12,549✔
77
                        createAtomicValue(
78
                                stringValues.map((stringValue) => stringValue.value).join(''),
30,171✔
79
                                ValueType.XSSTRING,
80
                        ),
81
                );
82
        });
83
};
84

85
const fnContains: FunctionDefinitionType = (
15✔
86
        _dynamicContext,
87
        _executionParameters,
88
        _staticContext,
89
        arg1,
90
        arg2,
91
) => {
92
        const stringToTest = !arg1.isEmpty() ? arg1.first().value : '';
30✔
93
        const contains = !arg2.isEmpty() ? arg2.first().value : '';
30✔
94
        if (contains.length === 0) {
30✔
95
                return sequenceFactory.singletonTrueSequence();
9✔
96
        }
97

98
        if (stringToTest.length === 0) {
21✔
99
                return sequenceFactory.singletonFalseSequence();
6✔
100
        }
101

102
        // TODO: choose a collation, this defines whether eszett (ß) should equal 'ss'
103
        if (stringToTest.includes(contains)) {
15✔
104
                return sequenceFactory.singletonTrueSequence();
12✔
105
        }
106
        return sequenceFactory.singletonFalseSequence();
3✔
107
};
108

109
const fnStartsWith: FunctionDefinitionType = (
15✔
110
        _dynamicContext,
111
        _executionParameters,
112
        _staticContext,
113
        arg1,
114
        arg2,
115
) => {
116
        const startsWith = !arg2.isEmpty() ? arg2.first().value : '';
66✔
117
        if (startsWith.length === 0) {
66✔
118
                return sequenceFactory.singletonTrueSequence();
9✔
119
        }
120
        const stringToTest = !arg1.isEmpty() ? arg1.first().value : '';
57✔
121
        if (stringToTest.length === 0) {
57✔
122
                return sequenceFactory.singletonFalseSequence();
9✔
123
        }
124
        // TODO: choose a collation, this defines whether eszett (ß) should equal 'ss'
125
        if (stringToTest.startsWith(startsWith)) {
48✔
126
                return sequenceFactory.singletonTrueSequence();
27✔
127
        }
128
        return sequenceFactory.singletonFalseSequence();
21✔
129
};
130

131
const fnEndsWith: FunctionDefinitionType = (
15✔
132
        _dynamicContext,
133
        _executionParameters,
134
        _staticContext,
135
        arg1,
136
        arg2,
137
) => {
138
        const endsWith = !arg2.isEmpty() ? arg2.first().value : '';
21✔
139
        if (endsWith.length === 0) {
21✔
140
                return sequenceFactory.singletonTrueSequence();
9✔
141
        }
142
        const stringToTest = !arg1.isEmpty() ? arg1.first().value : '';
12✔
143
        if (stringToTest.length === 0) {
12✔
144
                return sequenceFactory.singletonFalseSequence();
6✔
145
        }
146
        // TODO: choose a collation, this defines whether eszett (ß) should equal 'ss'
147
        if (stringToTest.endsWith(endsWith)) {
6✔
148
                return sequenceFactory.singletonTrueSequence();
3✔
149
        }
150
        return sequenceFactory.singletonFalseSequence();
3✔
151
};
152

153
const fnString: FunctionDefinitionType = (
15✔
154
        _dynamicContext,
155
        executionParameters,
156
        _staticContext,
157
        sequence,
158
) => {
159
        return sequence.switchCases({
12,840✔
160
                empty: () => sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING)),
9✔
161
                default: () =>
162
                        sequence.map((value) => {
12,369✔
163
                                if (isSubtypeOf(value.type, ValueType.NODE)) {
12,369✔
164
                                        const stringValueSequence = atomizeSingleValue(value, executionParameters);
11,904✔
165
                                        // Assume here that a node always atomizes to a singlevalue. This will not work
166
                                        // anymore when schema support will be imlemented.
167
                                        const stringValue = stringValueSequence.first();
11,904✔
168
                                        if (isSubtypeOf(value.type, ValueType.ATTRIBUTE)) {
11,904✔
169
                                                return castToType(stringValue, ValueType.XSSTRING);
10,203✔
170
                                        }
171
                                        return stringValue;
1,701✔
172
                                }
173
                                return castToType(value, ValueType.XSSTRING);
465✔
174
                        }),
175
        });
176
};
177

178
const fnStringJoin: FunctionDefinitionType = (
15✔
179
        _dynamicContext,
180
        executionParameters,
181
        _staticContext,
182
        sequence,
183
        separator,
184
) => {
185
        return zipSingleton([separator], ([separatorString]) =>
45✔
186
                atomize(sequence, executionParameters).mapAll((allStrings) => {
45✔
187
                        const joinedString = allStrings
45✔
188
                                .map((stringValue) => castToType(stringValue, ValueType.XSSTRING).value)
195✔
189
                                .join(separatorString.value);
190
                        return sequenceFactory.singleton(createAtomicValue(joinedString, ValueType.XSSTRING));
45✔
191
                }),
192
        );
193
};
194

195
const fnStringLength: FunctionDefinitionType = (
15✔
196
        _dynamicContext,
197
        _executionParameters,
198
        _staticContext,
199
        sequence,
200
) => {
201
        if (sequence.isEmpty()) {
12✔
202
                return sequenceFactory.singleton(createAtomicValue(0, ValueType.XSINTEGER));
3✔
203
        }
204
        const stringValue = sequence.first().value;
9✔
205
        // In ES6, Array.from(💩).length === 1
206
        return sequenceFactory.singleton(
9✔
207
                createAtomicValue(Array.from(stringValue).length, ValueType.XSINTEGER),
208
        );
209
};
210

211
const fnSubstringBefore: FunctionDefinitionType = (
15✔
212
        _dynamicContext,
213
        _executionParameters,
214
        _staticContext,
215
        arg1,
216
        arg2,
217
) => {
218
        const strArg1 = arg1.isEmpty() ? '' : arg1.first().value;
9✔
219

220
        const strArg2 = arg2.isEmpty() ? '' : arg2.first().value;
9✔
221

222
        if (strArg2 === '') {
9✔
223
                return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
3✔
224
        }
225
        const startIndex = strArg1.indexOf(strArg2);
6✔
226
        if (startIndex === -1) {
6!
227
                return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
×
228
        }
229
        return sequenceFactory.singleton(
6✔
230
                createAtomicValue(strArg1.substring(0, startIndex), ValueType.XSSTRING),
231
        );
232
};
233

234
const fnSubstringAfter: FunctionDefinitionType = (
15✔
235
        _dynamicContext,
236
        _executionParameters,
237
        _staticContext,
238
        arg1,
239
        arg2,
240
) => {
241
        const strArg1 = arg1.isEmpty() ? '' : arg1.first().value;
12✔
242

243
        const strArg2 = arg2.isEmpty() ? '' : arg2.first().value;
12✔
244

245
        if (strArg2 === '') {
12✔
246
                return sequenceFactory.singleton(createAtomicValue(strArg1, ValueType.XSSTRING));
3✔
247
        }
248
        const startIndex = strArg1.indexOf(strArg2);
9✔
249
        if (startIndex === -1) {
9!
250
                return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
×
251
        }
252
        return sequenceFactory.singleton(
9✔
253
                createAtomicValue(strArg1.substring(startIndex + strArg2.length), ValueType.XSSTRING),
254
        );
255
};
256

257
const fnSubstring: FunctionDefinitionType = (
15✔
258
        dynamicContext,
259
        executionParameters,
260
        staticContext,
261
        sourceString,
262
        start,
263
        length,
264
) => {
265
        const roundedStart = fnRound(
21✔
266
                false,
267
                dynamicContext,
268
                executionParameters,
269
                staticContext,
270
                start,
271
                null,
272
        );
273
        const roundedLength =
274
                length !== null
21!
275
                        ? fnRound(false, dynamicContext, executionParameters, staticContext, length, null)
276
                        : null;
277

278
        let done = false;
21✔
279
        let sourceStringItem: Value = null;
21✔
280
        let startItem: Value = null;
21✔
281
        let lengthItem: Value = null;
21✔
282
        return sequenceFactory.create({
21✔
283
                next: () => {
284
                        if (done) {
42✔
285
                                return DONE_TOKEN;
21✔
286
                        }
287
                        if (!sourceStringItem) {
21!
288
                                sourceStringItem = sourceString.first();
21✔
289

290
                                if (sourceStringItem === null) {
21!
291
                                        // The first argument can be the empty sequence
292
                                        done = true;
×
293
                                        return ready(createAtomicValue('', ValueType.XSSTRING));
×
294
                                }
295
                        }
296

297
                        if (!startItem) {
21!
298
                                startItem = roundedStart.first();
21✔
299
                        }
300

301
                        if (!lengthItem && length) {
21✔
302
                                lengthItem = null;
12✔
303
                                lengthItem = roundedLength.first();
12✔
304
                        }
305

306
                        done = true;
21✔
307

308
                        const strValue = sourceStringItem.value as string;
21✔
309
                        return ready(
21✔
310
                                createAtomicValue(
311
                                        Array.from(strValue)
312
                                                .slice(
313
                                                        Math.max((startItem.value as number) - 1, 0),
314
                                                        length
21✔
315
                                                                ? (startItem.value as number) + (lengthItem.value as number) - 1
316
                                                                : undefined,
317
                                                )
318
                                                .join(''),
319
                                        ValueType.XSSTRING,
320
                                ),
321
                        );
322
                },
323
        });
324
};
325

326
const fnTokenize: FunctionDefinitionType = (
15✔
327
        _dynamicContext,
328
        _executionParameters,
329
        _staticContext,
330
        input,
331
        pattern,
332
) => {
333
        if (input.isEmpty() || input.first().value.length === 0) {
36✔
334
                return sequenceFactory.empty();
6✔
335
        }
336
        const inputString: string = input.first().value;
30✔
337
        const patternString: string = pattern.first().value;
30✔
338

339
        const regex = compileJSRegex(patternString);
30✔
340
        regex.lastIndex = 0;
24✔
341
        const results = [];
24✔
342
        let result = regex.exec(inputString);
24✔
343

344
        let index = 0;
24✔
345

346
        // Repeatedly execute the regex to get all the outputs
347
        // TODO: with this approach it should not be too difficult to actually use XSPattern here.
348
        while (result) {
24✔
349
                results.push(inputString.slice(index, result.index));
60✔
350
                index = regex.lastIndex;
60✔
351
                result = regex.exec(inputString);
60✔
352
        }
353

354
        // Don't forget the last split part!
355
        results.push(inputString.slice(index));
24✔
356

357
        return sequenceFactory.create(
24✔
358
                results.map((token: string) => createAtomicValue(token, ValueType.XSSTRING)),
84✔
359
        );
360
};
361

362
const fnUpperCase: FunctionDefinitionType = (
15✔
363
        _dynamicContext,
364
        _executionParameters,
365
        _staticContext,
366
        stringSequence,
367
) => {
368
        if (stringSequence.isEmpty()) {
9✔
369
                return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
3✔
370
        }
371
        return stringSequence.map((stringValue) =>
6✔
372
                createAtomicValue(stringValue.value.toUpperCase(), ValueType.XSSTRING),
6✔
373
        );
374
};
375

376
const fnLowerCase: FunctionDefinitionType = (
15✔
377
        _dynamicContext,
378
        _executionParameters,
379
        _staticContext,
380
        stringSequence,
381
) => {
382
        if (stringSequence.isEmpty()) {
6✔
383
                return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
3✔
384
        }
385
        return stringSequence.map((stringValue) =>
3✔
386
                createAtomicValue(stringValue.value.toLowerCase(), ValueType.XSSTRING),
3✔
387
        );
388
};
389

390
const fnNormalizeSpace: FunctionDefinitionType = (
15✔
391
        _dynamicContext,
392
        _executionParameters,
393
        _staticContext,
394
        arg,
395
) => {
396
        if (arg.isEmpty()) {
45✔
397
                return sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING));
6✔
398
        }
399
        const stringValue = arg.first().value.trim();
39✔
400
        return sequenceFactory.singleton(
39✔
401
                createAtomicValue(stringValue.replace(/\s+/g, ' '), ValueType.XSSTRING),
402
        );
403
};
404

405
const fnTranslate: FunctionDefinitionType = (
15✔
406
        _dynamicContext,
407
        _executionParameters,
408
        _staticContext,
409
        argSequence,
410
        mapStringSequence,
411
        transStringSequence,
412
) => {
413
        return zipSingleton(
15✔
414
                [argSequence, mapStringSequence, transStringSequence],
415
                ([argValue, mapStringSequenceValue, transStringSequenceValue]) => {
416
                        const argArr = Array.from(argValue ? argValue.value : '');
15✔
417
                        const mapStringArr = Array.from(mapStringSequenceValue.value);
15✔
418
                        const transStringArr = Array.from(transStringSequenceValue.value);
15✔
419

420
                        const result = argArr.map((letter) => {
15✔
421
                                if (mapStringArr.includes(letter)) {
51✔
422
                                        const index = mapStringArr.indexOf(letter);
45✔
423
                                        if (index <= transStringArr.length) {
45!
424
                                                return transStringArr[index];
45✔
425
                                        }
426
                                } else {
427
                                        return letter;
6✔
428
                                }
429
                        });
430
                        return sequenceFactory.singleton(
15✔
431
                                createAtomicValue(result.join(''), ValueType.XSSTRING),
432
                        );
433
                },
434
        );
435
};
436

437
const fnCodepointsToString: FunctionDefinitionType = (
15✔
438
        _dynamicContext,
439
        _executionParameters,
440
        _staticContext,
441
        numberSequence: ISequence,
442
) => {
443
        return numberSequence.mapAll((numbers) => {
21✔
444
                const str = numbers
21✔
445
                        .map((num) => {
446
                                const numericValue: number = num.value;
36✔
447
                                if (
36✔
448
                                        numericValue === 0x9 ||
210✔
449
                                        numericValue === 0xa ||
450
                                        numericValue === 0xd ||
451
                                        (numericValue >= 0x20 && numericValue <= 0xd7ff) ||
452
                                        (numericValue >= 0xe000 && numericValue <= 0xfffd) ||
453
                                        (numericValue >= 0x10000 && numericValue <= 0x10ffff)
454
                                ) {
455
                                        return String.fromCodePoint(numericValue);
27✔
456
                                } else {
457
                                        throw new Error('FOCH0001');
9✔
458
                                }
459
                        })
460
                        .join('');
461
                return sequenceFactory.singleton(createAtomicValue(str, ValueType.XSSTRING));
12✔
462
        });
463
};
464

465
const fnStringToCodepoints: FunctionDefinitionType = (
15✔
466
        _dynamicContext,
467
        _executionParameters,
468
        _staticContext,
469
        stringSequence: ISequence,
470
) => {
471
        return zipSingleton([stringSequence], ([str]) => {
9✔
472
                const characters = str ? (str.value as string).split('') : [];
9✔
473
                if (characters.length === 0) {
9✔
474
                        return sequenceFactory.empty();
6✔
475
                }
476

477
                return sequenceFactory.create(
3✔
478
                        characters.map((character) =>
479
                                createAtomicValue(character.codePointAt(0), ValueType.XSINTEGER),
21✔
480
                        ),
481
                );
482
        });
483
};
484

485
const fnEncodeForUri: FunctionDefinitionType = (
15✔
486
        _dynamicContext,
487
        _executionParameters,
488
        _staticContext,
489
        stringSequence: ISequence,
490
) => {
491
        return zipSingleton([stringSequence], ([str]) => {
6✔
492
                if (str === null || str.value.length === 0) {
6✔
493
                        return sequenceFactory.create(createAtomicValue('', ValueType.XSSTRING));
3✔
494
                }
495

496
                // Adhering RFC 3986 which reserves !, ', (, ), and *
497
                return sequenceFactory.create(
3✔
498
                        createAtomicValue(
499
                                encodeURIComponent(str.value).replace(/[!'()*]/g, (c) => {
500
                                        return '%' + c.charCodeAt(0).toString(16).toUpperCase();
×
501
                                }),
502
                                ValueType.XSSTRING,
503
                        ),
504
                );
505
        });
506
};
507

508
const fnIriToUri: FunctionDefinitionType = (
15✔
509
        _dynamicContext,
510
        _executionParameters,
511
        _staticContext,
512
        stringSequence: ISequence,
513
) => {
514
        return zipSingleton([stringSequence], ([str]) => {
6✔
515
                if (str === null || str.value.length === 0) {
6✔
516
                        return sequenceFactory.create(createAtomicValue('', ValueType.XSSTRING));
3✔
517
                }
518

519
                const strValue = str.value as string;
3✔
520

521
                return sequenceFactory.create(
3✔
522
                        createAtomicValue(
523
                                strValue.replace(
524
                                        /([\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFEF "<>{}|\\^`/\n\u007f\u0080-\u009f]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g,
525
                                        (a) => encodeURI(a),
15✔
526
                                ),
527
                                ValueType.XSSTRING,
528
                        ),
529
                );
530
        });
531
};
532

533
const fnCodepointEqual: FunctionDefinitionType = (
15✔
534
        _dynamicContext,
535
        _executionParameters,
536
        _staticContext,
537
        stringSequence1: ISequence,
538
        stringSequence2: ISequence,
539
) => {
540
        return zipSingleton([stringSequence1, stringSequence2], ([value1, value2]) => {
24✔
541
                if (value1 === null || value2 === null) {
24✔
542
                        return sequenceFactory.empty();
12✔
543
                }
544

545
                const string1: string = value1.value;
12✔
546
                const string2: string = value2.value;
12✔
547

548
                if (string1.length !== string2.length) {
12✔
549
                        return sequenceFactory.singletonFalseSequence();
3✔
550
                }
551
                const string1Characters = string1.split('');
9✔
552
                const string2Characters = string2.split('');
9✔
553

554
                for (let i = 0; i < string1Characters.length; i++) {
9✔
555
                        if (string1Characters[i].codePointAt(0) !== string2Characters[i].codePointAt(0)) {
15✔
556
                                return sequenceFactory.singletonFalseSequence();
3✔
557
                        }
558
                }
559

560
                return sequenceFactory.singletonTrueSequence();
6✔
561
        });
562
};
563

564
const cachedPatterns: Map<string, MatchFn> = new Map();
15✔
565

566
const fnMatches: FunctionDefinitionType = (
15✔
567
        _dynamicContext,
568
        _executionParameters,
569
        _staticContext,
570
        inputSequence: ISequence,
571
        patternSequence: ISequence,
572
) => {
573
        return zipSingleton([inputSequence, patternSequence], ([inputValue, patternValue]) => {
6✔
574
                const input: string = inputValue ? inputValue.value : '';
6!
575
                const pattern: string = patternValue.value;
6✔
576
                let compiledPattern = cachedPatterns.get(pattern);
6✔
577
                if (!compiledPattern) {
6!
578
                        try {
6✔
579
                                compiledPattern = compile(pattern, { language: 'xpath' });
6✔
580
                        } catch (error) {
581
                                throw new Error(`FORX0002: ${error}`);
×
582
                        }
583
                        cachedPatterns.set(pattern, compiledPattern);
6✔
584
                }
585
                return compiledPattern(input)
6!
586
                        ? sequenceFactory.singletonTrueSequence()
587
                        : sequenceFactory.singletonFalseSequence();
588
        });
589
};
590

591
const compiledRegexes = new Map<string, RegExp>();
15✔
592
function compileJSRegex(pattern: string): RegExp {
593
        if (compiledRegexes.has(pattern)) {
66✔
594
                return compiledRegexes.get(pattern);
16✔
595
        }
596
        let regex;
597
        try {
50✔
598
                regex = new RegExp(pattern, 'g');
50✔
599
        } catch (error) {
600
                throw new Error(`FORX0002: ${error}`);
3✔
601
        }
602

603
        // Only do this check once per regex
604
        if (regex.test('')) {
47✔
605
                throw new Error(`FORX0003: the pattern ${pattern} matches the zero length string`);
6✔
606
        }
607

608
        compiledRegexes.set(pattern, regex);
41✔
609

610
        return regex;
41✔
611
}
612

613
const fnReplace: FunctionDefinitionType = (
15✔
614
        _dynamicContext,
615
        _executionParameters,
616
        _staticContext,
617
        inputSequence: ISequence,
618
        patternSequence: ISequence,
619
        replacementSequence: ISequence,
620
) => {
621
        return zipSingleton(
36✔
622
                [inputSequence, patternSequence, replacementSequence],
623
                ([inputValue, patternValue, replacementValue]) => {
624
                        const input = inputValue ? inputValue.value : '';
36!
625
                        const pattern = patternValue.value;
36✔
626
                        let replacement: string = replacementValue.value;
36✔
627
                        if (replacement.includes('$0')) {
36!
628
                                throw new Error(
×
629
                                        'Using $0 in fn:replace to replace substrings with full matches is not supported.',
630
                                );
631
                        }
632

633
                        // Note: while XPath patterns escape dollars with backslashes, JavaScript escapes them by duplicating
634
                        replacement = replacement
36✔
635
                                .split(/((?:\$\$)|(?:\\\$)|(?:\\\\))/)
636
                                .map((part) => {
637
                                        switch (part) {
54!
638
                                                case '\\$':
639
                                                        return '$$';
6✔
640
                                                case '\\\\':
641
                                                        return '\\';
3✔
642
                                                case '$$':
643
                                                        throw new Error('FORX0004: invalid replacement: "$$"');
×
644
                                                default:
645
                                                        return part;
45✔
646
                                        }
647
                                })
648
                                .join('');
649
                        const regex = compileJSRegex(pattern);
36✔
650
                        const result = input.replace(regex, replacement);
33✔
651

652
                        return sequenceFactory.singleton(createAtomicValue(result, ValueType.XSSTRING));
33✔
653
                },
654
        );
655
};
656

657
const declarations: BuiltinDeclarationType[] = [
15✔
658
        {
659
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
660
                localName: 'compare',
661
                argumentTypes: [
662
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
663
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
664
                ],
665
                returnType: { type: ValueType.XSINTEGER, mult: SequenceMultiplicity.ZERO_OR_ONE },
666
                callFunction: fnCompare,
667
        },
668

669
        {
670
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
671
                localName: 'compare',
672
                argumentTypes: [
673
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
674
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
675
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
676
                ],
677
                returnType: { type: ValueType.XSINTEGER, mult: SequenceMultiplicity.ZERO_OR_ONE },
678
                callFunction: collationError,
679
        },
680

681
        {
682
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
683
                localName: 'concat',
684
                argumentTypes: [
685
                        { type: ValueType.XSANYATOMICTYPE, mult: SequenceMultiplicity.ZERO_OR_ONE },
686
                        { type: ValueType.XSANYATOMICTYPE, mult: SequenceMultiplicity.ZERO_OR_ONE },
687
                        EllipsisType.ELLIPSIS,
688
                ],
689
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
690
                callFunction: fnConcat,
691
        },
692

693
        {
694
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
695
                localName: 'contains',
696
                argumentTypes: [
697
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
698
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
699
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
700
                ],
701
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
702
                callFunction: collationError,
703
        },
704

705
        {
706
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
707
                localName: 'contains',
708
                argumentTypes: [
709
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
710
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
711
                ],
712
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
713
                callFunction: fnContains,
714
        },
715

716
        {
717
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
718
                localName: 'ends-with',
719
                argumentTypes: [
720
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
721
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
722
                ],
723
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
724
                callFunction: fnEndsWith,
725
        },
726

727
        {
728
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
729
                localName: 'ends-with',
730
                argumentTypes: [
731
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
732
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
733
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
734
                ],
735
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
736
                callFunction: collationError,
737
        },
738

739
        {
740
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
741
                localName: 'normalize-space',
742
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
743
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
744
                callFunction: fnNormalizeSpace,
745
        },
746

747
        {
748
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
749
                localName: 'normalize-space',
750
                argumentTypes: [],
751
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
752
                callFunction: contextItemAsFirstArgument(
753
                        'normalize-space',
754
                        ValueType.XSSTRING,
755
                        (
756
                                dynamicContext: DynamicContext,
757
                                executionParameters: ExecutionParameters,
758
                                staticContext: StaticContext,
759
                                contextItem: ISequence,
760
                        ) =>
761
                                fnNormalizeSpace(
18✔
762
                                        dynamicContext,
763
                                        executionParameters,
764
                                        staticContext,
765
                                        fnString(dynamicContext, executionParameters, staticContext, contextItem),
766
                                ),
767
                ),
768
        },
769

770
        {
771
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
772
                localName: 'starts-with',
773
                argumentTypes: [
774
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
775
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
776
                ],
777
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
778
                callFunction: fnStartsWith,
779
        },
780

781
        {
782
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
783
                localName: 'starts-with',
784
                argumentTypes: [
785
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
786
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
787
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
788
                ],
789
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
790
                callFunction: collationError,
791
        },
792

793
        {
794
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
795
                localName: 'string',
796
                argumentTypes: [{ type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_ONE }],
797
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
798
                callFunction: fnString,
799
        },
800

801
        {
802
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
803
                localName: 'string',
804
                argumentTypes: [],
805
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
806
                callFunction: contextItemAsFirstArgument('string', ValueType.ITEM, fnString),
807
        },
808

809
        {
810
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
811
                localName: 'substring-before',
812
                argumentTypes: [
813
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
814
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
815
                ],
816
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
817
                callFunction: fnSubstringBefore,
818
        },
819

820
        {
821
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
822
                localName: 'substring-after',
823
                argumentTypes: [
824
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
825
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
826
                ],
827
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
828
                callFunction: fnSubstringAfter,
829
        },
830

831
        {
832
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
833
                localName: 'substring',
834
                argumentTypes: [
835
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
836
                        { type: ValueType.XSDOUBLE, mult: SequenceMultiplicity.EXACTLY_ONE },
837
                ],
838
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
839
                callFunction: fnSubstring,
840
        },
841

842
        {
843
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
844
                localName: 'substring',
845
                argumentTypes: [
846
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
847
                        { type: ValueType.XSDOUBLE, mult: SequenceMultiplicity.EXACTLY_ONE },
848
                        { type: ValueType.XSDOUBLE, mult: SequenceMultiplicity.EXACTLY_ONE },
849
                ],
850
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
851
                callFunction: fnSubstring,
852
        },
853

854
        {
855
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
856
                localName: 'upper-case',
857
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
858
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
859
                callFunction: fnUpperCase,
860
        },
861

862
        {
863
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
864
                localName: 'lower-case',
865
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
866
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
867
                callFunction: fnLowerCase,
868
        },
869

870
        {
871
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
872
                localName: 'string-join',
873
                argumentTypes: [
874
                        { type: ValueType.XSANYATOMICTYPE, mult: SequenceMultiplicity.ZERO_OR_MORE },
875
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
876
                ],
877
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
878
                callFunction: fnStringJoin,
879
        },
880

881
        {
882
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
883
                localName: 'string-join',
884
                argumentTypes: [
885
                        { type: ValueType.XSANYATOMICTYPE, mult: SequenceMultiplicity.ZERO_OR_MORE },
886
                ],
887
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
888
                callFunction(dynamicContext, executionParameters, staticContext, arg1) {
889
                        return fnStringJoin(
6✔
890
                                dynamicContext,
891
                                executionParameters,
892
                                staticContext,
893
                                arg1,
894
                                sequenceFactory.singleton(createAtomicValue('', ValueType.XSSTRING)),
895
                        );
896
                },
897
        },
898

899
        {
900
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
901
                localName: 'string-length',
902
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
903
                returnType: { type: ValueType.XSINTEGER, mult: SequenceMultiplicity.EXACTLY_ONE },
904
                callFunction: fnStringLength,
905
        },
906

907
        {
908
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
909
                localName: 'string-length',
910
                argumentTypes: [],
911
                returnType: { type: ValueType.XSINTEGER, mult: SequenceMultiplicity.EXACTLY_ONE },
912
                callFunction: contextItemAsFirstArgument(
913
                        'string-length',
914
                        // Note: deviate from the spec to allow expressions line `1!fn:string-length()` to
915
                        // atomize the input still
916
                        ValueType.XSANYATOMICTYPE,
917
                        (
918
                                dynamicContext: DynamicContext,
919
                                executionParameters: ExecutionParameters,
920
                                staticContext: StaticContext,
921
                                contextItem: ISequence,
922
                        ) =>
923
                                fnStringLength(
3✔
924
                                        dynamicContext,
925
                                        executionParameters,
926
                                        staticContext,
927
                                        fnString(dynamicContext, executionParameters, staticContext, contextItem),
928
                                ),
929
                ),
930
        },
931

932
        {
933
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
934
                localName: 'tokenize',
935
                argumentTypes: [
936
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
937
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
938
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
939
                ],
940
                returnType: {
941
                        type: ValueType.XSSTRING,
942
                        mult: SequenceMultiplicity.ZERO_OR_MORE,
943
                },
944
                callFunction(
945
                        _dynamicContext,
946
                        _executionParameters,
947
                        _staticContext,
948
                        _input,
949
                        _pattern,
950
                        _flags,
951
                ) {
952
                        throw new Error('Not implemented: Using flags in tokenize is not supported');
×
953
                },
954
        },
955

956
        {
957
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
958
                localName: 'tokenize',
959
                argumentTypes: [
960
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
961
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
962
                ],
963
                returnType: {
964
                        type: ValueType.XSSTRING,
965
                        mult: SequenceMultiplicity.ZERO_OR_MORE,
966
                },
967
                callFunction: fnTokenize,
968
        },
969

970
        {
971
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
972
                localName: 'tokenize',
973
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
974
                returnType: {
975
                        type: ValueType.XSSTRING,
976
                        mult: SequenceMultiplicity.ZERO_OR_MORE,
977
                },
978
                callFunction(dynamicContext, executionParameters, staticContext, input) {
979
                        return fnTokenize(
21✔
980
                                dynamicContext,
981
                                executionParameters,
982
                                staticContext,
983
                                fnNormalizeSpace(dynamicContext, executionParameters, staticContext, input),
984
                                sequenceFactory.singleton(createAtomicValue(' ', ValueType.XSSTRING)),
985
                        );
986
                },
987
        },
988

989
        {
990
                argumentTypes: [
991
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
992
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
993
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
994
                ],
995
                callFunction: fnTranslate,
996
                localName: 'translate',
997
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
998
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
999
        },
1000

1001
        {
1002
                argumentTypes: [
1003
                        {
1004
                                type: ValueType.XSINTEGER,
1005
                                mult: SequenceMultiplicity.ZERO_OR_MORE,
1006
                        },
1007
                ],
1008
                callFunction: fnCodepointsToString,
1009
                localName: 'codepoints-to-string',
1010
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1011
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1012
        },
1013

1014
        {
1015
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
1016
                callFunction: fnStringToCodepoints,
1017
                localName: 'string-to-codepoints',
1018
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1019
                returnType: {
1020
                        type: ValueType.XSINTEGER,
1021
                        mult: SequenceMultiplicity.ZERO_OR_MORE,
1022
                },
1023
        },
1024

1025
        {
1026
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
1027
                callFunction: fnEncodeForUri,
1028
                localName: 'encode-for-uri',
1029
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1030
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1031
        },
1032

1033
        {
1034
                argumentTypes: [{ type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE }],
1035
                callFunction: fnIriToUri,
1036
                localName: 'iri-to-uri',
1037
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1038
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1039
        },
1040

1041
        {
1042
                argumentTypes: [
1043
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
1044
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
1045
                ],
1046
                callFunction: fnCodepointEqual,
1047
                localName: 'codepoint-equal',
1048
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1049
                returnType: {
1050
                        type: ValueType.XSBOOLEAN,
1051
                        mult: SequenceMultiplicity.ZERO_OR_ONE,
1052
                },
1053
        },
1054

1055
        {
1056
                argumentTypes: [
1057
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
1058
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1059
                ],
1060
                callFunction: fnMatches,
1061
                localName: 'matches',
1062
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1063
                returnType: { type: ValueType.XSBOOLEAN, mult: SequenceMultiplicity.EXACTLY_ONE },
1064
        },
1065

1066
        {
1067
                argumentTypes: [
1068
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
1069
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1070
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1071
                ],
1072
                callFunction: fnReplace,
1073
                localName: 'replace',
1074
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1075
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1076
        },
1077

1078
        {
1079
                argumentTypes: [
1080
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.ZERO_OR_ONE },
1081
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1082
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1083
                        { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1084
                ],
1085
                localName: 'replace',
1086
                namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
1087
                returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
1088
                callFunction(
1089
                        _dynamicContext,
1090
                        _executionParameters,
1091
                        _staticContext,
1092
                        _input,
1093
                        _pattern,
1094
                        _flags,
1095
                ) {
1096
                        throw new Error('Not implemented: Using flags in replace is not supported');
3✔
1097
                },
1098
        },
1099
];
1100

1101
export default {
15✔
1102
        declarations,
1103
        functions: {
1104
                concat: fnConcat,
1105
                endsWith: fnEndsWith,
1106
                normalizeSpace: fnNormalizeSpace,
1107
                replace: fnReplace,
1108
                startsWith: fnStartsWith,
1109
                ['string']: fnString,
1110
                stringJoin: fnStringJoin,
1111
                stringLength: fnStringLength,
1112
                tokenize: fnTokenize,
1113
        },
1114
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc