html-validate 7.6.0 → 7.7.0
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/dist/cjs/browser.d.ts +1 -1
- package/dist/cjs/browser.js +2 -2
- package/dist/cjs/cli.js +31 -31
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/core.d.ts +593 -559
- package/dist/cjs/core.js +2423 -2163
- package/dist/cjs/core.js.map +1 -1
- package/dist/cjs/html-validate.js +12 -12
- package/dist/cjs/html-validate.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/jest-lib.js +13 -14
- package/dist/cjs/jest-lib.js.map +1 -1
- package/dist/cjs/jest.d.ts +1 -1
- package/dist/cjs/test-utils.d.ts +1 -1
- package/dist/cjs/test-utils.js +3 -5
- package/dist/cjs/test-utils.js.map +1 -1
- package/dist/es/browser.d.ts +1 -1
- package/dist/es/browser.js +1 -1
- package/dist/es/cli.js +1 -1
- package/dist/es/core.d.ts +593 -559
- package/dist/es/core.js +2425 -2167
- package/dist/es/core.js.map +1 -1
- package/dist/es/html-validate.js +1 -1
- package/dist/es/index.d.ts +2 -2
- package/dist/es/index.js +1 -1
- package/dist/es/jest-lib.js +1 -1
- package/dist/es/jest.d.ts +1 -1
- package/dist/es/test-utils.d.ts +1 -1
- package/dist/schema/elements.json +4 -0
- package/elements/html5.js +208 -2
- package/package.json +24 -20
package/dist/es/core.js
CHANGED
|
@@ -333,2222 +333,2537 @@ class SchemaValidationError extends UserError {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
/**
|
|
336
|
-
*
|
|
336
|
+
* Helper function to assist IDE with completion and type-checking.
|
|
337
337
|
*
|
|
338
|
-
* @
|
|
338
|
+
* @public
|
|
339
339
|
*/
|
|
340
|
-
function
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
h2 = Math.imul(h2 ^ ch, b);
|
|
340
|
+
function defineMetadata(metatable) {
|
|
341
|
+
return metatable;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @public
|
|
346
|
+
*/
|
|
347
|
+
class DynamicValue {
|
|
348
|
+
constructor(expr) {
|
|
349
|
+
this.expr = expr;
|
|
350
|
+
}
|
|
351
|
+
toString() {
|
|
352
|
+
return this.expr;
|
|
354
353
|
}
|
|
355
|
-
h1 = Math.imul(h1 ^ (h1 >>> 16), c) ^ Math.imul(h2 ^ (h2 >>> 13), d);
|
|
356
|
-
h2 = Math.imul(h2 ^ (h2 >>> 16), c) ^ Math.imul(h1 ^ (h1 >>> 13), d);
|
|
357
|
-
return e * (f & h2) + (h1 >>> 0);
|
|
358
354
|
}
|
|
359
|
-
const computeHash = cyrb53;
|
|
360
355
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
356
|
+
/**
|
|
357
|
+
* DOM Attribute.
|
|
358
|
+
*
|
|
359
|
+
* Represents a HTML attribute. Can contain either a fixed static value or a
|
|
360
|
+
* placeholder for dynamic values (e.g. interpolated).
|
|
361
|
+
*/
|
|
362
|
+
class Attribute {
|
|
363
|
+
/**
|
|
364
|
+
* @param key - Attribute name.
|
|
365
|
+
* @param value - Attribute value. Set to `null` for boolean attributes.
|
|
366
|
+
* @param keyLocation - Source location of attribute name.
|
|
367
|
+
* @param valueLocation - Source location of attribute value.
|
|
368
|
+
* @param originalAttribute - If this attribute was dynamically added via a
|
|
369
|
+
* transformation (e.g. vuejs `:id` generating the `id` attribute) this
|
|
370
|
+
* parameter should be set to the attribute name of the source attribute (`:id`).
|
|
371
|
+
*/
|
|
372
|
+
constructor(key, value, keyLocation, valueLocation, originalAttribute) {
|
|
373
|
+
this.key = key;
|
|
374
|
+
this.value = value;
|
|
375
|
+
this.keyLocation = keyLocation;
|
|
376
|
+
this.valueLocation = valueLocation;
|
|
377
|
+
this.originalAttribute = originalAttribute;
|
|
378
|
+
/* force undefined to null */
|
|
379
|
+
if (typeof this.value === "undefined") {
|
|
380
|
+
this.value = null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Flag set to true if the attribute value is static.
|
|
385
|
+
*/
|
|
386
|
+
get isStatic() {
|
|
387
|
+
return !this.isDynamic;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Flag set to true if the attribute value is dynamic.
|
|
391
|
+
*/
|
|
392
|
+
get isDynamic() {
|
|
393
|
+
return this.value instanceof DynamicValue;
|
|
394
|
+
}
|
|
395
|
+
valueMatches(pattern, dynamicMatches = true) {
|
|
396
|
+
if (this.value === null) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
/* dynamic values matches everything */
|
|
400
|
+
if (this.value instanceof DynamicValue) {
|
|
401
|
+
return dynamicMatches;
|
|
402
|
+
}
|
|
403
|
+
/* test value against pattern */
|
|
404
|
+
if (pattern instanceof RegExp) {
|
|
405
|
+
return this.value.match(pattern) !== null;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
return this.value === pattern;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
364
412
|
|
|
413
|
+
function getCSSDeclarations(value) {
|
|
414
|
+
return value
|
|
415
|
+
.trim()
|
|
416
|
+
.split(";")
|
|
417
|
+
.filter(Boolean)
|
|
418
|
+
.map((it) => {
|
|
419
|
+
const [property, value] = it.split(":", 2);
|
|
420
|
+
return [property.trim(), value ? value.trim() : ""];
|
|
421
|
+
});
|
|
422
|
+
}
|
|
365
423
|
/**
|
|
366
|
-
*
|
|
424
|
+
* @internal
|
|
367
425
|
*/
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
426
|
+
function parseCssDeclaration(value) {
|
|
427
|
+
if (!value || value instanceof DynamicValue) {
|
|
428
|
+
return {};
|
|
429
|
+
}
|
|
430
|
+
const pairs = getCSSDeclarations(value);
|
|
431
|
+
return Object.fromEntries(pairs);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function sliceSize(size, begin, end) {
|
|
435
|
+
if (typeof size !== "number") {
|
|
436
|
+
return size;
|
|
437
|
+
}
|
|
438
|
+
if (typeof end !== "number") {
|
|
439
|
+
return size - begin;
|
|
440
|
+
}
|
|
441
|
+
if (end < 0) {
|
|
442
|
+
end = size + end;
|
|
443
|
+
}
|
|
444
|
+
return Math.min(size, end - begin);
|
|
445
|
+
}
|
|
446
|
+
function sliceLocation(location, begin, end, wrap) {
|
|
447
|
+
if (!location)
|
|
448
|
+
return null;
|
|
449
|
+
const size = sliceSize(location.size, begin, end);
|
|
450
|
+
const sliced = {
|
|
451
|
+
filename: location.filename,
|
|
452
|
+
offset: location.offset + begin,
|
|
453
|
+
line: location.line,
|
|
454
|
+
column: location.column + begin,
|
|
455
|
+
size,
|
|
456
|
+
};
|
|
457
|
+
/* if text content is provided try to find all newlines and modify line/column accordingly */
|
|
458
|
+
if (wrap) {
|
|
459
|
+
let index = -1;
|
|
460
|
+
const col = sliced.column;
|
|
461
|
+
do {
|
|
462
|
+
index = wrap.indexOf("\n", index + 1);
|
|
463
|
+
if (index >= 0 && index < begin) {
|
|
464
|
+
sliced.column = col - (index + 1);
|
|
465
|
+
sliced.line++;
|
|
377
466
|
}
|
|
467
|
+
else {
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
} while (true); // eslint-disable-line no-constant-condition
|
|
471
|
+
}
|
|
472
|
+
return sliced;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
var State;
|
|
476
|
+
(function (State) {
|
|
477
|
+
State[State["INITIAL"] = 1] = "INITIAL";
|
|
478
|
+
State[State["DOCTYPE"] = 2] = "DOCTYPE";
|
|
479
|
+
State[State["TEXT"] = 3] = "TEXT";
|
|
480
|
+
State[State["TAG"] = 4] = "TAG";
|
|
481
|
+
State[State["ATTR"] = 5] = "ATTR";
|
|
482
|
+
State[State["CDATA"] = 6] = "CDATA";
|
|
483
|
+
State[State["SCRIPT"] = 7] = "SCRIPT";
|
|
484
|
+
State[State["STYLE"] = 8] = "STYLE";
|
|
485
|
+
})(State || (State = {}));
|
|
486
|
+
|
|
487
|
+
var ContentModel;
|
|
488
|
+
(function (ContentModel) {
|
|
489
|
+
ContentModel[ContentModel["TEXT"] = 1] = "TEXT";
|
|
490
|
+
ContentModel[ContentModel["SCRIPT"] = 2] = "SCRIPT";
|
|
491
|
+
ContentModel[ContentModel["STYLE"] = 3] = "STYLE";
|
|
492
|
+
})(ContentModel || (ContentModel = {}));
|
|
493
|
+
class Context {
|
|
494
|
+
constructor(source) {
|
|
495
|
+
var _a, _b, _c, _d;
|
|
496
|
+
this.state = State.INITIAL;
|
|
497
|
+
this.string = source.data;
|
|
498
|
+
this.filename = (_a = source.filename) !== null && _a !== void 0 ? _a : "";
|
|
499
|
+
this.offset = (_b = source.offset) !== null && _b !== void 0 ? _b : 0;
|
|
500
|
+
this.line = (_c = source.line) !== null && _c !== void 0 ? _c : 1;
|
|
501
|
+
this.column = (_d = source.column) !== null && _d !== void 0 ? _d : 1;
|
|
502
|
+
this.contentModel = ContentModel.TEXT;
|
|
503
|
+
}
|
|
504
|
+
getTruncatedLine(n = 13) {
|
|
505
|
+
return JSON.stringify(this.string.length > n ? `${this.string.slice(0, 10)}...` : this.string);
|
|
506
|
+
}
|
|
507
|
+
consume(n, state) {
|
|
508
|
+
/* if "n" is an regex match the first value is the full matched
|
|
509
|
+
* string so consume that many characters. */
|
|
510
|
+
if (typeof n !== "number") {
|
|
511
|
+
n = n[0].length; /* regex match */
|
|
512
|
+
}
|
|
513
|
+
/* poor mans line counter :( */
|
|
514
|
+
let consumed = this.string.slice(0, n);
|
|
515
|
+
let offset;
|
|
516
|
+
while ((offset = consumed.indexOf("\n")) >= 0) {
|
|
517
|
+
this.line++;
|
|
518
|
+
this.column = 1;
|
|
519
|
+
consumed = consumed.substr(offset + 1);
|
|
378
520
|
}
|
|
521
|
+
this.column += consumed.length;
|
|
522
|
+
this.offset += n;
|
|
523
|
+
/* remove N chars */
|
|
524
|
+
this.string = this.string.substr(n);
|
|
525
|
+
/* change state */
|
|
526
|
+
this.state = state;
|
|
527
|
+
}
|
|
528
|
+
getLocation(size) {
|
|
529
|
+
return {
|
|
530
|
+
filename: this.filename,
|
|
531
|
+
offset: this.offset,
|
|
532
|
+
line: this.line,
|
|
533
|
+
column: this.column,
|
|
534
|
+
size,
|
|
535
|
+
};
|
|
379
536
|
}
|
|
380
|
-
/* remove old module from cache */
|
|
381
|
-
delete legacyRequire.cache[filename];
|
|
382
|
-
return legacyRequire(filename);
|
|
383
537
|
}
|
|
384
538
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
},
|
|
671
|
-
deprecated: {
|
|
672
|
-
title: "Set to true or string if this attribute is deprecated",
|
|
673
|
-
oneOf: [
|
|
674
|
-
{
|
|
675
|
-
type: "boolean"
|
|
676
|
-
},
|
|
677
|
-
{
|
|
678
|
-
type: "string"
|
|
679
|
-
}
|
|
680
|
-
]
|
|
681
|
-
},
|
|
682
|
-
list: {
|
|
683
|
-
type: "boolean",
|
|
684
|
-
title: "Set to true if this attribute is a list of space-separated tokens, each which must be valid by itself"
|
|
685
|
-
},
|
|
686
|
-
"enum": {
|
|
687
|
-
type: "array",
|
|
688
|
-
title: "Exhaustive list of values (string or regex) this attribute accepts",
|
|
689
|
-
uniqueItems: true,
|
|
690
|
-
items: {
|
|
691
|
-
anyOf: [
|
|
692
|
-
{
|
|
693
|
-
type: "string"
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
regexp: true
|
|
697
|
-
}
|
|
698
|
-
]
|
|
699
|
-
}
|
|
700
|
-
},
|
|
701
|
-
omit: {
|
|
702
|
-
type: "boolean",
|
|
703
|
-
title: "Set to true if this attribute can optionally omit its value"
|
|
704
|
-
},
|
|
705
|
-
required: {
|
|
706
|
-
type: "boolean",
|
|
707
|
-
title: "Set to true if this attribute is required"
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
},
|
|
711
|
-
{
|
|
712
|
-
type: "array",
|
|
713
|
-
uniqueItems: true,
|
|
714
|
-
items: {
|
|
715
|
-
type: "string"
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
]
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
},
|
|
722
|
-
PermittedGroup: {
|
|
723
|
-
type: "object",
|
|
724
|
-
additionalProperties: false,
|
|
725
|
-
properties: {
|
|
726
|
-
exclude: {
|
|
727
|
-
anyOf: [
|
|
728
|
-
{
|
|
729
|
-
items: {
|
|
730
|
-
type: "string"
|
|
731
|
-
},
|
|
732
|
-
type: "array"
|
|
733
|
-
},
|
|
734
|
-
{
|
|
735
|
-
type: "string"
|
|
736
|
-
}
|
|
737
|
-
]
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
},
|
|
741
|
-
PermittedOrder: {
|
|
742
|
-
type: "array",
|
|
743
|
-
items: {
|
|
744
|
-
type: "string"
|
|
745
|
-
}
|
|
746
|
-
},
|
|
747
|
-
RequiredAncestors: {
|
|
748
|
-
type: "array",
|
|
749
|
-
items: {
|
|
750
|
-
type: "string"
|
|
751
|
-
}
|
|
752
|
-
},
|
|
753
|
-
RequiredContent: {
|
|
754
|
-
type: "array",
|
|
755
|
-
items: {
|
|
756
|
-
type: "string"
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
var schema = {
|
|
761
|
-
$schema: $schema$1,
|
|
762
|
-
$id: $id$1,
|
|
763
|
-
type: type$1,
|
|
764
|
-
properties: properties$1,
|
|
765
|
-
patternProperties: patternProperties,
|
|
766
|
-
definitions: definitions
|
|
767
|
-
};
|
|
539
|
+
var TextContent$1;
|
|
540
|
+
(function (TextContent) {
|
|
541
|
+
/* forbid node to have text content, inter-element whitespace is ignored */
|
|
542
|
+
TextContent["NONE"] = "none";
|
|
543
|
+
/* node can have text but not required too */
|
|
544
|
+
TextContent["DEFAULT"] = "default";
|
|
545
|
+
/* node requires text-nodes to be present (direct or by descendant) */
|
|
546
|
+
TextContent["REQUIRED"] = "required";
|
|
547
|
+
/* node requires accessible text (hidden text is ignored, tries to get text from accessibility tree) */
|
|
548
|
+
TextContent["ACCESSIBLE"] = "accessible";
|
|
549
|
+
})(TextContent$1 || (TextContent$1 = {}));
|
|
550
|
+
/**
|
|
551
|
+
* Properties listed here can be copied (loaded) onto another element using
|
|
552
|
+
* [[HtmlElement.loadMeta]].
|
|
553
|
+
*
|
|
554
|
+
* @public
|
|
555
|
+
*/
|
|
556
|
+
const MetaCopyableProperty = [
|
|
557
|
+
"metadata",
|
|
558
|
+
"flow",
|
|
559
|
+
"sectioning",
|
|
560
|
+
"heading",
|
|
561
|
+
"phrasing",
|
|
562
|
+
"embedded",
|
|
563
|
+
"interactive",
|
|
564
|
+
"transparent",
|
|
565
|
+
"form",
|
|
566
|
+
"labelable",
|
|
567
|
+
"attributes",
|
|
568
|
+
"permittedContent",
|
|
569
|
+
"permittedDescendants",
|
|
570
|
+
"permittedOrder",
|
|
571
|
+
"permittedParent",
|
|
572
|
+
"requiredAncestors",
|
|
573
|
+
"requiredContent",
|
|
574
|
+
];
|
|
575
|
+
/**
|
|
576
|
+
* @internal
|
|
577
|
+
*/
|
|
578
|
+
function setMetaProperty(dst, key, value) {
|
|
579
|
+
dst[key] = value;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
var NodeType;
|
|
583
|
+
(function (NodeType) {
|
|
584
|
+
NodeType[NodeType["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
|
|
585
|
+
NodeType[NodeType["TEXT_NODE"] = 3] = "TEXT_NODE";
|
|
586
|
+
NodeType[NodeType["DOCUMENT_NODE"] = 9] = "DOCUMENT_NODE";
|
|
587
|
+
})(NodeType || (NodeType = {}));
|
|
588
|
+
|
|
589
|
+
const DOCUMENT_NODE_NAME = "#document";
|
|
590
|
+
const TEXT_CONTENT = Symbol("textContent");
|
|
591
|
+
let counter = 0;
|
|
592
|
+
class DOMNode {
|
|
593
|
+
/**
|
|
594
|
+
* Create a new DOMNode.
|
|
595
|
+
*
|
|
596
|
+
* @param nodeType - What node type to create.
|
|
597
|
+
* @param nodeName - What node name to use. For `HtmlElement` this corresponds
|
|
598
|
+
* to the tagName but other node types have specific predefined values.
|
|
599
|
+
* @param location - Source code location of this node.
|
|
600
|
+
*/
|
|
601
|
+
constructor(nodeType, nodeName, location) {
|
|
602
|
+
this.nodeType = nodeType;
|
|
603
|
+
this.nodeName = nodeName !== null && nodeName !== void 0 ? nodeName : DOCUMENT_NODE_NAME;
|
|
604
|
+
this.location = location;
|
|
605
|
+
this.disabledRules = new Set();
|
|
606
|
+
this.childNodes = [];
|
|
607
|
+
this.unique = counter++;
|
|
608
|
+
this.cache = null;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Enable cache for this node.
|
|
612
|
+
*
|
|
613
|
+
* Should not be called before the node and all children are fully constructed.
|
|
614
|
+
*/
|
|
615
|
+
cacheEnable() {
|
|
616
|
+
this.cache = new Map();
|
|
617
|
+
}
|
|
618
|
+
cacheGet(key) {
|
|
619
|
+
if (this.cache) {
|
|
620
|
+
return this.cache.get(key);
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
cacheSet(key, value) {
|
|
627
|
+
if (this.cache) {
|
|
628
|
+
this.cache.set(key, value);
|
|
629
|
+
}
|
|
630
|
+
return value;
|
|
631
|
+
}
|
|
632
|
+
cacheRemove(key) {
|
|
633
|
+
if (this.cache) {
|
|
634
|
+
return this.cache.delete(key);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
cacheExists(key) {
|
|
641
|
+
return Boolean(this.cache && this.cache.has(key));
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Get the text (recursive) from all child nodes.
|
|
645
|
+
*/
|
|
646
|
+
get textContent() {
|
|
647
|
+
const cached = this.cacheGet(TEXT_CONTENT);
|
|
648
|
+
if (cached) {
|
|
649
|
+
return cached;
|
|
650
|
+
}
|
|
651
|
+
const text = this.childNodes.map((node) => node.textContent).join("");
|
|
652
|
+
this.cacheSet(TEXT_CONTENT, text);
|
|
653
|
+
return text;
|
|
654
|
+
}
|
|
655
|
+
append(node) {
|
|
656
|
+
this.childNodes.push(node);
|
|
657
|
+
}
|
|
658
|
+
isRootElement() {
|
|
659
|
+
return this.nodeType === NodeType.DOCUMENT_NODE;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Tests if two nodes are the same (references the same object).
|
|
663
|
+
*/
|
|
664
|
+
isSameNode(otherNode) {
|
|
665
|
+
return this.unique === otherNode.unique;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Returns a DOMNode representing the first direct child node or `null` if the
|
|
669
|
+
* node has no children.
|
|
670
|
+
*/
|
|
671
|
+
get firstChild() {
|
|
672
|
+
return this.childNodes[0] || null;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Returns a DOMNode representing the last direct child node or `null` if the
|
|
676
|
+
* node has no children.
|
|
677
|
+
*/
|
|
678
|
+
get lastChild() {
|
|
679
|
+
return this.childNodes[this.childNodes.length - 1] || null;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Disable a rule for this node.
|
|
683
|
+
*/
|
|
684
|
+
disableRule(ruleId) {
|
|
685
|
+
this.disabledRules.add(ruleId);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Disables multiple rules.
|
|
689
|
+
*/
|
|
690
|
+
disableRules(rules) {
|
|
691
|
+
for (const rule of rules) {
|
|
692
|
+
this.disableRule(rule);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Enable a previously disabled rule for this node.
|
|
697
|
+
*/
|
|
698
|
+
enableRule(ruleId) {
|
|
699
|
+
this.disabledRules.delete(ruleId);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Enables multiple rules.
|
|
703
|
+
*/
|
|
704
|
+
enableRules(rules) {
|
|
705
|
+
for (const rule of rules) {
|
|
706
|
+
this.enableRule(rule);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Test if a rule is enabled for this node.
|
|
711
|
+
*/
|
|
712
|
+
ruleEnabled(ruleId) {
|
|
713
|
+
return !this.disabledRules.has(ruleId);
|
|
714
|
+
}
|
|
715
|
+
generateSelector() {
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function parse(text, baseLocation) {
|
|
721
|
+
const tokens = [];
|
|
722
|
+
const locations = baseLocation ? [] : null;
|
|
723
|
+
for (let begin = 0; begin < text.length;) {
|
|
724
|
+
let end = text.indexOf(" ", begin);
|
|
725
|
+
/* if the last space was found move the position to the last character
|
|
726
|
+
* in the string */
|
|
727
|
+
if (end === -1) {
|
|
728
|
+
end = text.length;
|
|
729
|
+
}
|
|
730
|
+
/* handle multiple spaces */
|
|
731
|
+
const size = end - begin;
|
|
732
|
+
if (size === 0) {
|
|
733
|
+
begin++;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
/* extract token */
|
|
737
|
+
const token = text.substring(begin, end);
|
|
738
|
+
tokens.push(token);
|
|
739
|
+
/* extract location */
|
|
740
|
+
if (locations && baseLocation) {
|
|
741
|
+
const location = sliceLocation(baseLocation, begin, end);
|
|
742
|
+
locations.push(location);
|
|
743
|
+
}
|
|
744
|
+
/* advance position to the character after the current end position */
|
|
745
|
+
begin += size + 1;
|
|
746
|
+
}
|
|
747
|
+
return { tokens, locations };
|
|
748
|
+
}
|
|
749
|
+
class DOMTokenList extends Array {
|
|
750
|
+
constructor(value, location) {
|
|
751
|
+
if (value && typeof value === "string") {
|
|
752
|
+
/* replace all whitespace with a single space for easier parsing */
|
|
753
|
+
const normalized = value.replace(/[\t\r\n]/g, " ");
|
|
754
|
+
const { tokens, locations } = parse(normalized, location);
|
|
755
|
+
super(...tokens);
|
|
756
|
+
this.locations = locations;
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
super(0);
|
|
760
|
+
this.locations = null;
|
|
761
|
+
}
|
|
762
|
+
if (value instanceof DynamicValue) {
|
|
763
|
+
this.value = value.expr;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
this.value = value || "";
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
item(n) {
|
|
770
|
+
return this[n];
|
|
771
|
+
}
|
|
772
|
+
location(n) {
|
|
773
|
+
if (this.locations) {
|
|
774
|
+
return this.locations[n];
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
throw new Error("Trying to access DOMTokenList location when base location isn't set");
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
contains(token) {
|
|
781
|
+
return this.includes(token);
|
|
782
|
+
}
|
|
783
|
+
*iterator() {
|
|
784
|
+
for (let index = 0; index < this.length; index++) {
|
|
785
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
786
|
+
const item = this.item(index);
|
|
787
|
+
const location = this.location(index);
|
|
788
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
789
|
+
yield { index, item, location };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
var Combinator;
|
|
795
|
+
(function (Combinator) {
|
|
796
|
+
Combinator[Combinator["DESCENDANT"] = 1] = "DESCENDANT";
|
|
797
|
+
Combinator[Combinator["CHILD"] = 2] = "CHILD";
|
|
798
|
+
Combinator[Combinator["ADJACENT_SIBLING"] = 3] = "ADJACENT_SIBLING";
|
|
799
|
+
Combinator[Combinator["GENERAL_SIBLING"] = 4] = "GENERAL_SIBLING";
|
|
800
|
+
/* special cases */
|
|
801
|
+
Combinator[Combinator["SCOPE"] = 5] = "SCOPE";
|
|
802
|
+
})(Combinator || (Combinator = {}));
|
|
803
|
+
function parseCombinator(combinator, pattern) {
|
|
804
|
+
/* special case, when pattern is :scope [[Selector]] will handle this
|
|
805
|
+
* "combinator" to match itself instead of descendants */
|
|
806
|
+
if (pattern === ":scope") {
|
|
807
|
+
return Combinator.SCOPE;
|
|
808
|
+
}
|
|
809
|
+
switch (combinator) {
|
|
810
|
+
case undefined:
|
|
811
|
+
case null:
|
|
812
|
+
case "":
|
|
813
|
+
return Combinator.DESCENDANT;
|
|
814
|
+
case ">":
|
|
815
|
+
return Combinator.CHILD;
|
|
816
|
+
case "+":
|
|
817
|
+
return Combinator.ADJACENT_SIBLING;
|
|
818
|
+
case "~":
|
|
819
|
+
return Combinator.GENERAL_SIBLING;
|
|
820
|
+
default:
|
|
821
|
+
throw new Error(`Unknown combinator "${combinator}"`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
768
824
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
keyword: "type",
|
|
782
|
-
message: "should be a regular expression",
|
|
783
|
-
params: {
|
|
784
|
-
keyword: "type",
|
|
785
|
-
},
|
|
786
|
-
},
|
|
787
|
-
];
|
|
825
|
+
function firstChild(node) {
|
|
826
|
+
return node.previousSibling === null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function lastChild(node) {
|
|
830
|
+
return node.nextSibling === null;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const cache = {};
|
|
834
|
+
function getNthChild(node) {
|
|
835
|
+
if (!node.parent) {
|
|
836
|
+
return -1;
|
|
788
837
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
838
|
+
if (!cache[node.unique]) {
|
|
839
|
+
const parent = node.parent;
|
|
840
|
+
const index = parent.childElements.findIndex((cur) => {
|
|
841
|
+
return cur.unique === node.unique;
|
|
842
|
+
});
|
|
843
|
+
cache[node.unique] = index + 1; /* nthChild starts at 1 */
|
|
844
|
+
}
|
|
845
|
+
return cache[node.unique];
|
|
846
|
+
}
|
|
847
|
+
function nthChild(node, args) {
|
|
848
|
+
if (!args) {
|
|
849
|
+
throw new Error("Missing argument to nth-child");
|
|
850
|
+
}
|
|
851
|
+
const n = parseInt(args.trim(), 10);
|
|
852
|
+
const cur = getNthChild(node);
|
|
853
|
+
return cur === n;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function scope(node) {
|
|
857
|
+
return node.isSameNode(this.scope);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const table = {
|
|
861
|
+
"first-child": firstChild,
|
|
862
|
+
"last-child": lastChild,
|
|
863
|
+
"nth-child": nthChild,
|
|
864
|
+
scope: scope,
|
|
796
865
|
};
|
|
866
|
+
function factory(name, context) {
|
|
867
|
+
const fn = table[name];
|
|
868
|
+
if (fn) {
|
|
869
|
+
return fn.bind(context);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
throw new Error(`Pseudo-class "${name}" is not implemented`);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
797
875
|
|
|
798
|
-
var TextContent$1;
|
|
799
|
-
(function (TextContent) {
|
|
800
|
-
/* forbid node to have text content, inter-element whitespace is ignored */
|
|
801
|
-
TextContent["NONE"] = "none";
|
|
802
|
-
/* node can have text but not required too */
|
|
803
|
-
TextContent["DEFAULT"] = "default";
|
|
804
|
-
/* node requires text-nodes to be present (direct or by descendant) */
|
|
805
|
-
TextContent["REQUIRED"] = "required";
|
|
806
|
-
/* node requires accessible text (hidden text is ignored, tries to get text from accessibility tree) */
|
|
807
|
-
TextContent["ACCESSIBLE"] = "accessible";
|
|
808
|
-
})(TextContent$1 || (TextContent$1 = {}));
|
|
809
876
|
/**
|
|
810
|
-
*
|
|
811
|
-
* [[HtmlElement.loadMeta]].
|
|
877
|
+
* Homage to PHP: unescapes slashes.
|
|
812
878
|
*
|
|
813
|
-
*
|
|
879
|
+
* E.g. "foo\:bar" becomes "foo:bar"
|
|
814
880
|
*/
|
|
815
|
-
|
|
816
|
-
"
|
|
817
|
-
|
|
818
|
-
"sectioning",
|
|
819
|
-
"heading",
|
|
820
|
-
"phrasing",
|
|
821
|
-
"embedded",
|
|
822
|
-
"interactive",
|
|
823
|
-
"transparent",
|
|
824
|
-
"form",
|
|
825
|
-
"labelable",
|
|
826
|
-
"attributes",
|
|
827
|
-
"permittedContent",
|
|
828
|
-
"permittedDescendants",
|
|
829
|
-
"permittedOrder",
|
|
830
|
-
"permittedParent",
|
|
831
|
-
"requiredAncestors",
|
|
832
|
-
"requiredContent",
|
|
833
|
-
];
|
|
881
|
+
function stripslashes(value) {
|
|
882
|
+
return value.replace(/\\(.)/g, "$1");
|
|
883
|
+
}
|
|
834
884
|
/**
|
|
835
885
|
* @internal
|
|
836
886
|
*/
|
|
837
|
-
function
|
|
838
|
-
|
|
887
|
+
function escapeSelectorComponent(text) {
|
|
888
|
+
return text.toString().replace(/([^a-z0-9_-])/gi, "\\$1");
|
|
839
889
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
890
|
+
/**
|
|
891
|
+
* @internal
|
|
892
|
+
*/
|
|
893
|
+
function generateIdSelector(id) {
|
|
894
|
+
const escaped = escapeSelectorComponent(id);
|
|
895
|
+
return escaped.match(/^\d/) ? `[id="${escaped}"]` : `#${escaped}`;
|
|
843
896
|
}
|
|
844
|
-
|
|
845
|
-
|
|
897
|
+
/**
|
|
898
|
+
* Returns true if the character is a delimiter for different kinds of selectors:
|
|
899
|
+
*
|
|
900
|
+
* - `.` - begins a class selector
|
|
901
|
+
* - `#` - begins an id selector
|
|
902
|
+
* - `[` - begins an attribute selector
|
|
903
|
+
* - `:` - begins a pseudo class or element selector
|
|
904
|
+
*/
|
|
905
|
+
function isDelimiter(ch) {
|
|
906
|
+
return /[.#[:]/.test(ch);
|
|
846
907
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
908
|
+
/**
|
|
909
|
+
* Returns true if the character is a quotation mark.
|
|
910
|
+
*/
|
|
911
|
+
function isQuotationMark(ch) {
|
|
912
|
+
return /['"]/.test(ch);
|
|
850
913
|
}
|
|
851
|
-
function
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
if (
|
|
859
|
-
return
|
|
860
|
-
}
|
|
861
|
-
/* when the attribute is set to null we use a special property "delete" to
|
|
862
|
-
* flag it, if it is still set during merge (inheritance, overwriting, etc) the attribute will be removed */
|
|
863
|
-
if (attr === null) {
|
|
864
|
-
result.delete = true;
|
|
865
|
-
return stripUndefined(result);
|
|
914
|
+
function isPseudoElement(ch, buffer) {
|
|
915
|
+
return ch === ":" && buffer === ":";
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* @internal
|
|
919
|
+
*/
|
|
920
|
+
function* splitPattern(pattern) {
|
|
921
|
+
if (pattern === "") {
|
|
922
|
+
return;
|
|
866
923
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
924
|
+
const end = pattern.length;
|
|
925
|
+
let begin = 0;
|
|
926
|
+
let cur = 1;
|
|
927
|
+
let quoted = false;
|
|
928
|
+
while (cur < end) {
|
|
929
|
+
const ch = pattern[cur];
|
|
930
|
+
const buffer = pattern.slice(begin, cur);
|
|
931
|
+
/* escaped character, ignore whatever is next */
|
|
932
|
+
if (ch === "\\") {
|
|
933
|
+
cur += 2;
|
|
934
|
+
continue;
|
|
870
935
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (
|
|
874
|
-
|
|
936
|
+
/* if inside quoted string we only look for the end quotation mark */
|
|
937
|
+
if (quoted) {
|
|
938
|
+
if (ch === quoted) {
|
|
939
|
+
quoted = false;
|
|
875
940
|
}
|
|
941
|
+
cur += 1;
|
|
942
|
+
continue;
|
|
876
943
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
944
|
+
/* if the character is a quotation mark we store the character and the above
|
|
945
|
+
* condition will look for a similar end quotation mark */
|
|
946
|
+
if (isQuotationMark(ch)) {
|
|
947
|
+
quoted = ch;
|
|
948
|
+
cur += 1;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
/* special case when using :: pseudo element selector */
|
|
952
|
+
if (isPseudoElement(ch, buffer)) {
|
|
953
|
+
cur += 1;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
/* if the character is a delimiter we yield the string and reset the
|
|
957
|
+
* position */
|
|
958
|
+
if (isDelimiter(ch)) {
|
|
959
|
+
begin = cur;
|
|
960
|
+
yield buffer;
|
|
961
|
+
}
|
|
962
|
+
cur += 1;
|
|
881
963
|
}
|
|
964
|
+
/* yield the rest of the string */
|
|
965
|
+
const tail = pattern.slice(begin, cur);
|
|
966
|
+
yield tail;
|
|
882
967
|
}
|
|
883
|
-
|
|
884
|
-
var _a, _b, _c;
|
|
885
|
-
const keys = [
|
|
886
|
-
...Object.keys((_a = src.attributes) !== null && _a !== void 0 ? _a : {}),
|
|
887
|
-
...((_b = src.requiredAttributes) !== null && _b !== void 0 ? _b : []),
|
|
888
|
-
...((_c = src.deprecatedAttributes) !== null && _c !== void 0 ? _c : []),
|
|
889
|
-
].sort();
|
|
890
|
-
const entries = keys.map((key) => {
|
|
891
|
-
return [key, migrateSingleAttribute(src, key)];
|
|
892
|
-
});
|
|
893
|
-
return Object.fromEntries(entries);
|
|
894
|
-
}
|
|
895
|
-
function migrateElement(src) {
|
|
896
|
-
const result = {
|
|
897
|
-
...src,
|
|
898
|
-
attributes: migrateAttributes(src),
|
|
899
|
-
};
|
|
900
|
-
/* removed properties */
|
|
901
|
-
delete result.deprecatedAttributes;
|
|
902
|
-
delete result.requiredAttributes;
|
|
903
|
-
return result;
|
|
968
|
+
class Matcher {
|
|
904
969
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
let cur = node.parent;
|
|
913
|
-
while (cur && !cur.isRootElement()) {
|
|
914
|
-
if (cur.is(tagName)) {
|
|
915
|
-
return true;
|
|
916
|
-
}
|
|
917
|
-
cur = cur.parent;
|
|
970
|
+
class ClassMatcher extends Matcher {
|
|
971
|
+
constructor(classname) {
|
|
972
|
+
super();
|
|
973
|
+
this.classname = classname;
|
|
974
|
+
}
|
|
975
|
+
match(node) {
|
|
976
|
+
return node.classList.contains(this.classname);
|
|
918
977
|
}
|
|
919
|
-
return false;
|
|
920
978
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
979
|
+
class IdMatcher extends Matcher {
|
|
980
|
+
constructor(id) {
|
|
981
|
+
super();
|
|
982
|
+
this.id = stripslashes(id);
|
|
983
|
+
}
|
|
984
|
+
match(node) {
|
|
985
|
+
return node.id === this.id;
|
|
986
|
+
}
|
|
928
987
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
988
|
+
class AttrMatcher extends Matcher {
|
|
989
|
+
constructor(attr) {
|
|
990
|
+
super();
|
|
991
|
+
const [, key, op, value] = attr.match(/^(.+?)(?:([~^$*|]?=)"([^"]+?)")?$/);
|
|
992
|
+
this.key = key;
|
|
993
|
+
this.op = op;
|
|
994
|
+
this.value = value;
|
|
995
|
+
}
|
|
996
|
+
match(node) {
|
|
997
|
+
const attr = node.getAttribute(this.key, true) || [];
|
|
998
|
+
return attr.some((cur) => {
|
|
999
|
+
switch (this.op) {
|
|
1000
|
+
case undefined:
|
|
1001
|
+
return true; /* attribute exists */
|
|
1002
|
+
case "=":
|
|
1003
|
+
return cur.value === this.value;
|
|
1004
|
+
default:
|
|
1005
|
+
throw new Error(`Attribute selector operator ${this.op} is not implemented yet`);
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
940
1008
|
}
|
|
941
1009
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
};
|
|
958
|
-
const schemaCache = new Map();
|
|
959
|
-
function clone(src) {
|
|
960
|
-
return JSON.parse(JSON.stringify(src));
|
|
1010
|
+
class PseudoClassMatcher extends Matcher {
|
|
1011
|
+
constructor(pseudoclass, context) {
|
|
1012
|
+
super();
|
|
1013
|
+
const match = pseudoclass.match(/^([^(]+)(?:\((.*)\))?$/);
|
|
1014
|
+
if (!match) {
|
|
1015
|
+
throw new Error(`Missing pseudo-class after colon in selector pattern "${context}"`);
|
|
1016
|
+
}
|
|
1017
|
+
const [, name, args] = match;
|
|
1018
|
+
this.name = name;
|
|
1019
|
+
this.args = args;
|
|
1020
|
+
}
|
|
1021
|
+
match(node, context) {
|
|
1022
|
+
const fn = factory(this.name, context);
|
|
1023
|
+
return fn(node, this.args);
|
|
1024
|
+
}
|
|
961
1025
|
}
|
|
962
|
-
|
|
963
|
-
|
|
1026
|
+
class Pattern {
|
|
1027
|
+
constructor(pattern) {
|
|
1028
|
+
const match = pattern.match(/^([~+\->]?)((?:[*]|[^.#[:]+)?)(.*)$/);
|
|
1029
|
+
match.shift(); /* remove full matched string */
|
|
1030
|
+
this.selector = pattern;
|
|
1031
|
+
this.combinator = parseCombinator(match.shift(), pattern);
|
|
1032
|
+
this.tagName = match.shift() || "*";
|
|
1033
|
+
this.pattern = Array.from(splitPattern(match[0]), (it) => this.createMatcher(it));
|
|
1034
|
+
}
|
|
1035
|
+
match(node, context) {
|
|
1036
|
+
return node.is(this.tagName) && this.pattern.every((cur) => cur.match(node, context));
|
|
1037
|
+
}
|
|
1038
|
+
createMatcher(pattern) {
|
|
1039
|
+
switch (pattern[0]) {
|
|
1040
|
+
case ".":
|
|
1041
|
+
return new ClassMatcher(pattern.slice(1));
|
|
1042
|
+
case "#":
|
|
1043
|
+
return new IdMatcher(pattern.slice(1));
|
|
1044
|
+
case "[":
|
|
1045
|
+
return new AttrMatcher(pattern.slice(1, -1));
|
|
1046
|
+
case ":":
|
|
1047
|
+
return new PseudoClassMatcher(pattern.slice(1), this.selector);
|
|
1048
|
+
default:
|
|
1049
|
+
/* istanbul ignore next: fallback solution, the switch cases should cover
|
|
1050
|
+
* everything and there is no known way to trigger this fallback */
|
|
1051
|
+
throw new Error(`Failed to create matcher for "${pattern}"`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
964
1054
|
}
|
|
965
1055
|
/**
|
|
966
|
-
*
|
|
1056
|
+
* DOM Selector.
|
|
967
1057
|
*/
|
|
968
|
-
class
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
*/
|
|
972
|
-
constructor() {
|
|
973
|
-
this.elements = {};
|
|
974
|
-
this.schema = clone(schema);
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* @internal
|
|
978
|
-
*/
|
|
979
|
-
init() {
|
|
980
|
-
this.resolveGlobal();
|
|
1058
|
+
class Selector {
|
|
1059
|
+
constructor(selector) {
|
|
1060
|
+
this.pattern = Selector.parse(selector);
|
|
981
1061
|
}
|
|
982
1062
|
/**
|
|
983
|
-
*
|
|
1063
|
+
* Match this selector against a HtmlElement.
|
|
984
1064
|
*
|
|
985
|
-
* @
|
|
1065
|
+
* @param root - Element to match against.
|
|
1066
|
+
* @returns Iterator with matched elements.
|
|
986
1067
|
*/
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
patternProperties: {
|
|
991
|
-
"^[^$].*$": {
|
|
992
|
-
properties: patch.properties,
|
|
993
|
-
},
|
|
994
|
-
},
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
if (patch.definitions) {
|
|
998
|
-
this.schema = deepmerge(this.schema, {
|
|
999
|
-
definitions: patch.definitions,
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1068
|
+
*match(root) {
|
|
1069
|
+
const context = { scope: root };
|
|
1070
|
+
yield* this.matchInternal(root, 0, context);
|
|
1002
1071
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
* @param obj - Object with metadata to load
|
|
1008
|
-
* @param filename - Optional filename used when presenting validation error
|
|
1009
|
-
*/
|
|
1010
|
-
loadFromObject(obj, filename = null) {
|
|
1011
|
-
var _a;
|
|
1012
|
-
const validate = this.getSchemaValidator();
|
|
1013
|
-
if (!validate(obj)) {
|
|
1014
|
-
throw new SchemaValidationError(filename, `Element metadata is not valid`, obj, this.schema,
|
|
1015
|
-
/* istanbul ignore next: AJV sets .errors when validate returns false */
|
|
1016
|
-
(_a = validate.errors) !== null && _a !== void 0 ? _a : []);
|
|
1072
|
+
*matchInternal(root, level, context) {
|
|
1073
|
+
if (level >= this.pattern.length) {
|
|
1074
|
+
yield root;
|
|
1075
|
+
return;
|
|
1017
1076
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1077
|
+
const pattern = this.pattern[level];
|
|
1078
|
+
const matches = Selector.findCandidates(root, pattern);
|
|
1079
|
+
for (const node of matches) {
|
|
1080
|
+
if (!pattern.match(node, context)) {
|
|
1020
1081
|
continue;
|
|
1021
|
-
this.addEntry(key, migrateElement(value));
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Load metadata table from filename
|
|
1026
|
-
*
|
|
1027
|
-
* @internal
|
|
1028
|
-
* @param filename - Filename to load
|
|
1029
|
-
*/
|
|
1030
|
-
loadFromFile(filename) {
|
|
1031
|
-
try {
|
|
1032
|
-
/* load using require as it can process both js and json */
|
|
1033
|
-
const data = requireUncached(filename);
|
|
1034
|
-
this.loadFromObject(data, filename);
|
|
1035
|
-
}
|
|
1036
|
-
catch (err) {
|
|
1037
|
-
if (err instanceof SchemaValidationError) {
|
|
1038
|
-
throw err;
|
|
1039
1082
|
}
|
|
1040
|
-
|
|
1083
|
+
yield* this.matchInternal(node, level + 1, context);
|
|
1041
1084
|
}
|
|
1042
1085
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1086
|
+
static parse(selector) {
|
|
1087
|
+
/* strip whitespace before combinators, "ul > li" becomes "ul >li", for
|
|
1088
|
+
* easier parsing */
|
|
1089
|
+
selector = selector.replace(/([+~>]) /g, "$1");
|
|
1090
|
+
const pattern = selector.split(/(?:(?<!\\) )+/);
|
|
1091
|
+
return pattern.map((part) => new Pattern(part));
|
|
1092
|
+
}
|
|
1093
|
+
static findCandidates(root, pattern) {
|
|
1094
|
+
switch (pattern.combinator) {
|
|
1095
|
+
case Combinator.DESCENDANT:
|
|
1096
|
+
return root.getElementsByTagName(pattern.tagName);
|
|
1097
|
+
case Combinator.CHILD:
|
|
1098
|
+
return root.childElements.filter((node) => node.is(pattern.tagName));
|
|
1099
|
+
case Combinator.ADJACENT_SIBLING:
|
|
1100
|
+
return Selector.findAdjacentSibling(root);
|
|
1101
|
+
case Combinator.GENERAL_SIBLING:
|
|
1102
|
+
return Selector.findGeneralSibling(root);
|
|
1103
|
+
case Combinator.SCOPE:
|
|
1104
|
+
return [root];
|
|
1059
1105
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
* Find all tags which has enabled given property.
|
|
1064
|
-
*
|
|
1065
|
-
* @public
|
|
1066
|
-
*/
|
|
1067
|
-
getTagsWithProperty(propName) {
|
|
1068
|
-
return Object.entries(this.elements)
|
|
1069
|
-
.filter(([, entry]) => entry[propName])
|
|
1070
|
-
.map(([tagName]) => tagName);
|
|
1106
|
+
/* istanbul ignore next: fallback solution, the switch cases should cover
|
|
1107
|
+
* everything and there is no known way to trigger this fallback */
|
|
1108
|
+
return [];
|
|
1071
1109
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1110
|
+
static findAdjacentSibling(node) {
|
|
1111
|
+
let adjacent = false;
|
|
1112
|
+
return node.siblings.filter((cur) => {
|
|
1113
|
+
if (adjacent) {
|
|
1114
|
+
adjacent = false;
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
if (cur === node) {
|
|
1118
|
+
adjacent = true;
|
|
1119
|
+
}
|
|
1120
|
+
return false;
|
|
1121
|
+
});
|
|
1081
1122
|
}
|
|
1082
|
-
|
|
1083
|
-
let
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
parent = this.elements[name];
|
|
1088
|
-
if (!parent) {
|
|
1089
|
-
throw new UserError(`Element <${tagName}> cannot inherit from <${name}>: no such element`);
|
|
1123
|
+
static findGeneralSibling(node) {
|
|
1124
|
+
let after = false;
|
|
1125
|
+
return node.siblings.filter((cur) => {
|
|
1126
|
+
if (after) {
|
|
1127
|
+
return true;
|
|
1090
1128
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1129
|
+
if (cur === node) {
|
|
1130
|
+
after = true;
|
|
1131
|
+
}
|
|
1132
|
+
return false;
|
|
1133
|
+
});
|
|
1096
1134
|
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const TEXT_NODE_NAME = "#text";
|
|
1138
|
+
/**
|
|
1139
|
+
* Returns true if the node is a text node.
|
|
1140
|
+
*
|
|
1141
|
+
* @public
|
|
1142
|
+
*/
|
|
1143
|
+
function isTextNode(node) {
|
|
1144
|
+
return Boolean(node && node.nodeType === NodeType.TEXT_NODE);
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Represents a text in the HTML document.
|
|
1148
|
+
*
|
|
1149
|
+
* Text nodes are appended as children of `HtmlElement` and cannot have childen
|
|
1150
|
+
* of its own.
|
|
1151
|
+
*
|
|
1152
|
+
* @public
|
|
1153
|
+
*/
|
|
1154
|
+
class TextNode extends DOMNode {
|
|
1097
1155
|
/**
|
|
1098
|
-
*
|
|
1156
|
+
* @param text - Text to add. When a `DynamicValue` is used the expression is
|
|
1157
|
+
* used as "text".
|
|
1158
|
+
* @param location - Source code location of this node.
|
|
1099
1159
|
*/
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (cached) {
|
|
1104
|
-
return cached;
|
|
1105
|
-
}
|
|
1106
|
-
else {
|
|
1107
|
-
const ajv = new Ajv({ strict: true, strictTuples: true, strictTypes: true });
|
|
1108
|
-
ajv.addMetaSchema(ajvSchemaDraft);
|
|
1109
|
-
ajv.addKeyword(ajvRegexpKeyword);
|
|
1110
|
-
ajv.addKeyword({ keyword: "copyable" });
|
|
1111
|
-
const validate = ajv.compile(this.schema);
|
|
1112
|
-
schemaCache.set(hash, validate);
|
|
1113
|
-
return validate;
|
|
1114
|
-
}
|
|
1160
|
+
constructor(text, location) {
|
|
1161
|
+
super(NodeType.TEXT_NODE, TEXT_NODE_NAME, location);
|
|
1162
|
+
this.text = text;
|
|
1115
1163
|
}
|
|
1116
1164
|
/**
|
|
1117
|
-
*
|
|
1165
|
+
* Get the text from node.
|
|
1118
1166
|
*/
|
|
1119
|
-
|
|
1120
|
-
return this.
|
|
1167
|
+
get textContent() {
|
|
1168
|
+
return this.text.toString();
|
|
1121
1169
|
}
|
|
1122
1170
|
/**
|
|
1123
|
-
*
|
|
1124
|
-
* global, e.g. to assign global attributes.
|
|
1171
|
+
* Flag set to true if the attribute value is static.
|
|
1125
1172
|
*/
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
if (!this.elements["*"])
|
|
1129
|
-
return;
|
|
1130
|
-
/* fetch and remove the global element, it should not be resolvable by
|
|
1131
|
-
* itself */
|
|
1132
|
-
const global = this.elements["*"];
|
|
1133
|
-
delete this.elements["*"];
|
|
1134
|
-
/* hack: unset default properties which global should not override */
|
|
1135
|
-
delete global.tagName;
|
|
1136
|
-
delete global.void;
|
|
1137
|
-
/* merge elements */
|
|
1138
|
-
for (const [tagName, entry] of Object.entries(this.elements)) {
|
|
1139
|
-
this.elements[tagName] = this.mergeElement(global, entry);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
mergeElement(a, b) {
|
|
1143
|
-
const merged = deepmerge(a, b, { arrayMerge: overwriteMerge$1 });
|
|
1144
|
-
/* special handling when removing attributes by setting them to null
|
|
1145
|
-
* resulting in the deletion flag being set */
|
|
1146
|
-
const filteredAttrs = Object.entries(merged.attributes).filter(([, attr]) => {
|
|
1147
|
-
const val = !attr.delete;
|
|
1148
|
-
delete attr.delete;
|
|
1149
|
-
return val;
|
|
1150
|
-
});
|
|
1151
|
-
merged.attributes = Object.fromEntries(filteredAttrs);
|
|
1152
|
-
return merged;
|
|
1173
|
+
get isStatic() {
|
|
1174
|
+
return !this.isDynamic;
|
|
1153
1175
|
}
|
|
1154
1176
|
/**
|
|
1155
|
-
*
|
|
1177
|
+
* Flag set to true if the attribute value is dynamic.
|
|
1156
1178
|
*/
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
expandProperties(node, node.meta);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
function expandProperties(node, entry) {
|
|
1164
|
-
for (const key of dynamicKeys) {
|
|
1165
|
-
const property = entry[key];
|
|
1166
|
-
if (property && typeof property !== "boolean") {
|
|
1167
|
-
setMetaProperty(entry, key, evaluateProperty(node, property));
|
|
1168
|
-
}
|
|
1179
|
+
get isDynamic() {
|
|
1180
|
+
return this.text instanceof DynamicValue;
|
|
1169
1181
|
}
|
|
1170
1182
|
}
|
|
1183
|
+
|
|
1171
1184
|
/**
|
|
1172
|
-
*
|
|
1173
|
-
* in /../ it creates and returns a regex instead.
|
|
1185
|
+
* @public
|
|
1174
1186
|
*/
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
return new RegExp(`^${expr}$`, flags);
|
|
1184
|
-
}
|
|
1185
|
-
else {
|
|
1186
|
-
return value;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1187
|
+
var NodeClosed;
|
|
1188
|
+
(function (NodeClosed) {
|
|
1189
|
+
NodeClosed[NodeClosed["Open"] = 0] = "Open";
|
|
1190
|
+
NodeClosed[NodeClosed["EndTag"] = 1] = "EndTag";
|
|
1191
|
+
NodeClosed[NodeClosed["VoidOmitted"] = 2] = "VoidOmitted";
|
|
1192
|
+
NodeClosed[NodeClosed["VoidSelfClosed"] = 3] = "VoidSelfClosed";
|
|
1193
|
+
NodeClosed[NodeClosed["ImplicitClosed"] = 4] = "ImplicitClosed";
|
|
1194
|
+
})(NodeClosed || (NodeClosed = {}));
|
|
1189
1195
|
/**
|
|
1190
|
-
*
|
|
1196
|
+
* Returns true if the node is an element node.
|
|
1197
|
+
*
|
|
1198
|
+
* @public
|
|
1191
1199
|
*/
|
|
1192
|
-
function
|
|
1193
|
-
|
|
1194
|
-
if (values.enum) {
|
|
1195
|
-
entry.attributes[name].enum = values.enum.map(expandRegexValue);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1200
|
+
function isElementNode(node) {
|
|
1201
|
+
return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
|
|
1198
1202
|
}
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
return func(node, options);
|
|
1203
|
+
function isValidTagName(tagName) {
|
|
1204
|
+
return Boolean(tagName !== "" && tagName !== "*");
|
|
1202
1205
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
if (!
|
|
1211
|
-
throw new Error(`
|
|
1206
|
+
/**
|
|
1207
|
+
* @public
|
|
1208
|
+
*/
|
|
1209
|
+
class HtmlElement extends DOMNode {
|
|
1210
|
+
constructor(tagName, parent, closed, meta, location) {
|
|
1211
|
+
const nodeType = tagName ? NodeType.ELEMENT_NODE : NodeType.DOCUMENT_NODE;
|
|
1212
|
+
super(nodeType, tagName, location);
|
|
1213
|
+
if (!isValidTagName(tagName)) {
|
|
1214
|
+
throw new Error(`The tag name provided ('${tagName || ""}') is not a valid name`);
|
|
1215
|
+
}
|
|
1216
|
+
this.tagName = tagName || "#document";
|
|
1217
|
+
this.parent = parent !== null && parent !== void 0 ? parent : null;
|
|
1218
|
+
this.attr = {};
|
|
1219
|
+
this.metaElement = meta !== null && meta !== void 0 ? meta : null;
|
|
1220
|
+
this.closed = closed;
|
|
1221
|
+
this.voidElement = meta ? Boolean(meta.void) : false;
|
|
1222
|
+
this.depth = 0;
|
|
1223
|
+
this.annotation = null;
|
|
1224
|
+
if (parent) {
|
|
1225
|
+
parent.childNodes.push(this);
|
|
1226
|
+
/* calculate depth in domtree */
|
|
1227
|
+
let cur = parent;
|
|
1228
|
+
while (cur.parent) {
|
|
1229
|
+
this.depth++;
|
|
1230
|
+
cur = cur.parent;
|
|
1231
|
+
}
|
|
1212
1232
|
}
|
|
1213
|
-
return [func, options];
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
function isDescendantFacade(node, tagName) {
|
|
1217
|
-
if (typeof tagName !== "string") {
|
|
1218
|
-
throw new Error(`Property expression "isDescendant" must take string argument when evaluating metadata for <${node.tagName}>`);
|
|
1219
|
-
}
|
|
1220
|
-
return isDescendant(node, tagName);
|
|
1221
|
-
}
|
|
1222
|
-
function hasAttributeFacade(node, attr) {
|
|
1223
|
-
if (typeof attr !== "string") {
|
|
1224
|
-
throw new Error(`Property expression "hasAttribute" must take string argument when evaluating metadata for <${node.tagName}>`);
|
|
1225
|
-
}
|
|
1226
|
-
return hasAttribute(node, attr);
|
|
1227
|
-
}
|
|
1228
|
-
function matchAttributeFacade(node, match) {
|
|
1229
|
-
if (!Array.isArray(match) || match.length !== 3) {
|
|
1230
|
-
throw new Error(`Property expression "matchAttribute" must take [key, op, value] array as argument when evaluating metadata for <${node.tagName}>`);
|
|
1231
|
-
}
|
|
1232
|
-
const [key, op, value] = match.map((x) => x.toLowerCase());
|
|
1233
|
-
switch (op) {
|
|
1234
|
-
case "!=":
|
|
1235
|
-
case "=":
|
|
1236
|
-
return matchAttribute(node, key, op, value);
|
|
1237
|
-
default:
|
|
1238
|
-
throw new Error(`Property expression "matchAttribute" has invalid operator "${op}" when evaluating metadata for <${node.tagName}>`);
|
|
1239
1233
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
this.expr = expr;
|
|
1234
|
+
/**
|
|
1235
|
+
* @internal
|
|
1236
|
+
*/
|
|
1237
|
+
static rootNode(location) {
|
|
1238
|
+
const root = new HtmlElement(undefined, null, NodeClosed.EndTag, null, location);
|
|
1239
|
+
root.setAnnotation("#document");
|
|
1240
|
+
return root;
|
|
1248
1241
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1242
|
+
/**
|
|
1243
|
+
* @internal
|
|
1244
|
+
*
|
|
1245
|
+
* @param namespace - If given it is appended to the tagName.
|
|
1246
|
+
*/
|
|
1247
|
+
static fromTokens(startToken, endToken, parent, metaTable, namespace = "") {
|
|
1248
|
+
const name = startToken.data[2];
|
|
1249
|
+
const tagName = namespace ? `${namespace}:${name}` : name;
|
|
1250
|
+
if (!name) {
|
|
1251
|
+
throw new Error("tagName cannot be empty");
|
|
1252
|
+
}
|
|
1253
|
+
const meta = metaTable ? metaTable.getMetaFor(tagName) : null;
|
|
1254
|
+
const open = startToken.data[1] !== "/";
|
|
1255
|
+
const closed = isClosed(endToken, meta);
|
|
1256
|
+
/* location contains position of '<' so strip it out */
|
|
1257
|
+
const location = sliceLocation(startToken.location, 1);
|
|
1258
|
+
return new HtmlElement(tagName, open ? parent : null, closed, meta, location);
|
|
1251
1259
|
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
/**
|
|
1255
|
-
* DOM Attribute.
|
|
1256
|
-
*
|
|
1257
|
-
* Represents a HTML attribute. Can contain either a fixed static value or a
|
|
1258
|
-
* placeholder for dynamic values (e.g. interpolated).
|
|
1259
|
-
*/
|
|
1260
|
-
class Attribute {
|
|
1261
1260
|
/**
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
* @param valueLocation - Source location of attribute value.
|
|
1266
|
-
* @param originalAttribute - If this attribute was dynamically added via a
|
|
1267
|
-
* transformation (e.g. vuejs `:id` generating the `id` attribute) this
|
|
1268
|
-
* parameter should be set to the attribute name of the source attribute (`:id`).
|
|
1261
|
+
* Returns annotated name if set or defaults to `<tagName>`.
|
|
1262
|
+
*
|
|
1263
|
+
* E.g. `my-annotation` or `<div>`.
|
|
1269
1264
|
*/
|
|
1270
|
-
|
|
1271
|
-
this.
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
/* force undefined to null */
|
|
1277
|
-
if (typeof this.value === "undefined") {
|
|
1278
|
-
this.value = null;
|
|
1265
|
+
get annotatedName() {
|
|
1266
|
+
if (this.annotation) {
|
|
1267
|
+
return this.annotation;
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
return `<${this.tagName}>`;
|
|
1279
1271
|
}
|
|
1280
1272
|
}
|
|
1281
1273
|
/**
|
|
1282
|
-
*
|
|
1274
|
+
* Get list of IDs referenced by `aria-labelledby`.
|
|
1275
|
+
*
|
|
1276
|
+
* If the attribute is unset or empty this getter returns null.
|
|
1277
|
+
* If the attribute is dynamic the original {@link DynamicValue} is returned.
|
|
1278
|
+
*
|
|
1279
|
+
* @public
|
|
1283
1280
|
*/
|
|
1284
|
-
get
|
|
1285
|
-
|
|
1281
|
+
get ariaLabelledby() {
|
|
1282
|
+
const attr = this.getAttribute("aria-labelledby");
|
|
1283
|
+
if (!attr || !attr.value) {
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1286
|
+
if (attr.value instanceof DynamicValue) {
|
|
1287
|
+
return attr.value;
|
|
1288
|
+
}
|
|
1289
|
+
const list = new DOMTokenList(attr.value, attr.valueLocation);
|
|
1290
|
+
return list.length ? Array.from(list) : null;
|
|
1286
1291
|
}
|
|
1287
1292
|
/**
|
|
1288
|
-
*
|
|
1293
|
+
* Similar to childNodes but only elements.
|
|
1289
1294
|
*/
|
|
1290
|
-
get
|
|
1291
|
-
return this.
|
|
1295
|
+
get childElements() {
|
|
1296
|
+
return this.childNodes.filter(isElementNode);
|
|
1292
1297
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Find the first ancestor matching a selector.
|
|
1300
|
+
*
|
|
1301
|
+
* Implementation of DOM specification of Element.closest(selectors).
|
|
1302
|
+
*/
|
|
1303
|
+
closest(selectors) {
|
|
1304
|
+
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
|
1305
|
+
let node = this;
|
|
1306
|
+
while (node) {
|
|
1307
|
+
if (node.matches(selectors)) {
|
|
1308
|
+
return node;
|
|
1309
|
+
}
|
|
1310
|
+
node = node.parent;
|
|
1296
1311
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1312
|
+
return null;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Generate a DOM selector for this element. The returned selector will be
|
|
1316
|
+
* unique inside the current document.
|
|
1317
|
+
*/
|
|
1318
|
+
generateSelector() {
|
|
1319
|
+
/* root element cannot have a selector as it isn't a proper element */
|
|
1320
|
+
if (this.isRootElement()) {
|
|
1321
|
+
return null;
|
|
1300
1322
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1323
|
+
const parts = [];
|
|
1324
|
+
let root;
|
|
1325
|
+
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
|
1326
|
+
for (root = this; root.parent; root = root.parent) {
|
|
1327
|
+
/* .. */
|
|
1304
1328
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1329
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1330
|
+
for (let cur = this; cur.parent; cur = cur.parent) {
|
|
1331
|
+
/* if a unique id is present, use it and short-circuit */
|
|
1332
|
+
if (cur.id) {
|
|
1333
|
+
const selector = generateIdSelector(cur.id);
|
|
1334
|
+
const matches = root.querySelectorAll(selector);
|
|
1335
|
+
if (matches.length === 1) {
|
|
1336
|
+
parts.push(selector);
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
const parent = cur.parent;
|
|
1341
|
+
const child = parent.childElements;
|
|
1342
|
+
const index = child.findIndex((it) => it.unique === cur.unique);
|
|
1343
|
+
const numOfType = child.filter((it) => it.is(cur.tagName)).length;
|
|
1344
|
+
const solo = numOfType === 1;
|
|
1345
|
+
/* if this is the only tagName in this level of siblings nth-child isn't needed */
|
|
1346
|
+
if (solo) {
|
|
1347
|
+
parts.push(cur.tagName.toLowerCase());
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
/* this will generate the worst kind of selector but at least it will be accurate (optimizations welcome) */
|
|
1351
|
+
parts.push(`${cur.tagName.toLowerCase()}:nth-child(${index + 1})`);
|
|
1307
1352
|
}
|
|
1353
|
+
return parts.reverse().join(" > ");
|
|
1308
1354
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
.
|
|
1316
|
-
.map((it) => {
|
|
1317
|
-
const [property, value] = it.split(":", 2);
|
|
1318
|
-
return [property.trim(), value ? value.trim() : ""];
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
/**
|
|
1322
|
-
* @internal
|
|
1323
|
-
*/
|
|
1324
|
-
function parseCssDeclaration(value) {
|
|
1325
|
-
if (!value || value instanceof DynamicValue) {
|
|
1326
|
-
return {};
|
|
1327
|
-
}
|
|
1328
|
-
const pairs = getCSSDeclarations(value);
|
|
1329
|
-
return Object.fromEntries(pairs);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
function sliceSize(size, begin, end) {
|
|
1333
|
-
if (typeof size !== "number") {
|
|
1334
|
-
return size;
|
|
1335
|
-
}
|
|
1336
|
-
if (typeof end !== "number") {
|
|
1337
|
-
return size - begin;
|
|
1338
|
-
}
|
|
1339
|
-
if (end < 0) {
|
|
1340
|
-
end = size + end;
|
|
1355
|
+
/**
|
|
1356
|
+
* Tests if this element has given tagname.
|
|
1357
|
+
*
|
|
1358
|
+
* If passing "*" this test will pass if any tagname is set.
|
|
1359
|
+
*/
|
|
1360
|
+
is(tagName) {
|
|
1361
|
+
return tagName === "*" || this.tagName.toLowerCase() === tagName.toLowerCase();
|
|
1341
1362
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1363
|
+
/**
|
|
1364
|
+
* Load new element metadata onto this element.
|
|
1365
|
+
*
|
|
1366
|
+
* Do note that semantics such as `void` cannot be changed (as the element has
|
|
1367
|
+
* already been created). In addition the element will still "be" the same
|
|
1368
|
+
* element, i.e. even if loading meta for a `<p>` tag upon a `<div>` tag it
|
|
1369
|
+
* will still be a `<div>` as far as the rest of the validator is concerned.
|
|
1370
|
+
*
|
|
1371
|
+
* In fact only certain properties will be copied onto the element:
|
|
1372
|
+
*
|
|
1373
|
+
* - content categories (flow, phrasing, etc)
|
|
1374
|
+
* - required attributes
|
|
1375
|
+
* - attribute allowed values
|
|
1376
|
+
* - permitted/required elements
|
|
1377
|
+
*
|
|
1378
|
+
* Properties *not* loaded:
|
|
1379
|
+
*
|
|
1380
|
+
* - inherit
|
|
1381
|
+
* - deprecated
|
|
1382
|
+
* - foreign
|
|
1383
|
+
* - void
|
|
1384
|
+
* - implicitClosed
|
|
1385
|
+
* - scriptSupporting
|
|
1386
|
+
* - deprecatedAttributes
|
|
1387
|
+
*
|
|
1388
|
+
* Changes to element metadata will only be visible after `element:ready` (and
|
|
1389
|
+
* the subsequent `dom:ready` event).
|
|
1390
|
+
*/
|
|
1391
|
+
loadMeta(meta) {
|
|
1392
|
+
if (!this.metaElement) {
|
|
1393
|
+
this.metaElement = {};
|
|
1394
|
+
}
|
|
1395
|
+
for (const key of MetaCopyableProperty) {
|
|
1396
|
+
const value = meta[key];
|
|
1397
|
+
if (typeof value !== "undefined") {
|
|
1398
|
+
setMetaProperty(this.metaElement, key, value);
|
|
1364
1399
|
}
|
|
1365
1400
|
else {
|
|
1366
|
-
|
|
1401
|
+
delete this.metaElement[key];
|
|
1367
1402
|
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
return sliced;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
var State;
|
|
1374
|
-
(function (State) {
|
|
1375
|
-
State[State["INITIAL"] = 1] = "INITIAL";
|
|
1376
|
-
State[State["DOCTYPE"] = 2] = "DOCTYPE";
|
|
1377
|
-
State[State["TEXT"] = 3] = "TEXT";
|
|
1378
|
-
State[State["TAG"] = 4] = "TAG";
|
|
1379
|
-
State[State["ATTR"] = 5] = "ATTR";
|
|
1380
|
-
State[State["CDATA"] = 6] = "CDATA";
|
|
1381
|
-
State[State["SCRIPT"] = 7] = "SCRIPT";
|
|
1382
|
-
State[State["STYLE"] = 8] = "STYLE";
|
|
1383
|
-
})(State || (State = {}));
|
|
1384
|
-
|
|
1385
|
-
var ContentModel;
|
|
1386
|
-
(function (ContentModel) {
|
|
1387
|
-
ContentModel[ContentModel["TEXT"] = 1] = "TEXT";
|
|
1388
|
-
ContentModel[ContentModel["SCRIPT"] = 2] = "SCRIPT";
|
|
1389
|
-
ContentModel[ContentModel["STYLE"] = 3] = "STYLE";
|
|
1390
|
-
})(ContentModel || (ContentModel = {}));
|
|
1391
|
-
class Context {
|
|
1392
|
-
constructor(source) {
|
|
1393
|
-
var _a, _b, _c, _d;
|
|
1394
|
-
this.state = State.INITIAL;
|
|
1395
|
-
this.string = source.data;
|
|
1396
|
-
this.filename = (_a = source.filename) !== null && _a !== void 0 ? _a : "";
|
|
1397
|
-
this.offset = (_b = source.offset) !== null && _b !== void 0 ? _b : 0;
|
|
1398
|
-
this.line = (_c = source.line) !== null && _c !== void 0 ? _c : 1;
|
|
1399
|
-
this.column = (_d = source.column) !== null && _d !== void 0 ? _d : 1;
|
|
1400
|
-
this.contentModel = ContentModel.TEXT;
|
|
1401
|
-
}
|
|
1402
|
-
getTruncatedLine(n = 13) {
|
|
1403
|
-
return JSON.stringify(this.string.length > n ? `${this.string.slice(0, 10)}...` : this.string);
|
|
1403
|
+
}
|
|
1404
1404
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1405
|
+
/**
|
|
1406
|
+
* Match this element against given selectors. Returns true if any selector
|
|
1407
|
+
* matches.
|
|
1408
|
+
*
|
|
1409
|
+
* Implementation of DOM specification of Element.matches(selectors).
|
|
1410
|
+
*/
|
|
1411
|
+
matches(selector) {
|
|
1412
|
+
/* find root element */
|
|
1413
|
+
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
|
1414
|
+
let root = this;
|
|
1415
|
+
while (root.parent) {
|
|
1416
|
+
root = root.parent;
|
|
1410
1417
|
}
|
|
1411
|
-
/*
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
this.
|
|
1417
|
-
|
|
1418
|
+
/* a bit slow implementation as it finds all candidates for the selector and
|
|
1419
|
+
* then tests if any of them are the current element. A better
|
|
1420
|
+
* implementation would be to walk the selector right-to-left and test
|
|
1421
|
+
* ancestors. */
|
|
1422
|
+
for (const match of root.querySelectorAll(selector)) {
|
|
1423
|
+
if (match.unique === this.unique) {
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1418
1426
|
}
|
|
1419
|
-
|
|
1420
|
-
this.offset += n;
|
|
1421
|
-
/* remove N chars */
|
|
1422
|
-
this.string = this.string.substr(n);
|
|
1423
|
-
/* change state */
|
|
1424
|
-
this.state = state;
|
|
1427
|
+
return false;
|
|
1425
1428
|
}
|
|
1426
|
-
|
|
1427
|
-
return
|
|
1428
|
-
filename: this.filename,
|
|
1429
|
-
offset: this.offset,
|
|
1430
|
-
line: this.line,
|
|
1431
|
-
column: this.column,
|
|
1432
|
-
size,
|
|
1433
|
-
};
|
|
1429
|
+
get meta() {
|
|
1430
|
+
return this.metaElement;
|
|
1434
1431
|
}
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
var NodeType;
|
|
1438
|
-
(function (NodeType) {
|
|
1439
|
-
NodeType[NodeType["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
|
|
1440
|
-
NodeType[NodeType["TEXT_NODE"] = 3] = "TEXT_NODE";
|
|
1441
|
-
NodeType[NodeType["DOCUMENT_NODE"] = 9] = "DOCUMENT_NODE";
|
|
1442
|
-
})(NodeType || (NodeType = {}));
|
|
1443
|
-
|
|
1444
|
-
const DOCUMENT_NODE_NAME = "#document";
|
|
1445
|
-
const TEXT_CONTENT = Symbol("textContent");
|
|
1446
|
-
let counter = 0;
|
|
1447
|
-
class DOMNode {
|
|
1448
1432
|
/**
|
|
1449
|
-
*
|
|
1450
|
-
*
|
|
1451
|
-
* @param nodeType - What node type to create.
|
|
1452
|
-
* @param nodeName - What node name to use. For `HtmlElement` this corresponds
|
|
1453
|
-
* to the tagName but other node types have specific predefined values.
|
|
1454
|
-
* @param location - Source code location of this node.
|
|
1433
|
+
* Set annotation for this element.
|
|
1455
1434
|
*/
|
|
1456
|
-
|
|
1457
|
-
this.
|
|
1458
|
-
this.nodeName = nodeName !== null && nodeName !== void 0 ? nodeName : DOCUMENT_NODE_NAME;
|
|
1459
|
-
this.location = location;
|
|
1460
|
-
this.disabledRules = new Set();
|
|
1461
|
-
this.childNodes = [];
|
|
1462
|
-
this.unique = counter++;
|
|
1463
|
-
this.cache = null;
|
|
1435
|
+
setAnnotation(text) {
|
|
1436
|
+
this.annotation = text;
|
|
1464
1437
|
}
|
|
1465
1438
|
/**
|
|
1466
|
-
*
|
|
1439
|
+
* Set attribute. Stores all attributes set even with the same name.
|
|
1467
1440
|
*
|
|
1468
|
-
*
|
|
1441
|
+
* @param key - Attribute name
|
|
1442
|
+
* @param value - Attribute value. Use `null` if no value is present.
|
|
1443
|
+
* @param keyLocation - Location of the attribute name.
|
|
1444
|
+
* @param valueLocation - Location of the attribute value (excluding quotation)
|
|
1445
|
+
* @param originalAttribute - If attribute is an alias for another attribute
|
|
1446
|
+
* (dynamic attributes) set this to the original attribute name.
|
|
1469
1447
|
*/
|
|
1470
|
-
|
|
1471
|
-
|
|
1448
|
+
setAttribute(key, value, keyLocation, valueLocation, originalAttribute) {
|
|
1449
|
+
key = key.toLowerCase();
|
|
1450
|
+
if (!this.attr[key]) {
|
|
1451
|
+
this.attr[key] = [];
|
|
1452
|
+
}
|
|
1453
|
+
this.attr[key].push(new Attribute(key, value, keyLocation, valueLocation, originalAttribute));
|
|
1472
1454
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1455
|
+
/**
|
|
1456
|
+
* Get a list of all attributes on this node.
|
|
1457
|
+
*/
|
|
1458
|
+
get attributes() {
|
|
1459
|
+
return Object.values(this.attr).reduce((result, cur) => {
|
|
1460
|
+
return result.concat(cur);
|
|
1461
|
+
}, []);
|
|
1462
|
+
}
|
|
1463
|
+
hasAttribute(key) {
|
|
1464
|
+
key = key.toLowerCase();
|
|
1465
|
+
return key in this.attr;
|
|
1466
|
+
}
|
|
1467
|
+
getAttribute(key, all = false) {
|
|
1468
|
+
key = key.toLowerCase();
|
|
1469
|
+
if (key in this.attr) {
|
|
1470
|
+
const matches = this.attr[key];
|
|
1471
|
+
return all ? matches : matches[0];
|
|
1476
1472
|
}
|
|
1477
1473
|
else {
|
|
1478
|
-
return
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
cacheSet(key, value) {
|
|
1482
|
-
if (this.cache) {
|
|
1483
|
-
this.cache.set(key, value);
|
|
1474
|
+
return null;
|
|
1484
1475
|
}
|
|
1485
|
-
return value;
|
|
1486
1476
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1477
|
+
/**
|
|
1478
|
+
* Get attribute value.
|
|
1479
|
+
*
|
|
1480
|
+
* Returns the attribute value if present.
|
|
1481
|
+
*
|
|
1482
|
+
* - Missing attributes return `null`.
|
|
1483
|
+
* - Boolean attributes return `null`.
|
|
1484
|
+
* - `DynamicValue` returns attribute expression.
|
|
1485
|
+
*
|
|
1486
|
+
* @param key - Attribute name
|
|
1487
|
+
* @returns Attribute value or null.
|
|
1488
|
+
*/
|
|
1489
|
+
getAttributeValue(key) {
|
|
1490
|
+
const attr = this.getAttribute(key);
|
|
1491
|
+
if (attr) {
|
|
1492
|
+
return attr.value !== null ? attr.value.toString() : null;
|
|
1490
1493
|
}
|
|
1491
1494
|
else {
|
|
1492
|
-
return
|
|
1495
|
+
return null;
|
|
1493
1496
|
}
|
|
1494
1497
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1498
|
+
/**
|
|
1499
|
+
* Add text as a child node to this element.
|
|
1500
|
+
*
|
|
1501
|
+
* @param text - Text to add.
|
|
1502
|
+
* @param location - Source code location of this text.
|
|
1503
|
+
*/
|
|
1504
|
+
appendText(text, location) {
|
|
1505
|
+
this.childNodes.push(new TextNode(text, location));
|
|
1497
1506
|
}
|
|
1498
1507
|
/**
|
|
1499
|
-
*
|
|
1508
|
+
* Return a list of all known classes on the element. Dynamic values are
|
|
1509
|
+
* ignored.
|
|
1500
1510
|
*/
|
|
1501
|
-
get
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
return cached;
|
|
1511
|
+
get classList() {
|
|
1512
|
+
if (!this.hasAttribute("class")) {
|
|
1513
|
+
return new DOMTokenList(null, null);
|
|
1505
1514
|
}
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
|
|
1515
|
+
const classes = this.getAttribute("class", true)
|
|
1516
|
+
.filter((attr) => attr.isStatic)
|
|
1517
|
+
.map((attr) => attr.value)
|
|
1518
|
+
.join(" ");
|
|
1519
|
+
return new DOMTokenList(classes, null);
|
|
1509
1520
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1521
|
+
/**
|
|
1522
|
+
* Get element ID if present.
|
|
1523
|
+
*/
|
|
1524
|
+
get id() {
|
|
1525
|
+
return this.getAttributeValue("id");
|
|
1512
1526
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1527
|
+
get style() {
|
|
1528
|
+
const attr = this.getAttribute("style");
|
|
1529
|
+
return parseCssDeclaration(attr === null || attr === void 0 ? void 0 : attr.value);
|
|
1515
1530
|
}
|
|
1516
1531
|
/**
|
|
1517
|
-
*
|
|
1532
|
+
* Returns the first child element or null if there are no child elements.
|
|
1518
1533
|
*/
|
|
1519
|
-
|
|
1520
|
-
|
|
1534
|
+
get firstElementChild() {
|
|
1535
|
+
const children = this.childElements;
|
|
1536
|
+
return children.length > 0 ? children[0] : null;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Returns the last child element or null if there are no child elements.
|
|
1540
|
+
*/
|
|
1541
|
+
get lastElementChild() {
|
|
1542
|
+
const children = this.childElements;
|
|
1543
|
+
return children.length > 0 ? children[children.length - 1] : null;
|
|
1544
|
+
}
|
|
1545
|
+
get siblings() {
|
|
1546
|
+
return this.parent ? this.parent.childElements : [this];
|
|
1547
|
+
}
|
|
1548
|
+
get previousSibling() {
|
|
1549
|
+
const i = this.siblings.findIndex((node) => node.unique === this.unique);
|
|
1550
|
+
return i >= 1 ? this.siblings[i - 1] : null;
|
|
1551
|
+
}
|
|
1552
|
+
get nextSibling() {
|
|
1553
|
+
const i = this.siblings.findIndex((node) => node.unique === this.unique);
|
|
1554
|
+
return i <= this.siblings.length - 2 ? this.siblings[i + 1] : null;
|
|
1555
|
+
}
|
|
1556
|
+
getElementsByTagName(tagName) {
|
|
1557
|
+
return this.childElements.reduce((matches, node) => {
|
|
1558
|
+
return matches.concat(node.is(tagName) ? [node] : [], node.getElementsByTagName(tagName));
|
|
1559
|
+
}, []);
|
|
1521
1560
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
get firstChild() {
|
|
1527
|
-
return this.childNodes[0] || null;
|
|
1561
|
+
querySelector(selector) {
|
|
1562
|
+
var _a;
|
|
1563
|
+
const it = this.querySelectorImpl(selector);
|
|
1564
|
+
return (_a = it.next().value) !== null && _a !== void 0 ? _a : null; // eslint-disable-line @typescript-eslint/no-unsafe-return
|
|
1528
1565
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
get lastChild() {
|
|
1534
|
-
return this.childNodes[this.childNodes.length - 1] || null;
|
|
1566
|
+
querySelectorAll(selector) {
|
|
1567
|
+
const it = this.querySelectorImpl(selector);
|
|
1568
|
+
const unique = new Set(it);
|
|
1569
|
+
return Array.from(unique.values());
|
|
1535
1570
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1571
|
+
*querySelectorImpl(selectorList) {
|
|
1572
|
+
if (!selectorList) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
for (const selector of selectorList.split(/,\s*/)) {
|
|
1576
|
+
const pattern = new Selector(selector);
|
|
1577
|
+
yield* pattern.match(this);
|
|
1578
|
+
}
|
|
1541
1579
|
}
|
|
1542
1580
|
/**
|
|
1543
|
-
*
|
|
1581
|
+
* Visit all nodes from this node and down. Depth first.
|
|
1582
|
+
*
|
|
1583
|
+
* @internal
|
|
1544
1584
|
*/
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1585
|
+
visitDepthFirst(callback) {
|
|
1586
|
+
function visit(node) {
|
|
1587
|
+
node.childElements.forEach(visit);
|
|
1588
|
+
if (!node.isRootElement()) {
|
|
1589
|
+
callback(node);
|
|
1590
|
+
}
|
|
1548
1591
|
}
|
|
1592
|
+
visit(this);
|
|
1549
1593
|
}
|
|
1550
1594
|
/**
|
|
1551
|
-
*
|
|
1595
|
+
* Evaluates callbackk on all descendants, returning true if any are true.
|
|
1596
|
+
*
|
|
1597
|
+
* @internal
|
|
1552
1598
|
*/
|
|
1553
|
-
|
|
1554
|
-
this.
|
|
1599
|
+
someChildren(callback) {
|
|
1600
|
+
return this.childElements.some(visit);
|
|
1601
|
+
function visit(node) {
|
|
1602
|
+
if (callback(node)) {
|
|
1603
|
+
return true;
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
return node.childElements.some(visit);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1555
1609
|
}
|
|
1556
1610
|
/**
|
|
1557
|
-
*
|
|
1611
|
+
* Evaluates callbackk on all descendants, returning true if all are true.
|
|
1612
|
+
*
|
|
1613
|
+
* @internal
|
|
1558
1614
|
*/
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1615
|
+
everyChildren(callback) {
|
|
1616
|
+
return this.childElements.every(visit);
|
|
1617
|
+
function visit(node) {
|
|
1618
|
+
if (!callback(node)) {
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
return node.childElements.every(visit);
|
|
1562
1622
|
}
|
|
1563
1623
|
}
|
|
1564
1624
|
/**
|
|
1565
|
-
*
|
|
1625
|
+
* Visit all nodes from this node and down. Breadth first.
|
|
1626
|
+
*
|
|
1627
|
+
* The first node for which the callback evaluates to true is returned.
|
|
1628
|
+
*
|
|
1629
|
+
* @internal
|
|
1566
1630
|
*/
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
let end = text.indexOf(" ", begin);
|
|
1580
|
-
/* if the last space was found move the position to the last character
|
|
1581
|
-
* in the string */
|
|
1582
|
-
if (end === -1) {
|
|
1583
|
-
end = text.length;
|
|
1584
|
-
}
|
|
1585
|
-
/* handle multiple spaces */
|
|
1586
|
-
const size = end - begin;
|
|
1587
|
-
if (size === 0) {
|
|
1588
|
-
begin++;
|
|
1589
|
-
continue;
|
|
1590
|
-
}
|
|
1591
|
-
/* extract token */
|
|
1592
|
-
const token = text.substring(begin, end);
|
|
1593
|
-
tokens.push(token);
|
|
1594
|
-
/* extract location */
|
|
1595
|
-
if (locations && baseLocation) {
|
|
1596
|
-
const location = sliceLocation(baseLocation, begin, end);
|
|
1597
|
-
locations.push(location);
|
|
1631
|
+
find(callback) {
|
|
1632
|
+
function visit(node) {
|
|
1633
|
+
if (callback(node)) {
|
|
1634
|
+
return node;
|
|
1635
|
+
}
|
|
1636
|
+
for (const child of node.childElements) {
|
|
1637
|
+
const match = child.find(callback);
|
|
1638
|
+
if (match) {
|
|
1639
|
+
return match;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
return null;
|
|
1598
1643
|
}
|
|
1599
|
-
|
|
1600
|
-
begin += size + 1;
|
|
1644
|
+
return visit(this);
|
|
1601
1645
|
}
|
|
1602
|
-
return { tokens, locations };
|
|
1603
1646
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
const normalized = value.replace(/[\t\r\n]/g, " ");
|
|
1609
|
-
const { tokens, locations } = parse(normalized, location);
|
|
1610
|
-
super(...tokens);
|
|
1611
|
-
this.locations = locations;
|
|
1612
|
-
}
|
|
1613
|
-
else {
|
|
1614
|
-
super(0);
|
|
1615
|
-
this.locations = null;
|
|
1616
|
-
}
|
|
1617
|
-
if (value instanceof DynamicValue) {
|
|
1618
|
-
this.value = value.expr;
|
|
1619
|
-
}
|
|
1620
|
-
else {
|
|
1621
|
-
this.value = value || "";
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
item(n) {
|
|
1625
|
-
return this[n];
|
|
1626
|
-
}
|
|
1627
|
-
location(n) {
|
|
1628
|
-
if (this.locations) {
|
|
1629
|
-
return this.locations[n];
|
|
1630
|
-
}
|
|
1631
|
-
else {
|
|
1632
|
-
throw new Error("Trying to access DOMTokenList location when base location isn't set");
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
contains(token) {
|
|
1636
|
-
return this.includes(token);
|
|
1647
|
+
function isClosed(endToken, meta) {
|
|
1648
|
+
let closed = NodeClosed.Open;
|
|
1649
|
+
if (meta && meta.void) {
|
|
1650
|
+
closed = NodeClosed.VoidOmitted;
|
|
1637
1651
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
1641
|
-
const item = this.item(index);
|
|
1642
|
-
const location = this.location(index);
|
|
1643
|
-
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
1644
|
-
yield { index, item, location };
|
|
1645
|
-
}
|
|
1652
|
+
if (endToken.data[0] === "/>") {
|
|
1653
|
+
closed = NodeClosed.VoidSelfClosed;
|
|
1646
1654
|
}
|
|
1655
|
+
return closed;
|
|
1647
1656
|
}
|
|
1648
1657
|
|
|
1649
|
-
|
|
1650
|
-
(
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
Combinator[Combinator["GENERAL_SIBLING"] = 4] = "GENERAL_SIBLING";
|
|
1655
|
-
/* special cases */
|
|
1656
|
-
Combinator[Combinator["SCOPE"] = 5] = "SCOPE";
|
|
1657
|
-
})(Combinator || (Combinator = {}));
|
|
1658
|
-
function parseCombinator(combinator, pattern) {
|
|
1659
|
-
/* special case, when pattern is :scope [[Selector]] will handle this
|
|
1660
|
-
* "combinator" to match itself instead of descendants */
|
|
1661
|
-
if (pattern === ":scope") {
|
|
1662
|
-
return Combinator.SCOPE;
|
|
1658
|
+
class DOMTree {
|
|
1659
|
+
constructor(location) {
|
|
1660
|
+
this.root = HtmlElement.rootNode(location);
|
|
1661
|
+
this.active = this.root;
|
|
1662
|
+
this.doctype = null;
|
|
1663
1663
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
case null:
|
|
1667
|
-
case "":
|
|
1668
|
-
return Combinator.DESCENDANT;
|
|
1669
|
-
case ">":
|
|
1670
|
-
return Combinator.CHILD;
|
|
1671
|
-
case "+":
|
|
1672
|
-
return Combinator.ADJACENT_SIBLING;
|
|
1673
|
-
case "~":
|
|
1674
|
-
return Combinator.GENERAL_SIBLING;
|
|
1675
|
-
default:
|
|
1676
|
-
throw new Error(`Unknown combinator "${combinator}"`);
|
|
1664
|
+
pushActive(node) {
|
|
1665
|
+
this.active = node;
|
|
1677
1666
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
function lastChild(node) {
|
|
1685
|
-
return node.nextSibling === null;
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
const cache = {};
|
|
1689
|
-
function getNthChild(node) {
|
|
1690
|
-
if (!node.parent) {
|
|
1691
|
-
return -1;
|
|
1667
|
+
popActive() {
|
|
1668
|
+
if (this.active.isRootElement()) {
|
|
1669
|
+
/* root element should never be popped, continue as if nothing happened */
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
this.active = this.active.parent || this.root;
|
|
1692
1673
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
const index = parent.childElements.findIndex((cur) => {
|
|
1696
|
-
return cur.unique === node.unique;
|
|
1697
|
-
});
|
|
1698
|
-
cache[node.unique] = index + 1; /* nthChild starts at 1 */
|
|
1674
|
+
getActive() {
|
|
1675
|
+
return this.active;
|
|
1699
1676
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1677
|
+
/**
|
|
1678
|
+
* Resolve dynamic meta expressions.
|
|
1679
|
+
*/
|
|
1680
|
+
resolveMeta(table) {
|
|
1681
|
+
this.visitDepthFirst((node) => table.resolve(node));
|
|
1682
|
+
}
|
|
1683
|
+
getElementsByTagName(tagName) {
|
|
1684
|
+
return this.root.getElementsByTagName(tagName);
|
|
1705
1685
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
return cur === n;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
function scope(node) {
|
|
1712
|
-
return node.isSameNode(this.scope);
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
const table = {
|
|
1716
|
-
"first-child": firstChild,
|
|
1717
|
-
"last-child": lastChild,
|
|
1718
|
-
"nth-child": nthChild,
|
|
1719
|
-
scope: scope,
|
|
1720
|
-
};
|
|
1721
|
-
function factory(name, context) {
|
|
1722
|
-
const fn = table[name];
|
|
1723
|
-
if (fn) {
|
|
1724
|
-
return fn.bind(context);
|
|
1686
|
+
visitDepthFirst(callback) {
|
|
1687
|
+
this.root.visitDepthFirst(callback);
|
|
1725
1688
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1689
|
+
find(callback) {
|
|
1690
|
+
return this.root.find(callback);
|
|
1691
|
+
}
|
|
1692
|
+
querySelector(selector) {
|
|
1693
|
+
return this.root.querySelector(selector);
|
|
1694
|
+
}
|
|
1695
|
+
querySelectorAll(selector) {
|
|
1696
|
+
return this.root.querySelectorAll(selector);
|
|
1728
1697
|
}
|
|
1729
1698
|
}
|
|
1730
1699
|
|
|
1700
|
+
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
1701
|
+
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
1702
|
+
const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
|
|
1731
1703
|
/**
|
|
1732
|
-
*
|
|
1704
|
+
* Tests if this element is present in the accessibility tree.
|
|
1733
1705
|
*
|
|
1734
|
-
*
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
return value.replace(/\\(.)/g, "$1");
|
|
1738
|
-
}
|
|
1739
|
-
/**
|
|
1740
|
-
* @internal
|
|
1706
|
+
* In practice it tests whenever the element or its parents has
|
|
1707
|
+
* `role="presentation"` or `aria-hidden="false"`. Dynamic values counts as
|
|
1708
|
+
* visible since the element might be in the visibility tree sometimes.
|
|
1741
1709
|
*/
|
|
1742
|
-
function
|
|
1743
|
-
return
|
|
1710
|
+
function inAccessibilityTree(node) {
|
|
1711
|
+
return !isAriaHidden(node) && !isPresentation(node);
|
|
1744
1712
|
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
return
|
|
1713
|
+
function isAriaHiddenImpl(node) {
|
|
1714
|
+
const isHidden = (node) => {
|
|
1715
|
+
const ariaHidden = node.getAttribute("aria-hidden");
|
|
1716
|
+
return Boolean(ariaHidden && ariaHidden.value === "true");
|
|
1717
|
+
};
|
|
1718
|
+
return {
|
|
1719
|
+
byParent: node.parent ? isAriaHidden(node.parent) : false,
|
|
1720
|
+
bySelf: isHidden(node),
|
|
1721
|
+
};
|
|
1751
1722
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
*/
|
|
1760
|
-
function isDelimiter(ch) {
|
|
1761
|
-
return /[.#[:]/.test(ch);
|
|
1723
|
+
function isAriaHidden(node, details) {
|
|
1724
|
+
const cached = node.cacheGet(ARIA_HIDDEN_CACHE);
|
|
1725
|
+
if (cached) {
|
|
1726
|
+
return details ? cached : cached.byParent || cached.bySelf;
|
|
1727
|
+
}
|
|
1728
|
+
const result = node.cacheSet(ARIA_HIDDEN_CACHE, isAriaHiddenImpl(node));
|
|
1729
|
+
return details ? result : result.byParent || result.bySelf;
|
|
1762
1730
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1731
|
+
function isHTMLHiddenImpl(node) {
|
|
1732
|
+
const isHidden = (node) => {
|
|
1733
|
+
const hidden = node.getAttribute("hidden");
|
|
1734
|
+
return hidden !== null && hidden.isStatic;
|
|
1735
|
+
};
|
|
1736
|
+
return {
|
|
1737
|
+
byParent: node.parent ? isHTMLHidden(node.parent) : false,
|
|
1738
|
+
bySelf: isHidden(node),
|
|
1739
|
+
};
|
|
1768
1740
|
}
|
|
1769
|
-
function
|
|
1770
|
-
|
|
1741
|
+
function isHTMLHidden(node, details) {
|
|
1742
|
+
const cached = node.cacheGet(HTML_HIDDEN_CACHE);
|
|
1743
|
+
if (cached) {
|
|
1744
|
+
return details ? cached : cached.byParent || cached.bySelf;
|
|
1745
|
+
}
|
|
1746
|
+
const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
|
|
1747
|
+
return details ? result : result.byParent || result.bySelf;
|
|
1771
1748
|
}
|
|
1772
1749
|
/**
|
|
1773
|
-
*
|
|
1750
|
+
* Tests if this element or a parent element has role="presentation".
|
|
1751
|
+
*
|
|
1752
|
+
* Dynamic values yields `false` just as if the attribute wasn't present.
|
|
1774
1753
|
*/
|
|
1775
|
-
function
|
|
1776
|
-
if (
|
|
1777
|
-
return;
|
|
1754
|
+
function isPresentation(node) {
|
|
1755
|
+
if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
|
|
1756
|
+
return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
|
|
1778
1757
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
const buffer = pattern.slice(begin, cur);
|
|
1786
|
-
/* escaped character, ignore whatever is next */
|
|
1787
|
-
if (ch === "\\") {
|
|
1788
|
-
cur += 2;
|
|
1789
|
-
continue;
|
|
1790
|
-
}
|
|
1791
|
-
/* if inside quoted string we only look for the end quotation mark */
|
|
1792
|
-
if (quoted) {
|
|
1793
|
-
if (ch === quoted) {
|
|
1794
|
-
quoted = false;
|
|
1795
|
-
}
|
|
1796
|
-
cur += 1;
|
|
1797
|
-
continue;
|
|
1798
|
-
}
|
|
1799
|
-
/* if the character is a quotation mark we store the character and the above
|
|
1800
|
-
* condition will look for a similar end quotation mark */
|
|
1801
|
-
if (isQuotationMark(ch)) {
|
|
1802
|
-
quoted = ch;
|
|
1803
|
-
cur += 1;
|
|
1804
|
-
continue;
|
|
1805
|
-
}
|
|
1806
|
-
/* special case when using :: pseudo element selector */
|
|
1807
|
-
if (isPseudoElement(ch, buffer)) {
|
|
1808
|
-
cur += 1;
|
|
1809
|
-
continue;
|
|
1758
|
+
let cur = node;
|
|
1759
|
+
do {
|
|
1760
|
+
const role = cur.getAttribute("role");
|
|
1761
|
+
/* role="presentation" */
|
|
1762
|
+
if (role && role.value === "presentation") {
|
|
1763
|
+
return cur.cacheSet(ROLE_PRESENTATION_CACHE, true);
|
|
1810
1764
|
}
|
|
1811
|
-
/* if
|
|
1812
|
-
*
|
|
1813
|
-
if (
|
|
1814
|
-
|
|
1815
|
-
yield buffer;
|
|
1765
|
+
/* sanity check: break if no parent is present, normally not an issue as the
|
|
1766
|
+
* root element should be found first */
|
|
1767
|
+
if (!cur.parent) {
|
|
1768
|
+
break;
|
|
1816
1769
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
yield tail;
|
|
1822
|
-
}
|
|
1823
|
-
class Matcher {
|
|
1824
|
-
}
|
|
1825
|
-
class ClassMatcher extends Matcher {
|
|
1826
|
-
constructor(classname) {
|
|
1827
|
-
super();
|
|
1828
|
-
this.classname = classname;
|
|
1829
|
-
}
|
|
1830
|
-
match(node) {
|
|
1831
|
-
return node.classList.contains(this.classname);
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
class IdMatcher extends Matcher {
|
|
1835
|
-
constructor(id) {
|
|
1836
|
-
super();
|
|
1837
|
-
this.id = stripslashes(id);
|
|
1838
|
-
}
|
|
1839
|
-
match(node) {
|
|
1840
|
-
return node.id === this.id;
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
class AttrMatcher extends Matcher {
|
|
1844
|
-
constructor(attr) {
|
|
1845
|
-
super();
|
|
1846
|
-
const [, key, op, value] = attr.match(/^(.+?)(?:([~^$*|]?=)"([^"]+?)")?$/);
|
|
1847
|
-
this.key = key;
|
|
1848
|
-
this.op = op;
|
|
1849
|
-
this.value = value;
|
|
1850
|
-
}
|
|
1851
|
-
match(node) {
|
|
1852
|
-
const attr = node.getAttribute(this.key, true) || [];
|
|
1853
|
-
return attr.some((cur) => {
|
|
1854
|
-
switch (this.op) {
|
|
1855
|
-
case undefined:
|
|
1856
|
-
return true; /* attribute exists */
|
|
1857
|
-
case "=":
|
|
1858
|
-
return cur.value === this.value;
|
|
1859
|
-
default:
|
|
1860
|
-
throw new Error(`Attribute selector operator ${this.op} is not implemented yet`);
|
|
1861
|
-
}
|
|
1862
|
-
});
|
|
1863
|
-
}
|
|
1770
|
+
/* check parents */
|
|
1771
|
+
cur = cur.parent;
|
|
1772
|
+
} while (!cur.isRootElement());
|
|
1773
|
+
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
1864
1774
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1775
|
+
|
|
1776
|
+
const cachePrefix = classifyNodeText.name;
|
|
1777
|
+
const HTML_CACHE_KEY = Symbol(`${cachePrefix}|html`);
|
|
1778
|
+
const A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y`);
|
|
1779
|
+
const IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY = Symbol(`${cachePrefix}|html|ignore-hidden-root`);
|
|
1780
|
+
const IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y|ignore-hidden-root`);
|
|
1781
|
+
/**
|
|
1782
|
+
* @public
|
|
1783
|
+
*/
|
|
1784
|
+
var TextClassification;
|
|
1785
|
+
(function (TextClassification) {
|
|
1786
|
+
TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
1787
|
+
TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
1788
|
+
TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
1789
|
+
})(TextClassification || (TextClassification = {}));
|
|
1790
|
+
function getCachekey(options = {}) {
|
|
1791
|
+
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
1792
|
+
if (accessible && ignoreHiddenRoot) {
|
|
1793
|
+
return IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY;
|
|
1879
1794
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
constructor(pattern) {
|
|
1883
|
-
const match = pattern.match(/^([~+\->]?)((?:[*]|[^.#[:]+)?)(.*)$/);
|
|
1884
|
-
match.shift(); /* remove full matched string */
|
|
1885
|
-
this.selector = pattern;
|
|
1886
|
-
this.combinator = parseCombinator(match.shift(), pattern);
|
|
1887
|
-
this.tagName = match.shift() || "*";
|
|
1888
|
-
this.pattern = Array.from(splitPattern(match[0]), (it) => this.createMatcher(it));
|
|
1795
|
+
else if (ignoreHiddenRoot) {
|
|
1796
|
+
return IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY;
|
|
1889
1797
|
}
|
|
1890
|
-
|
|
1891
|
-
return
|
|
1798
|
+
else if (accessible) {
|
|
1799
|
+
return A11Y_CACHE_KEY;
|
|
1892
1800
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
case ".":
|
|
1896
|
-
return new ClassMatcher(pattern.slice(1));
|
|
1897
|
-
case "#":
|
|
1898
|
-
return new IdMatcher(pattern.slice(1));
|
|
1899
|
-
case "[":
|
|
1900
|
-
return new AttrMatcher(pattern.slice(1, -1));
|
|
1901
|
-
case ":":
|
|
1902
|
-
return new PseudoClassMatcher(pattern.slice(1), this.selector);
|
|
1903
|
-
default:
|
|
1904
|
-
/* istanbul ignore next: fallback solution, the switch cases should cover
|
|
1905
|
-
* everything and there is no known way to trigger this fallback */
|
|
1906
|
-
throw new Error(`Failed to create matcher for "${pattern}"`);
|
|
1907
|
-
}
|
|
1801
|
+
else {
|
|
1802
|
+
return HTML_CACHE_KEY;
|
|
1908
1803
|
}
|
|
1909
1804
|
}
|
|
1805
|
+
/* While I cannot find a reference about this in the standard the <select>
|
|
1806
|
+
* element kinda acts as if there is no text content, most particularly it
|
|
1807
|
+
* doesn't receive and accessible name. The `.textContent` property does
|
|
1808
|
+
* however include the <option> childrens text. But for the sake of the
|
|
1809
|
+
* validator it is probably best if the classification acts as if there is no
|
|
1810
|
+
* text as I think that is what is expected of the return values. Might have
|
|
1811
|
+
* to revisit this at some point or if someone could clarify what section of
|
|
1812
|
+
* the standard deals with this. */
|
|
1813
|
+
function isSpecialEmpty(node) {
|
|
1814
|
+
return node.is("select") || node.is("textarea");
|
|
1815
|
+
}
|
|
1910
1816
|
/**
|
|
1911
|
-
*
|
|
1817
|
+
* Checks text content of an element.
|
|
1818
|
+
*
|
|
1819
|
+
* Any text is considered including text from descendant elements. Whitespace is
|
|
1820
|
+
* ignored.
|
|
1821
|
+
*
|
|
1822
|
+
* If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
|
|
1823
|
+
*
|
|
1824
|
+
* @public
|
|
1912
1825
|
*/
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1826
|
+
function classifyNodeText(node, options = {}) {
|
|
1827
|
+
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
1828
|
+
const cacheKey = getCachekey(options);
|
|
1829
|
+
if (node.cacheExists(cacheKey)) {
|
|
1830
|
+
return node.cacheGet(cacheKey);
|
|
1916
1831
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
*
|
|
1920
|
-
* @param root - Element to match against.
|
|
1921
|
-
* @returns Iterator with matched elements.
|
|
1922
|
-
*/
|
|
1923
|
-
*match(root) {
|
|
1924
|
-
const context = { scope: root };
|
|
1925
|
-
yield* this.matchInternal(root, 0, context);
|
|
1832
|
+
if (!ignoreHiddenRoot && isHTMLHidden(node)) {
|
|
1833
|
+
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
1926
1834
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
yield root;
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
const pattern = this.pattern[level];
|
|
1933
|
-
const matches = Selector.findCandidates(root, pattern);
|
|
1934
|
-
for (const node of matches) {
|
|
1935
|
-
if (!pattern.match(node, context)) {
|
|
1936
|
-
continue;
|
|
1937
|
-
}
|
|
1938
|
-
yield* this.matchInternal(node, level + 1, context);
|
|
1939
|
-
}
|
|
1835
|
+
if (!ignoreHiddenRoot && accessible && isAriaHidden(node)) {
|
|
1836
|
+
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
1940
1837
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
* easier parsing */
|
|
1944
|
-
selector = selector.replace(/([+~>]) /g, "$1");
|
|
1945
|
-
const pattern = selector.split(/(?:(?<!\\) )+/);
|
|
1946
|
-
return pattern.map((part) => new Pattern(part));
|
|
1838
|
+
if (isSpecialEmpty(node)) {
|
|
1839
|
+
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
1947
1840
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
return Selector.findAdjacentSibling(root);
|
|
1956
|
-
case Combinator.GENERAL_SIBLING:
|
|
1957
|
-
return Selector.findGeneralSibling(root);
|
|
1958
|
-
case Combinator.SCOPE:
|
|
1959
|
-
return [root];
|
|
1960
|
-
}
|
|
1961
|
-
/* istanbul ignore next: fallback solution, the switch cases should cover
|
|
1962
|
-
* everything and there is no known way to trigger this fallback */
|
|
1963
|
-
return [];
|
|
1841
|
+
const text = findTextNodes(node, {
|
|
1842
|
+
...options,
|
|
1843
|
+
ignoreHiddenRoot: false,
|
|
1844
|
+
});
|
|
1845
|
+
/* if any text is dynamic classify as dynamic */
|
|
1846
|
+
if (text.some((cur) => cur.isDynamic)) {
|
|
1847
|
+
return node.cacheSet(cacheKey, TextClassification.DYNAMIC_TEXT);
|
|
1964
1848
|
}
|
|
1965
|
-
static
|
|
1966
|
-
|
|
1967
|
-
return node.
|
|
1968
|
-
if (adjacent) {
|
|
1969
|
-
adjacent = false;
|
|
1970
|
-
return true;
|
|
1971
|
-
}
|
|
1972
|
-
if (cur === node) {
|
|
1973
|
-
adjacent = true;
|
|
1974
|
-
}
|
|
1975
|
-
return false;
|
|
1976
|
-
});
|
|
1849
|
+
/* if any text has non-whitespace character classify as static */
|
|
1850
|
+
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
|
|
1851
|
+
return node.cacheSet(cacheKey, TextClassification.STATIC_TEXT);
|
|
1977
1852
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1853
|
+
/* default to empty */
|
|
1854
|
+
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
1855
|
+
}
|
|
1856
|
+
function findTextNodes(node, options) {
|
|
1857
|
+
const { accessible = false } = options;
|
|
1858
|
+
let text = [];
|
|
1859
|
+
for (const child of node.childNodes) {
|
|
1860
|
+
if (isTextNode(child)) {
|
|
1861
|
+
text.push(child);
|
|
1862
|
+
}
|
|
1863
|
+
else if (isElementNode(child)) {
|
|
1864
|
+
if (isHTMLHidden(child, true).bySelf) {
|
|
1865
|
+
continue;
|
|
1983
1866
|
}
|
|
1984
|
-
if (
|
|
1985
|
-
|
|
1867
|
+
if (accessible && isAriaHidden(child, true).bySelf) {
|
|
1868
|
+
continue;
|
|
1986
1869
|
}
|
|
1987
|
-
|
|
1988
|
-
}
|
|
1870
|
+
text = text.concat(findTextNodes(child, options));
|
|
1871
|
+
}
|
|
1989
1872
|
}
|
|
1873
|
+
return text;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function hasAltText(image) {
|
|
1877
|
+
const alt = image.getAttribute("alt");
|
|
1878
|
+
/* missing or boolean */
|
|
1879
|
+
if (alt === null || alt.value === null) {
|
|
1880
|
+
return false;
|
|
1881
|
+
}
|
|
1882
|
+
return alt.isDynamic || alt.value.toString() !== "";
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function hasAriaLabel(node) {
|
|
1886
|
+
const label = node.getAttribute("aria-label");
|
|
1887
|
+
/* missing or boolean */
|
|
1888
|
+
if (label === null || label.value === null) {
|
|
1889
|
+
return false;
|
|
1890
|
+
}
|
|
1891
|
+
return label.isDynamic || label.value.toString() !== "";
|
|
1990
1892
|
}
|
|
1991
1893
|
|
|
1992
|
-
const TEXT_NODE_NAME = "#text";
|
|
1993
1894
|
/**
|
|
1994
|
-
*
|
|
1895
|
+
* Joins a list of words into natural language.
|
|
1995
1896
|
*
|
|
1996
|
-
*
|
|
1897
|
+
* - `["foo"]` becomes `"foo"`
|
|
1898
|
+
* - `["foo", "bar"]` becomes `"foo or bar"`
|
|
1899
|
+
* - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
|
|
1900
|
+
* - and so on...
|
|
1901
|
+
*
|
|
1902
|
+
* @internal
|
|
1903
|
+
* @param values - List of words to join
|
|
1904
|
+
* @param conjunction - Conjunction for the last element.
|
|
1905
|
+
* @returns String with the words naturally joined with a conjunction.
|
|
1997
1906
|
*/
|
|
1998
|
-
function
|
|
1999
|
-
|
|
1907
|
+
function naturalJoin(values, conjunction = "or") {
|
|
1908
|
+
switch (values.length) {
|
|
1909
|
+
case 0:
|
|
1910
|
+
return "";
|
|
1911
|
+
case 1:
|
|
1912
|
+
return values[0];
|
|
1913
|
+
case 2:
|
|
1914
|
+
return `${values[0]} ${conjunction} ${values[1]}`;
|
|
1915
|
+
default:
|
|
1916
|
+
return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
|
|
1917
|
+
}
|
|
2000
1918
|
}
|
|
1919
|
+
|
|
2001
1920
|
/**
|
|
2002
|
-
*
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1921
|
+
* @internal
|
|
1922
|
+
*/
|
|
1923
|
+
function allowedIfAttributeIsPresent(...attr) {
|
|
1924
|
+
return (node) => {
|
|
1925
|
+
if (attr.some((it) => node.hasAttribute(it))) {
|
|
1926
|
+
return null;
|
|
1927
|
+
}
|
|
1928
|
+
const expected = naturalJoin(attr.map((it) => `"${it}"`));
|
|
1929
|
+
return `requires ${expected} attribute to be present`;
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* @internal
|
|
1934
|
+
*/
|
|
1935
|
+
function allowedIfAttributeIsAbsent(...attr) {
|
|
1936
|
+
return (node) => {
|
|
1937
|
+
const present = attr.filter((it) => node.hasAttribute(it));
|
|
1938
|
+
if (present.length === 0) {
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
const expected = naturalJoin(present.map((it) => `"${it}"`));
|
|
1942
|
+
return `cannot be used at the same time as ${expected}`;
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* @internal
|
|
1947
|
+
*/
|
|
1948
|
+
function allowedIfAttributeHasValue(key, expectedValue, { defaultValue } = {}) {
|
|
1949
|
+
return (node) => {
|
|
1950
|
+
const attr = node.getAttribute(key);
|
|
1951
|
+
if (attr === null || attr === void 0 ? void 0 : attr.isDynamic) {
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1954
|
+
const actualValue = (attr === null || attr === void 0 ? void 0 : attr.value) ? attr.value.toString() : defaultValue;
|
|
1955
|
+
if (actualValue && expectedValue.includes(actualValue.toLocaleLowerCase())) {
|
|
1956
|
+
return null;
|
|
1957
|
+
}
|
|
1958
|
+
const expected = naturalJoin(expectedValue.map((it) => `"${it}"`));
|
|
1959
|
+
return `"${key}" attribute must be ${expected}`;
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
const metadataHelper = {
|
|
1963
|
+
allowedIfAttributeIsPresent,
|
|
1964
|
+
allowedIfAttributeIsAbsent,
|
|
1965
|
+
allowedIfAttributeHasValue,
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
/**
|
|
1969
|
+
* Computes hash for given string.
|
|
2006
1970
|
*
|
|
2007
|
-
* @
|
|
1971
|
+
* @internal
|
|
2008
1972
|
*/
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
return this.text.toString();
|
|
2024
|
-
}
|
|
2025
|
-
/**
|
|
2026
|
-
* Flag set to true if the attribute value is static.
|
|
2027
|
-
*/
|
|
2028
|
-
get isStatic() {
|
|
2029
|
-
return !this.isDynamic;
|
|
1973
|
+
function cyrb53(str) {
|
|
1974
|
+
const a = 2654435761;
|
|
1975
|
+
const b = 1597334677;
|
|
1976
|
+
const c = 2246822507;
|
|
1977
|
+
const d = 3266489909;
|
|
1978
|
+
const e = 4294967296;
|
|
1979
|
+
const f = 2097151;
|
|
1980
|
+
const seed = 0;
|
|
1981
|
+
let h1 = 0xdeadbeef ^ seed;
|
|
1982
|
+
let h2 = 0x41c6ce57 ^ seed;
|
|
1983
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
1984
|
+
ch = str.charCodeAt(i);
|
|
1985
|
+
h1 = Math.imul(h1 ^ ch, a);
|
|
1986
|
+
h2 = Math.imul(h2 ^ ch, b);
|
|
2030
1987
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
1988
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), c) ^ Math.imul(h2 ^ (h2 >>> 13), d);
|
|
1989
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), c) ^ Math.imul(h1 ^ (h1 >>> 13), d);
|
|
1990
|
+
return e * (f & h2) + (h1 >>> 0);
|
|
1991
|
+
}
|
|
1992
|
+
const computeHash = cyrb53;
|
|
1993
|
+
|
|
1994
|
+
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../");
|
|
1995
|
+
const legacyRequire = createRequire(import.meta.url);
|
|
1996
|
+
const distFolder = path.resolve(projectRoot, "dist/es");
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* Similar to `require(..)` but removes the cached copy first.
|
|
2000
|
+
*/
|
|
2001
|
+
function requireUncached(moduleId) {
|
|
2002
|
+
const filename = legacyRequire.resolve(moduleId);
|
|
2003
|
+
/* remove references from the parent module to prevent memory leak */
|
|
2004
|
+
const m = legacyRequire.cache[filename];
|
|
2005
|
+
if (m && m.parent) {
|
|
2006
|
+
const { parent } = m;
|
|
2007
|
+
for (let i = parent.children.length - 1; i >= 0; i--) {
|
|
2008
|
+
if (parent.children[i].id === filename) {
|
|
2009
|
+
parent.children.splice(i, 1);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2036
2012
|
}
|
|
2013
|
+
/* remove old module from cache */
|
|
2014
|
+
delete legacyRequire.cache[filename];
|
|
2015
|
+
return legacyRequire(filename);
|
|
2037
2016
|
}
|
|
2038
2017
|
|
|
2018
|
+
const $schema$1 = "http://json-schema.org/draft-06/schema#";
|
|
2019
|
+
const $id$1 = "https://html-validate.org/schemas/elements.json";
|
|
2020
|
+
const type$1 = "object";
|
|
2021
|
+
const properties$1 = {
|
|
2022
|
+
$schema: {
|
|
2023
|
+
type: "string"
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
const patternProperties = {
|
|
2027
|
+
"^[^$].*$": {
|
|
2028
|
+
type: "object",
|
|
2029
|
+
properties: {
|
|
2030
|
+
inherit: {
|
|
2031
|
+
title: "Inherit from another element",
|
|
2032
|
+
description: "Most properties from the parent element will be copied onto this one",
|
|
2033
|
+
type: "string"
|
|
2034
|
+
},
|
|
2035
|
+
embedded: {
|
|
2036
|
+
title: "Mark this element as belonging in the embedded content category",
|
|
2037
|
+
$ref: "#/definitions/contentCategory"
|
|
2038
|
+
},
|
|
2039
|
+
flow: {
|
|
2040
|
+
title: "Mark this element as belonging in the flow content category",
|
|
2041
|
+
$ref: "#/definitions/contentCategory"
|
|
2042
|
+
},
|
|
2043
|
+
heading: {
|
|
2044
|
+
title: "Mark this element as belonging in the heading content category",
|
|
2045
|
+
$ref: "#/definitions/contentCategory"
|
|
2046
|
+
},
|
|
2047
|
+
interactive: {
|
|
2048
|
+
title: "Mark this element as belonging in the interactive content category",
|
|
2049
|
+
$ref: "#/definitions/contentCategory"
|
|
2050
|
+
},
|
|
2051
|
+
metadata: {
|
|
2052
|
+
title: "Mark this element as belonging in the metadata content category",
|
|
2053
|
+
$ref: "#/definitions/contentCategory"
|
|
2054
|
+
},
|
|
2055
|
+
phrasing: {
|
|
2056
|
+
title: "Mark this element as belonging in the phrasing content category",
|
|
2057
|
+
$ref: "#/definitions/contentCategory"
|
|
2058
|
+
},
|
|
2059
|
+
sectioning: {
|
|
2060
|
+
title: "Mark this element as belonging in the sectioning content category",
|
|
2061
|
+
$ref: "#/definitions/contentCategory"
|
|
2062
|
+
},
|
|
2063
|
+
deprecated: {
|
|
2064
|
+
title: "Mark element as deprecated",
|
|
2065
|
+
description: "Deprecated elements should not be used. If a message is provided it will be included in the error",
|
|
2066
|
+
anyOf: [
|
|
2067
|
+
{
|
|
2068
|
+
type: "boolean"
|
|
2069
|
+
},
|
|
2070
|
+
{
|
|
2071
|
+
type: "string"
|
|
2072
|
+
},
|
|
2073
|
+
{
|
|
2074
|
+
$ref: "#/definitions/deprecatedElement"
|
|
2075
|
+
}
|
|
2076
|
+
]
|
|
2077
|
+
},
|
|
2078
|
+
foreign: {
|
|
2079
|
+
title: "Mark element as foreign",
|
|
2080
|
+
description: "Foreign elements are elements which have a start and end tag but is otherwize not parsed",
|
|
2081
|
+
type: "boolean"
|
|
2082
|
+
},
|
|
2083
|
+
"void": {
|
|
2084
|
+
title: "Mark element as void",
|
|
2085
|
+
description: "Void elements are elements which cannot have content and thus must not use an end tag",
|
|
2086
|
+
type: "boolean"
|
|
2087
|
+
},
|
|
2088
|
+
transparent: {
|
|
2089
|
+
title: "Mark element as transparent",
|
|
2090
|
+
description: "Transparent elements follows the same content model as its parent, i.e. the content must be allowed in the parent.",
|
|
2091
|
+
anyOf: [
|
|
2092
|
+
{
|
|
2093
|
+
type: "boolean"
|
|
2094
|
+
},
|
|
2095
|
+
{
|
|
2096
|
+
type: "array",
|
|
2097
|
+
items: {
|
|
2098
|
+
type: "string"
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
]
|
|
2102
|
+
},
|
|
2103
|
+
implicitClosed: {
|
|
2104
|
+
title: "List of elements which implicitly closes this element",
|
|
2105
|
+
description: "Some elements are automatically closed when another start tag occurs",
|
|
2106
|
+
type: "array",
|
|
2107
|
+
items: {
|
|
2108
|
+
type: "string"
|
|
2109
|
+
}
|
|
2110
|
+
},
|
|
2111
|
+
scriptSupporting: {
|
|
2112
|
+
title: "Mark element as script-supporting",
|
|
2113
|
+
description: "Script-supporting elements are elements which can be inserted where othersise not permitted to assist in templating",
|
|
2114
|
+
type: "boolean"
|
|
2115
|
+
},
|
|
2116
|
+
form: {
|
|
2117
|
+
title: "Mark element as a submittable form element",
|
|
2118
|
+
type: "boolean"
|
|
2119
|
+
},
|
|
2120
|
+
labelable: {
|
|
2121
|
+
title: "Mark this element as labelable",
|
|
2122
|
+
description: "This element may contain an associated label element.",
|
|
2123
|
+
anyOf: [
|
|
2124
|
+
{
|
|
2125
|
+
type: "boolean"
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
$ref: "#/definitions/expression"
|
|
2129
|
+
}
|
|
2130
|
+
]
|
|
2131
|
+
},
|
|
2132
|
+
deprecatedAttributes: {
|
|
2133
|
+
title: "List of deprecated attributes",
|
|
2134
|
+
type: "array",
|
|
2135
|
+
items: {
|
|
2136
|
+
type: "string"
|
|
2137
|
+
}
|
|
2138
|
+
},
|
|
2139
|
+
requiredAttributes: {
|
|
2140
|
+
title: "List of required attributes",
|
|
2141
|
+
type: "array",
|
|
2142
|
+
items: {
|
|
2143
|
+
type: "string"
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
attributes: {
|
|
2147
|
+
title: "List of known attributes and allowed values",
|
|
2148
|
+
$ref: "#/definitions/PermittedAttribute"
|
|
2149
|
+
},
|
|
2150
|
+
permittedContent: {
|
|
2151
|
+
title: "List of elements or categories allowed as content in this element",
|
|
2152
|
+
$ref: "#/definitions/Permitted"
|
|
2153
|
+
},
|
|
2154
|
+
permittedDescendants: {
|
|
2155
|
+
title: "List of elements or categories allowed as descendants in this element",
|
|
2156
|
+
$ref: "#/definitions/Permitted"
|
|
2157
|
+
},
|
|
2158
|
+
permittedOrder: {
|
|
2159
|
+
title: "Required order of child elements",
|
|
2160
|
+
$ref: "#/definitions/PermittedOrder"
|
|
2161
|
+
},
|
|
2162
|
+
permittedParent: {
|
|
2163
|
+
title: "List of elements or categories allowed as parent to this element",
|
|
2164
|
+
$ref: "#/definitions/Permitted"
|
|
2165
|
+
},
|
|
2166
|
+
requiredAncestors: {
|
|
2167
|
+
title: "List of required ancestor elements",
|
|
2168
|
+
$ref: "#/definitions/RequiredAncestors"
|
|
2169
|
+
},
|
|
2170
|
+
requiredContent: {
|
|
2171
|
+
title: "List of required content elements",
|
|
2172
|
+
$ref: "#/definitions/RequiredContent"
|
|
2173
|
+
},
|
|
2174
|
+
textContent: {
|
|
2175
|
+
title: "Allow, disallow or require textual content",
|
|
2176
|
+
description: "This property controls whenever an element allows, disallows or requires text. Text from any descendant counts, not only direct children",
|
|
2177
|
+
"default": "default",
|
|
2178
|
+
type: "string",
|
|
2179
|
+
"enum": [
|
|
2180
|
+
"none",
|
|
2181
|
+
"default",
|
|
2182
|
+
"required",
|
|
2183
|
+
"accessible"
|
|
2184
|
+
]
|
|
2185
|
+
}
|
|
2186
|
+
},
|
|
2187
|
+
additionalProperties: false
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
const definitions = {
|
|
2191
|
+
contentCategory: {
|
|
2192
|
+
anyOf: [
|
|
2193
|
+
{
|
|
2194
|
+
type: "boolean"
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
$ref: "#/definitions/expression"
|
|
2198
|
+
}
|
|
2199
|
+
]
|
|
2200
|
+
},
|
|
2201
|
+
expression: {
|
|
2202
|
+
type: "array",
|
|
2203
|
+
minItems: 2,
|
|
2204
|
+
maxItems: 2,
|
|
2205
|
+
items: [
|
|
2206
|
+
{
|
|
2207
|
+
type: "string",
|
|
2208
|
+
"enum": [
|
|
2209
|
+
"isDescendant",
|
|
2210
|
+
"hasAttribute",
|
|
2211
|
+
"matchAttribute"
|
|
2212
|
+
]
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
anyOf: [
|
|
2216
|
+
{
|
|
2217
|
+
type: "string"
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
$ref: "#/definitions/operation"
|
|
2221
|
+
}
|
|
2222
|
+
]
|
|
2223
|
+
}
|
|
2224
|
+
]
|
|
2225
|
+
},
|
|
2226
|
+
operation: {
|
|
2227
|
+
type: "array",
|
|
2228
|
+
minItems: 3,
|
|
2229
|
+
maxItems: 3,
|
|
2230
|
+
items: [
|
|
2231
|
+
{
|
|
2232
|
+
type: "string"
|
|
2233
|
+
},
|
|
2234
|
+
{
|
|
2235
|
+
type: "string",
|
|
2236
|
+
"enum": [
|
|
2237
|
+
"!=",
|
|
2238
|
+
"="
|
|
2239
|
+
]
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
type: "string"
|
|
2243
|
+
}
|
|
2244
|
+
]
|
|
2245
|
+
},
|
|
2246
|
+
deprecatedElement: {
|
|
2247
|
+
type: "object",
|
|
2248
|
+
additionalProperties: false,
|
|
2249
|
+
properties: {
|
|
2250
|
+
message: {
|
|
2251
|
+
type: "string",
|
|
2252
|
+
title: "A short text message shown next to the regular error message."
|
|
2253
|
+
},
|
|
2254
|
+
documentation: {
|
|
2255
|
+
type: "string",
|
|
2256
|
+
title: "An extended markdown formatted message shown with the contextual rule documentation."
|
|
2257
|
+
},
|
|
2258
|
+
source: {
|
|
2259
|
+
type: "string",
|
|
2260
|
+
title: "Element source, e.g. what standard or library deprecated this element.",
|
|
2261
|
+
"default": "html5"
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
},
|
|
2265
|
+
Permitted: {
|
|
2266
|
+
type: "array",
|
|
2267
|
+
items: {
|
|
2268
|
+
anyOf: [
|
|
2269
|
+
{
|
|
2270
|
+
type: "string"
|
|
2271
|
+
},
|
|
2272
|
+
{
|
|
2273
|
+
type: "array",
|
|
2274
|
+
items: {
|
|
2275
|
+
anyOf: [
|
|
2276
|
+
{
|
|
2277
|
+
type: "string"
|
|
2278
|
+
},
|
|
2279
|
+
{
|
|
2280
|
+
$ref: "#/definitions/PermittedGroup"
|
|
2281
|
+
}
|
|
2282
|
+
]
|
|
2283
|
+
}
|
|
2284
|
+
},
|
|
2285
|
+
{
|
|
2286
|
+
$ref: "#/definitions/PermittedGroup"
|
|
2287
|
+
}
|
|
2288
|
+
]
|
|
2289
|
+
}
|
|
2290
|
+
},
|
|
2291
|
+
PermittedAttribute: {
|
|
2292
|
+
type: "object",
|
|
2293
|
+
patternProperties: {
|
|
2294
|
+
"^.*$": {
|
|
2295
|
+
anyOf: [
|
|
2296
|
+
{
|
|
2297
|
+
type: "object",
|
|
2298
|
+
additionalProperties: false,
|
|
2299
|
+
properties: {
|
|
2300
|
+
allowed: {
|
|
2301
|
+
"function": true,
|
|
2302
|
+
title: "Set to a function to evaluate if this attribute is allowed in this context"
|
|
2303
|
+
},
|
|
2304
|
+
boolean: {
|
|
2305
|
+
type: "boolean",
|
|
2306
|
+
title: "Set to true if this is a boolean attribute"
|
|
2307
|
+
},
|
|
2308
|
+
deprecated: {
|
|
2309
|
+
title: "Set to true or string if this attribute is deprecated",
|
|
2310
|
+
oneOf: [
|
|
2311
|
+
{
|
|
2312
|
+
type: "boolean"
|
|
2313
|
+
},
|
|
2314
|
+
{
|
|
2315
|
+
type: "string"
|
|
2316
|
+
}
|
|
2317
|
+
]
|
|
2318
|
+
},
|
|
2319
|
+
list: {
|
|
2320
|
+
type: "boolean",
|
|
2321
|
+
title: "Set to true if this attribute is a list of space-separated tokens, each which must be valid by itself"
|
|
2322
|
+
},
|
|
2323
|
+
"enum": {
|
|
2324
|
+
type: "array",
|
|
2325
|
+
title: "Exhaustive list of values (string or regex) this attribute accepts",
|
|
2326
|
+
uniqueItems: true,
|
|
2327
|
+
items: {
|
|
2328
|
+
anyOf: [
|
|
2329
|
+
{
|
|
2330
|
+
type: "string"
|
|
2331
|
+
},
|
|
2332
|
+
{
|
|
2333
|
+
regexp: true
|
|
2334
|
+
}
|
|
2335
|
+
]
|
|
2336
|
+
}
|
|
2337
|
+
},
|
|
2338
|
+
omit: {
|
|
2339
|
+
type: "boolean",
|
|
2340
|
+
title: "Set to true if this attribute can optionally omit its value"
|
|
2341
|
+
},
|
|
2342
|
+
required: {
|
|
2343
|
+
type: "boolean",
|
|
2344
|
+
title: "Set to true if this attribute is required"
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
},
|
|
2348
|
+
{
|
|
2349
|
+
type: "array",
|
|
2350
|
+
uniqueItems: true,
|
|
2351
|
+
items: {
|
|
2352
|
+
type: "string"
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
]
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
},
|
|
2359
|
+
PermittedGroup: {
|
|
2360
|
+
type: "object",
|
|
2361
|
+
additionalProperties: false,
|
|
2362
|
+
properties: {
|
|
2363
|
+
exclude: {
|
|
2364
|
+
anyOf: [
|
|
2365
|
+
{
|
|
2366
|
+
items: {
|
|
2367
|
+
type: "string"
|
|
2368
|
+
},
|
|
2369
|
+
type: "array"
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
type: "string"
|
|
2373
|
+
}
|
|
2374
|
+
]
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
},
|
|
2378
|
+
PermittedOrder: {
|
|
2379
|
+
type: "array",
|
|
2380
|
+
items: {
|
|
2381
|
+
type: "string"
|
|
2382
|
+
}
|
|
2383
|
+
},
|
|
2384
|
+
RequiredAncestors: {
|
|
2385
|
+
type: "array",
|
|
2386
|
+
items: {
|
|
2387
|
+
type: "string"
|
|
2388
|
+
}
|
|
2389
|
+
},
|
|
2390
|
+
RequiredContent: {
|
|
2391
|
+
type: "array",
|
|
2392
|
+
items: {
|
|
2393
|
+
type: "string"
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
var schema = {
|
|
2398
|
+
$schema: $schema$1,
|
|
2399
|
+
$id: $id$1,
|
|
2400
|
+
type: type$1,
|
|
2401
|
+
properties: properties$1,
|
|
2402
|
+
patternProperties: patternProperties,
|
|
2403
|
+
definitions: definitions
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2039
2406
|
/**
|
|
2040
|
-
*
|
|
2041
|
-
|
|
2042
|
-
var NodeClosed;
|
|
2043
|
-
(function (NodeClosed) {
|
|
2044
|
-
NodeClosed[NodeClosed["Open"] = 0] = "Open";
|
|
2045
|
-
NodeClosed[NodeClosed["EndTag"] = 1] = "EndTag";
|
|
2046
|
-
NodeClosed[NodeClosed["VoidOmitted"] = 2] = "VoidOmitted";
|
|
2047
|
-
NodeClosed[NodeClosed["VoidSelfClosed"] = 3] = "VoidSelfClosed";
|
|
2048
|
-
NodeClosed[NodeClosed["ImplicitClosed"] = 4] = "ImplicitClosed";
|
|
2049
|
-
})(NodeClosed || (NodeClosed = {}));
|
|
2050
|
-
/**
|
|
2051
|
-
* Returns true if the node is an element node.
|
|
2052
|
-
*
|
|
2053
|
-
* @public
|
|
2054
|
-
*/
|
|
2055
|
-
function isElementNode(node) {
|
|
2056
|
-
return Boolean(node && node.nodeType === NodeType.ELEMENT_NODE);
|
|
2057
|
-
}
|
|
2058
|
-
function isValidTagName(tagName) {
|
|
2059
|
-
return Boolean(tagName !== "" && tagName !== "*");
|
|
2060
|
-
}
|
|
2061
|
-
/**
|
|
2062
|
-
* @public
|
|
2407
|
+
* AJV keyword "regexp" to validate the type to be a regular expression.
|
|
2408
|
+
* Injects errors with the "type" keyword to give the same output.
|
|
2063
2409
|
*/
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
if (parent) {
|
|
2080
|
-
parent.childNodes.push(this);
|
|
2081
|
-
/* calculate depth in domtree */
|
|
2082
|
-
let cur = parent;
|
|
2083
|
-
while (cur.parent) {
|
|
2084
|
-
this.depth++;
|
|
2085
|
-
cur = cur.parent;
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
/**
|
|
2090
|
-
* @internal
|
|
2091
|
-
*/
|
|
2092
|
-
static rootNode(location) {
|
|
2093
|
-
const root = new HtmlElement(undefined, null, NodeClosed.EndTag, null, location);
|
|
2094
|
-
root.setAnnotation("#document");
|
|
2095
|
-
return root;
|
|
2096
|
-
}
|
|
2097
|
-
/**
|
|
2098
|
-
* @internal
|
|
2099
|
-
*
|
|
2100
|
-
* @param namespace - If given it is appended to the tagName.
|
|
2101
|
-
*/
|
|
2102
|
-
static fromTokens(startToken, endToken, parent, metaTable, namespace = "") {
|
|
2103
|
-
const name = startToken.data[2];
|
|
2104
|
-
const tagName = namespace ? `${namespace}:${name}` : name;
|
|
2105
|
-
if (!name) {
|
|
2106
|
-
throw new Error("tagName cannot be empty");
|
|
2107
|
-
}
|
|
2108
|
-
const meta = metaTable ? metaTable.getMetaFor(tagName) : null;
|
|
2109
|
-
const open = startToken.data[1] !== "/";
|
|
2110
|
-
const closed = isClosed(endToken, meta);
|
|
2111
|
-
/* location contains position of '<' so strip it out */
|
|
2112
|
-
const location = sliceLocation(startToken.location, 1);
|
|
2113
|
-
return new HtmlElement(tagName, open ? parent : null, closed, meta, location);
|
|
2114
|
-
}
|
|
2115
|
-
/**
|
|
2116
|
-
* Returns annotated name if set or defaults to `<tagName>`.
|
|
2117
|
-
*
|
|
2118
|
-
* E.g. `my-annotation` or `<div>`.
|
|
2119
|
-
*/
|
|
2120
|
-
get annotatedName() {
|
|
2121
|
-
if (this.annotation) {
|
|
2122
|
-
return this.annotation;
|
|
2123
|
-
}
|
|
2124
|
-
else {
|
|
2125
|
-
return `<${this.tagName}>`;
|
|
2126
|
-
}
|
|
2410
|
+
/* istanbul ignore next: manual testing */
|
|
2411
|
+
const ajvRegexpValidate = function (data, dataCxt) {
|
|
2412
|
+
const valid = data instanceof RegExp;
|
|
2413
|
+
if (!valid) {
|
|
2414
|
+
ajvRegexpValidate.errors = [
|
|
2415
|
+
{
|
|
2416
|
+
instancePath: dataCxt === null || dataCxt === void 0 ? void 0 : dataCxt.instancePath,
|
|
2417
|
+
schemaPath: undefined,
|
|
2418
|
+
keyword: "type",
|
|
2419
|
+
message: "should be a regular expression",
|
|
2420
|
+
params: {
|
|
2421
|
+
keyword: "type",
|
|
2422
|
+
},
|
|
2423
|
+
},
|
|
2424
|
+
];
|
|
2127
2425
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2426
|
+
return valid;
|
|
2427
|
+
};
|
|
2428
|
+
const ajvRegexpKeyword = {
|
|
2429
|
+
keyword: "regexp",
|
|
2430
|
+
schema: false,
|
|
2431
|
+
errors: true,
|
|
2432
|
+
validate: ajvRegexpValidate,
|
|
2433
|
+
};
|
|
2434
|
+
|
|
2435
|
+
/**
|
|
2436
|
+
* AJV keyword "function" to validate the type to be a function. Injects errors
|
|
2437
|
+
* with the "type" keyword to give the same output.
|
|
2438
|
+
*/
|
|
2439
|
+
const ajvFunctionValidate = function (data, dataCxt) {
|
|
2440
|
+
const valid = typeof data === "function";
|
|
2441
|
+
if (!valid) {
|
|
2442
|
+
ajvFunctionValidate.errors = [
|
|
2443
|
+
{
|
|
2444
|
+
instancePath: /* istanbul ignore next */ dataCxt === null || dataCxt === void 0 ? void 0 : dataCxt.instancePath,
|
|
2445
|
+
schemaPath: undefined,
|
|
2446
|
+
keyword: "type",
|
|
2447
|
+
message: "should be a function",
|
|
2448
|
+
params: {
|
|
2449
|
+
keyword: "type",
|
|
2450
|
+
},
|
|
2451
|
+
},
|
|
2452
|
+
];
|
|
2146
2453
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2454
|
+
return valid;
|
|
2455
|
+
};
|
|
2456
|
+
const ajvFunctionKeyword = {
|
|
2457
|
+
keyword: "function",
|
|
2458
|
+
schema: false,
|
|
2459
|
+
errors: true,
|
|
2460
|
+
validate: ajvFunctionValidate,
|
|
2461
|
+
};
|
|
2462
|
+
|
|
2463
|
+
function isSet(value) {
|
|
2464
|
+
return typeof value !== "undefined";
|
|
2465
|
+
}
|
|
2466
|
+
function flag(value) {
|
|
2467
|
+
return value ? true : undefined;
|
|
2468
|
+
}
|
|
2469
|
+
function stripUndefined(src) {
|
|
2470
|
+
const entries = Object.entries(src).filter(([, value]) => isSet(value));
|
|
2471
|
+
return Object.fromEntries(entries);
|
|
2472
|
+
}
|
|
2473
|
+
function migrateSingleAttribute(src, key) {
|
|
2474
|
+
var _a, _b;
|
|
2475
|
+
const result = {};
|
|
2476
|
+
result.deprecated = flag((_a = src.deprecatedAttributes) === null || _a === void 0 ? void 0 : _a.includes(key));
|
|
2477
|
+
result.required = flag((_b = src.requiredAttributes) === null || _b === void 0 ? void 0 : _b.includes(key));
|
|
2478
|
+
result.omit = undefined;
|
|
2479
|
+
const attr = src.attributes ? src.attributes[key] : undefined;
|
|
2480
|
+
if (typeof attr === "undefined") {
|
|
2481
|
+
return stripUndefined(result);
|
|
2152
2482
|
}
|
|
2153
|
-
|
|
2154
|
-
*
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
closest(selectors) {
|
|
2159
|
-
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
|
2160
|
-
let node = this;
|
|
2161
|
-
while (node) {
|
|
2162
|
-
if (node.matches(selectors)) {
|
|
2163
|
-
return node;
|
|
2164
|
-
}
|
|
2165
|
-
node = node.parent;
|
|
2166
|
-
}
|
|
2167
|
-
return null;
|
|
2483
|
+
/* when the attribute is set to null we use a special property "delete" to
|
|
2484
|
+
* flag it, if it is still set during merge (inheritance, overwriting, etc) the attribute will be removed */
|
|
2485
|
+
if (attr === null) {
|
|
2486
|
+
result.delete = true;
|
|
2487
|
+
return stripUndefined(result);
|
|
2168
2488
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
*/
|
|
2173
|
-
generateSelector() {
|
|
2174
|
-
/* root element cannot have a selector as it isn't a proper element */
|
|
2175
|
-
if (this.isRootElement()) {
|
|
2176
|
-
return null;
|
|
2177
|
-
}
|
|
2178
|
-
const parts = [];
|
|
2179
|
-
let root;
|
|
2180
|
-
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
|
2181
|
-
for (root = this; root.parent; root = root.parent) {
|
|
2182
|
-
/* .. */
|
|
2489
|
+
if (Array.isArray(attr)) {
|
|
2490
|
+
if (attr.length === 0) {
|
|
2491
|
+
result.boolean = true;
|
|
2183
2492
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
const selector = generateIdSelector(cur.id);
|
|
2189
|
-
const matches = root.querySelectorAll(selector);
|
|
2190
|
-
if (matches.length === 1) {
|
|
2191
|
-
parts.push(selector);
|
|
2192
|
-
break;
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
const parent = cur.parent;
|
|
2196
|
-
const child = parent.childElements;
|
|
2197
|
-
const index = child.findIndex((it) => it.unique === cur.unique);
|
|
2198
|
-
const numOfType = child.filter((it) => it.is(cur.tagName)).length;
|
|
2199
|
-
const solo = numOfType === 1;
|
|
2200
|
-
/* if this is the only tagName in this level of siblings nth-child isn't needed */
|
|
2201
|
-
if (solo) {
|
|
2202
|
-
parts.push(cur.tagName.toLowerCase());
|
|
2203
|
-
continue;
|
|
2493
|
+
else {
|
|
2494
|
+
result.enum = attr.filter((it) => it !== "");
|
|
2495
|
+
if (attr.includes("")) {
|
|
2496
|
+
result.omit = true;
|
|
2204
2497
|
}
|
|
2205
|
-
/* this will generate the worst kind of selector but at least it will be accurate (optimizations welcome) */
|
|
2206
|
-
parts.push(`${cur.tagName.toLowerCase()}:nth-child(${index + 1})`);
|
|
2207
2498
|
}
|
|
2208
|
-
return
|
|
2499
|
+
return stripUndefined(result);
|
|
2209
2500
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
*
|
|
2213
|
-
* If passing "*" this test will pass if any tagname is set.
|
|
2214
|
-
*/
|
|
2215
|
-
is(tagName) {
|
|
2216
|
-
return tagName === "*" || this.tagName.toLowerCase() === tagName.toLowerCase();
|
|
2501
|
+
else {
|
|
2502
|
+
return stripUndefined({ ...result, ...attr });
|
|
2217
2503
|
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
}
|
|
2504
|
+
}
|
|
2505
|
+
function migrateAttributes(src) {
|
|
2506
|
+
var _a, _b, _c;
|
|
2507
|
+
const keys = [
|
|
2508
|
+
...Object.keys((_a = src.attributes) !== null && _a !== void 0 ? _a : {}),
|
|
2509
|
+
...((_b = src.requiredAttributes) !== null && _b !== void 0 ? _b : []),
|
|
2510
|
+
...((_c = src.deprecatedAttributes) !== null && _c !== void 0 ? _c : []),
|
|
2511
|
+
].sort();
|
|
2512
|
+
const entries = keys.map((key) => {
|
|
2513
|
+
return [key, migrateSingleAttribute(src, key)];
|
|
2514
|
+
});
|
|
2515
|
+
return Object.fromEntries(entries);
|
|
2516
|
+
}
|
|
2517
|
+
function migrateElement(src) {
|
|
2518
|
+
const result = {
|
|
2519
|
+
...src,
|
|
2520
|
+
attributes: migrateAttributes(src),
|
|
2521
|
+
textContent: src.textContent,
|
|
2522
|
+
};
|
|
2523
|
+
/* removed properties */
|
|
2524
|
+
delete result.deprecatedAttributes;
|
|
2525
|
+
delete result.requiredAttributes;
|
|
2526
|
+
/* strip out undefined */
|
|
2527
|
+
if (!result.textContent) {
|
|
2528
|
+
delete result.textContent;
|
|
2529
|
+
}
|
|
2530
|
+
return result;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/**
|
|
2534
|
+
* Returns true if given element is a descendant of given tagname.
|
|
2535
|
+
*
|
|
2536
|
+
* @internal
|
|
2537
|
+
*/
|
|
2538
|
+
function isDescendant(node, tagName) {
|
|
2539
|
+
let cur = node.parent;
|
|
2540
|
+
while (cur && !cur.isRootElement()) {
|
|
2541
|
+
if (cur.is(tagName)) {
|
|
2542
|
+
return true;
|
|
2258
2543
|
}
|
|
2544
|
+
cur = cur.parent;
|
|
2545
|
+
}
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* Returns true if given element has given attribute (no matter the value, null,
|
|
2551
|
+
* dynamic, etc).
|
|
2552
|
+
*/
|
|
2553
|
+
function hasAttribute(node, attr) {
|
|
2554
|
+
return node.hasAttribute(attr);
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
/**
|
|
2558
|
+
* Matches attribute against value.
|
|
2559
|
+
*/
|
|
2560
|
+
function matchAttribute(node, key, op, value) {
|
|
2561
|
+
const nodeValue = (node.getAttributeValue(key) || "").toLowerCase();
|
|
2562
|
+
switch (op) {
|
|
2563
|
+
case "!=":
|
|
2564
|
+
return nodeValue !== value;
|
|
2565
|
+
case "=":
|
|
2566
|
+
return nodeValue === value;
|
|
2259
2567
|
}
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
const dynamicKeys = [
|
|
2571
|
+
"metadata",
|
|
2572
|
+
"flow",
|
|
2573
|
+
"sectioning",
|
|
2574
|
+
"heading",
|
|
2575
|
+
"phrasing",
|
|
2576
|
+
"embedded",
|
|
2577
|
+
"interactive",
|
|
2578
|
+
"labelable",
|
|
2579
|
+
];
|
|
2580
|
+
const functionTable = {
|
|
2581
|
+
isDescendant: isDescendantFacade,
|
|
2582
|
+
hasAttribute: hasAttributeFacade,
|
|
2583
|
+
matchAttribute: matchAttributeFacade,
|
|
2584
|
+
};
|
|
2585
|
+
const schemaCache = new Map();
|
|
2586
|
+
function clone(src) {
|
|
2587
|
+
return JSON.parse(JSON.stringify(src));
|
|
2588
|
+
}
|
|
2589
|
+
function overwriteMerge$1(a, b) {
|
|
2590
|
+
return b;
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* @public
|
|
2594
|
+
*/
|
|
2595
|
+
class MetaTable {
|
|
2260
2596
|
/**
|
|
2261
|
-
*
|
|
2262
|
-
* matches.
|
|
2263
|
-
*
|
|
2264
|
-
* Implementation of DOM specification of Element.matches(selectors).
|
|
2597
|
+
* @internal
|
|
2265
2598
|
*/
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
let root = this;
|
|
2270
|
-
while (root.parent) {
|
|
2271
|
-
root = root.parent;
|
|
2272
|
-
}
|
|
2273
|
-
/* a bit slow implementation as it finds all candidates for the selector and
|
|
2274
|
-
* then tests if any of them are the current element. A better
|
|
2275
|
-
* implementation would be to walk the selector right-to-left and test
|
|
2276
|
-
* ancestors. */
|
|
2277
|
-
for (const match of root.querySelectorAll(selector)) {
|
|
2278
|
-
if (match.unique === this.unique) {
|
|
2279
|
-
return true;
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
return false;
|
|
2283
|
-
}
|
|
2284
|
-
get meta() {
|
|
2285
|
-
return this.metaElement;
|
|
2599
|
+
constructor() {
|
|
2600
|
+
this.elements = {};
|
|
2601
|
+
this.schema = clone(schema);
|
|
2286
2602
|
}
|
|
2287
2603
|
/**
|
|
2288
|
-
*
|
|
2604
|
+
* @internal
|
|
2289
2605
|
*/
|
|
2290
|
-
|
|
2291
|
-
this.
|
|
2606
|
+
init() {
|
|
2607
|
+
this.resolveGlobal();
|
|
2292
2608
|
}
|
|
2293
2609
|
/**
|
|
2294
|
-
*
|
|
2610
|
+
* Extend validation schema.
|
|
2295
2611
|
*
|
|
2296
|
-
* @
|
|
2297
|
-
* @param value - Attribute value. Use `null` if no value is present.
|
|
2298
|
-
* @param keyLocation - Location of the attribute name.
|
|
2299
|
-
* @param valueLocation - Location of the attribute value (excluding quotation)
|
|
2300
|
-
* @param originalAttribute - If attribute is an alias for another attribute
|
|
2301
|
-
* (dynamic attributes) set this to the original attribute name.
|
|
2612
|
+
* @internal
|
|
2302
2613
|
*/
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2614
|
+
extendValidationSchema(patch) {
|
|
2615
|
+
if (patch.properties) {
|
|
2616
|
+
this.schema = deepmerge(this.schema, {
|
|
2617
|
+
patternProperties: {
|
|
2618
|
+
"^[^$].*$": {
|
|
2619
|
+
properties: patch.properties,
|
|
2620
|
+
},
|
|
2621
|
+
},
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
if (patch.definitions) {
|
|
2625
|
+
this.schema = deepmerge(this.schema, {
|
|
2626
|
+
definitions: patch.definitions,
|
|
2627
|
+
});
|
|
2307
2628
|
}
|
|
2308
|
-
this.attr[key].push(new Attribute(key, value, keyLocation, valueLocation, originalAttribute));
|
|
2309
2629
|
}
|
|
2310
2630
|
/**
|
|
2311
|
-
*
|
|
2631
|
+
* Load metadata table from object.
|
|
2632
|
+
*
|
|
2633
|
+
* @internal
|
|
2634
|
+
* @param obj - Object with metadata to load
|
|
2635
|
+
* @param filename - Optional filename used when presenting validation error
|
|
2312
2636
|
*/
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
return key in this.attr;
|
|
2321
|
-
}
|
|
2322
|
-
getAttribute(key, all = false) {
|
|
2323
|
-
key = key.toLowerCase();
|
|
2324
|
-
if (key in this.attr) {
|
|
2325
|
-
const matches = this.attr[key];
|
|
2326
|
-
return all ? matches : matches[0];
|
|
2637
|
+
loadFromObject(obj, filename = null) {
|
|
2638
|
+
var _a;
|
|
2639
|
+
const validate = this.getSchemaValidator();
|
|
2640
|
+
if (!validate(obj)) {
|
|
2641
|
+
throw new SchemaValidationError(filename, `Element metadata is not valid`, obj, this.schema,
|
|
2642
|
+
/* istanbul ignore next: AJV sets .errors when validate returns false */
|
|
2643
|
+
(_a = validate.errors) !== null && _a !== void 0 ? _a : []);
|
|
2327
2644
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2645
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2646
|
+
if (key === "$schema")
|
|
2647
|
+
continue;
|
|
2648
|
+
this.addEntry(key, migrateElement(value));
|
|
2330
2649
|
}
|
|
2331
2650
|
}
|
|
2332
2651
|
/**
|
|
2333
|
-
*
|
|
2334
|
-
*
|
|
2335
|
-
* Returns the attribute value if present.
|
|
2336
|
-
*
|
|
2337
|
-
* - Missing attributes return `null`.
|
|
2338
|
-
* - Boolean attributes return `null`.
|
|
2339
|
-
* - `DynamicValue` returns attribute expression.
|
|
2652
|
+
* Load metadata table from filename
|
|
2340
2653
|
*
|
|
2341
|
-
* @
|
|
2342
|
-
* @
|
|
2654
|
+
* @internal
|
|
2655
|
+
* @param filename - Filename to load
|
|
2343
2656
|
*/
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2657
|
+
loadFromFile(filename) {
|
|
2658
|
+
try {
|
|
2659
|
+
/* load using require as it can process both js and json */
|
|
2660
|
+
const data = requireUncached(filename);
|
|
2661
|
+
this.loadFromObject(data, filename);
|
|
2348
2662
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2663
|
+
catch (err) {
|
|
2664
|
+
if (err instanceof SchemaValidationError) {
|
|
2665
|
+
throw err;
|
|
2666
|
+
}
|
|
2667
|
+
throw new UserError(`Failed to load element metadata from "${filename}"`, ensureError(err));
|
|
2351
2668
|
}
|
|
2352
2669
|
}
|
|
2353
2670
|
/**
|
|
2354
|
-
*
|
|
2671
|
+
* Get [[MetaElement]] for the given tag. If no specific metadata is present
|
|
2672
|
+
* the global metadata is returned or null if no global is present.
|
|
2355
2673
|
*
|
|
2356
|
-
* @
|
|
2357
|
-
* @
|
|
2358
|
-
*/
|
|
2359
|
-
appendText(text, location) {
|
|
2360
|
-
this.childNodes.push(new TextNode(text, location));
|
|
2361
|
-
}
|
|
2362
|
-
/**
|
|
2363
|
-
* Return a list of all known classes on the element. Dynamic values are
|
|
2364
|
-
* ignored.
|
|
2674
|
+
* @public
|
|
2675
|
+
* @returns A shallow copy of metadata.
|
|
2365
2676
|
*/
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2677
|
+
getMetaFor(tagName) {
|
|
2678
|
+
/* try to locate by tagname */
|
|
2679
|
+
tagName = tagName.toLowerCase();
|
|
2680
|
+
if (this.elements[tagName]) {
|
|
2681
|
+
return { ...this.elements[tagName] };
|
|
2369
2682
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
return
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* Get element ID if present.
|
|
2378
|
-
*/
|
|
2379
|
-
get id() {
|
|
2380
|
-
return this.getAttributeValue("id");
|
|
2381
|
-
}
|
|
2382
|
-
get style() {
|
|
2383
|
-
const attr = this.getAttribute("style");
|
|
2384
|
-
return parseCssDeclaration(attr === null || attr === void 0 ? void 0 : attr.value);
|
|
2683
|
+
/* try to locate global element */
|
|
2684
|
+
if (this.elements["*"]) {
|
|
2685
|
+
return { ...this.elements["*"] };
|
|
2686
|
+
}
|
|
2687
|
+
return null;
|
|
2385
2688
|
}
|
|
2386
2689
|
/**
|
|
2387
|
-
*
|
|
2690
|
+
* Find all tags which has enabled given property.
|
|
2691
|
+
*
|
|
2692
|
+
* @public
|
|
2388
2693
|
*/
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2694
|
+
getTagsWithProperty(propName) {
|
|
2695
|
+
return Object.entries(this.elements)
|
|
2696
|
+
.filter(([, entry]) => entry[propName])
|
|
2697
|
+
.map(([tagName]) => tagName);
|
|
2392
2698
|
}
|
|
2393
2699
|
/**
|
|
2394
|
-
*
|
|
2700
|
+
* Find tag matching tagName or inheriting from it.
|
|
2701
|
+
*
|
|
2702
|
+
* @public
|
|
2395
2703
|
*/
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
get siblings() {
|
|
2401
|
-
return this.parent ? this.parent.childElements : [this];
|
|
2402
|
-
}
|
|
2403
|
-
get previousSibling() {
|
|
2404
|
-
const i = this.siblings.findIndex((node) => node.unique === this.unique);
|
|
2405
|
-
return i >= 1 ? this.siblings[i - 1] : null;
|
|
2406
|
-
}
|
|
2407
|
-
get nextSibling() {
|
|
2408
|
-
const i = this.siblings.findIndex((node) => node.unique === this.unique);
|
|
2409
|
-
return i <= this.siblings.length - 2 ? this.siblings[i + 1] : null;
|
|
2410
|
-
}
|
|
2411
|
-
getElementsByTagName(tagName) {
|
|
2412
|
-
return this.childElements.reduce((matches, node) => {
|
|
2413
|
-
return matches.concat(node.is(tagName) ? [node] : [], node.getElementsByTagName(tagName));
|
|
2414
|
-
}, []);
|
|
2415
|
-
}
|
|
2416
|
-
querySelector(selector) {
|
|
2417
|
-
var _a;
|
|
2418
|
-
const it = this.querySelectorImpl(selector);
|
|
2419
|
-
return (_a = it.next().value) !== null && _a !== void 0 ? _a : null; // eslint-disable-line @typescript-eslint/no-unsafe-return
|
|
2420
|
-
}
|
|
2421
|
-
querySelectorAll(selector) {
|
|
2422
|
-
const it = this.querySelectorImpl(selector);
|
|
2423
|
-
const unique = new Set(it);
|
|
2424
|
-
return Array.from(unique.values());
|
|
2704
|
+
getTagsDerivedFrom(tagName) {
|
|
2705
|
+
return Object.entries(this.elements)
|
|
2706
|
+
.filter(([key, entry]) => key === tagName || entry.inherit === tagName)
|
|
2707
|
+
.map(([tagName]) => tagName);
|
|
2425
2708
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2709
|
+
addEntry(tagName, entry) {
|
|
2710
|
+
let parent = this.elements[tagName] || {};
|
|
2711
|
+
/* handle inheritance */
|
|
2712
|
+
if (entry.inherit) {
|
|
2713
|
+
const name = entry.inherit;
|
|
2714
|
+
parent = this.elements[name];
|
|
2715
|
+
if (!parent) {
|
|
2716
|
+
throw new UserError(`Element <${tagName}> cannot inherit from <${name}>: no such element`);
|
|
2717
|
+
}
|
|
2433
2718
|
}
|
|
2719
|
+
/* merge all sources together */
|
|
2720
|
+
const expanded = this.mergeElement(parent, { ...entry, tagName });
|
|
2721
|
+
expandRegex(expanded);
|
|
2722
|
+
this.elements[tagName] = expanded;
|
|
2434
2723
|
}
|
|
2435
2724
|
/**
|
|
2436
|
-
*
|
|
2437
|
-
*
|
|
2438
|
-
* @internal
|
|
2725
|
+
* Construct a new AJV schema validator.
|
|
2439
2726
|
*/
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2727
|
+
getSchemaValidator() {
|
|
2728
|
+
const hash = computeHash(JSON.stringify(this.schema));
|
|
2729
|
+
const cached = schemaCache.get(hash);
|
|
2730
|
+
if (cached) {
|
|
2731
|
+
return cached;
|
|
2732
|
+
}
|
|
2733
|
+
else {
|
|
2734
|
+
const ajv = new Ajv({ strict: true, strictTuples: true, strictTypes: true });
|
|
2735
|
+
ajv.addMetaSchema(ajvSchemaDraft);
|
|
2736
|
+
ajv.addKeyword(ajvFunctionKeyword);
|
|
2737
|
+
ajv.addKeyword(ajvRegexpKeyword);
|
|
2738
|
+
ajv.addKeyword({ keyword: "copyable" });
|
|
2739
|
+
const validate = ajv.compile(this.schema);
|
|
2740
|
+
schemaCache.set(hash, validate);
|
|
2741
|
+
return validate;
|
|
2446
2742
|
}
|
|
2447
|
-
visit(this);
|
|
2448
2743
|
}
|
|
2449
2744
|
/**
|
|
2450
|
-
*
|
|
2451
|
-
*
|
|
2452
|
-
* @internal
|
|
2745
|
+
* @public
|
|
2453
2746
|
*/
|
|
2454
|
-
|
|
2455
|
-
return this.
|
|
2456
|
-
function visit(node) {
|
|
2457
|
-
if (callback(node)) {
|
|
2458
|
-
return true;
|
|
2459
|
-
}
|
|
2460
|
-
else {
|
|
2461
|
-
return node.childElements.some(visit);
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2747
|
+
getJSONSchema() {
|
|
2748
|
+
return this.schema;
|
|
2464
2749
|
}
|
|
2465
2750
|
/**
|
|
2466
|
-
*
|
|
2467
|
-
*
|
|
2468
|
-
* @internal
|
|
2751
|
+
* Finds the global element definition and merges each known element with the
|
|
2752
|
+
* global, e.g. to assign global attributes.
|
|
2469
2753
|
*/
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2754
|
+
resolveGlobal() {
|
|
2755
|
+
/* skip if there is no global elements */
|
|
2756
|
+
if (!this.elements["*"])
|
|
2757
|
+
return;
|
|
2758
|
+
/* fetch and remove the global element, it should not be resolvable by
|
|
2759
|
+
* itself */
|
|
2760
|
+
const global = this.elements["*"];
|
|
2761
|
+
delete this.elements["*"];
|
|
2762
|
+
/* hack: unset default properties which global should not override */
|
|
2763
|
+
delete global.tagName;
|
|
2764
|
+
delete global.void;
|
|
2765
|
+
/* merge elements */
|
|
2766
|
+
for (const [tagName, entry] of Object.entries(this.elements)) {
|
|
2767
|
+
this.elements[tagName] = this.mergeElement(global, entry);
|
|
2477
2768
|
}
|
|
2478
2769
|
}
|
|
2770
|
+
mergeElement(a, b) {
|
|
2771
|
+
const merged = deepmerge(a, b, { arrayMerge: overwriteMerge$1 });
|
|
2772
|
+
/* special handling when removing attributes by setting them to null
|
|
2773
|
+
* resulting in the deletion flag being set */
|
|
2774
|
+
const filteredAttrs = Object.entries(merged.attributes).filter(([, attr]) => {
|
|
2775
|
+
const val = !attr.delete;
|
|
2776
|
+
delete attr.delete;
|
|
2777
|
+
return val;
|
|
2778
|
+
});
|
|
2779
|
+
merged.attributes = Object.fromEntries(filteredAttrs);
|
|
2780
|
+
return merged;
|
|
2781
|
+
}
|
|
2479
2782
|
/**
|
|
2480
|
-
* Visit all nodes from this node and down. Breadth first.
|
|
2481
|
-
*
|
|
2482
|
-
* The first node for which the callback evaluates to true is returned.
|
|
2483
|
-
*
|
|
2484
2783
|
* @internal
|
|
2485
2784
|
*/
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
return node;
|
|
2490
|
-
}
|
|
2491
|
-
for (const child of node.childElements) {
|
|
2492
|
-
const match = child.find(callback);
|
|
2493
|
-
if (match) {
|
|
2494
|
-
return match;
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
return null;
|
|
2785
|
+
resolve(node) {
|
|
2786
|
+
if (node.meta) {
|
|
2787
|
+
expandProperties(node, node.meta);
|
|
2498
2788
|
}
|
|
2499
|
-
return visit(this);
|
|
2500
2789
|
}
|
|
2501
2790
|
}
|
|
2502
|
-
function
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
closed = NodeClosed.VoidSelfClosed;
|
|
2791
|
+
function expandProperties(node, entry) {
|
|
2792
|
+
for (const key of dynamicKeys) {
|
|
2793
|
+
const property = entry[key];
|
|
2794
|
+
if (property && typeof property !== "boolean") {
|
|
2795
|
+
setMetaProperty(entry, key, evaluateProperty(node, property));
|
|
2796
|
+
}
|
|
2509
2797
|
}
|
|
2510
|
-
return closed;
|
|
2511
2798
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2799
|
+
/**
|
|
2800
|
+
* Given a string it returns either the string as-is or if the string is wrapped
|
|
2801
|
+
* in /../ it creates and returns a regex instead.
|
|
2802
|
+
*/
|
|
2803
|
+
function expandRegexValue(value) {
|
|
2804
|
+
if (value instanceof RegExp) {
|
|
2805
|
+
return value;
|
|
2518
2806
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2807
|
+
const match = value.match(/^\/\^?([^/$]*)\$?\/([i]*)$/);
|
|
2808
|
+
if (match) {
|
|
2809
|
+
const [, expr, flags] = match;
|
|
2810
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
2811
|
+
return new RegExp(`^${expr}$`, flags);
|
|
2521
2812
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
/* root element should never be popped, continue as if nothing happened */
|
|
2525
|
-
return;
|
|
2526
|
-
}
|
|
2527
|
-
this.active = this.active.parent || this.root;
|
|
2813
|
+
else {
|
|
2814
|
+
return value;
|
|
2528
2815
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2816
|
+
}
|
|
2817
|
+
/**
|
|
2818
|
+
* Expand all regular expressions in strings ("/../"). This mutates the object.
|
|
2819
|
+
*/
|
|
2820
|
+
function expandRegex(entry) {
|
|
2821
|
+
for (const [name, values] of Object.entries(entry.attributes)) {
|
|
2822
|
+
if (values.enum) {
|
|
2823
|
+
entry.attributes[name].enum = values.enum.map(expandRegexValue);
|
|
2824
|
+
}
|
|
2531
2825
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2826
|
+
}
|
|
2827
|
+
function evaluateProperty(node, expr) {
|
|
2828
|
+
const [func, options] = parseExpression(expr);
|
|
2829
|
+
return func(node, options);
|
|
2830
|
+
}
|
|
2831
|
+
function parseExpression(expr) {
|
|
2832
|
+
if (typeof expr === "string") {
|
|
2833
|
+
return parseExpression([expr, {}]);
|
|
2537
2834
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2835
|
+
else {
|
|
2836
|
+
const [funcName, options] = expr;
|
|
2837
|
+
const func = functionTable[funcName];
|
|
2838
|
+
if (!func) {
|
|
2839
|
+
throw new Error(`Failed to find function "${funcName}" when evaluating property expression`);
|
|
2840
|
+
}
|
|
2841
|
+
return [func, options];
|
|
2540
2842
|
}
|
|
2541
|
-
|
|
2542
|
-
|
|
2843
|
+
}
|
|
2844
|
+
function isDescendantFacade(node, tagName) {
|
|
2845
|
+
if (typeof tagName !== "string") {
|
|
2846
|
+
throw new Error(`Property expression "isDescendant" must take string argument when evaluating metadata for <${node.tagName}>`);
|
|
2543
2847
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2848
|
+
return isDescendant(node, tagName);
|
|
2849
|
+
}
|
|
2850
|
+
function hasAttributeFacade(node, attr) {
|
|
2851
|
+
if (typeof attr !== "string") {
|
|
2852
|
+
throw new Error(`Property expression "hasAttribute" must take string argument when evaluating metadata for <${node.tagName}>`);
|
|
2546
2853
|
}
|
|
2547
|
-
|
|
2548
|
-
|
|
2854
|
+
return hasAttribute(node, attr);
|
|
2855
|
+
}
|
|
2856
|
+
function matchAttributeFacade(node, match) {
|
|
2857
|
+
if (!Array.isArray(match) || match.length !== 3) {
|
|
2858
|
+
throw new Error(`Property expression "matchAttribute" must take [key, op, value] array as argument when evaluating metadata for <${node.tagName}>`);
|
|
2549
2859
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2860
|
+
const [key, op, value] = match.map((x) => x.toLowerCase());
|
|
2861
|
+
switch (op) {
|
|
2862
|
+
case "!=":
|
|
2863
|
+
case "=":
|
|
2864
|
+
return matchAttribute(node, key, op, value);
|
|
2865
|
+
default:
|
|
2866
|
+
throw new Error(`Property expression "matchAttribute" has invalid operator "${op}" when evaluating metadata for <${node.tagName}>`);
|
|
2552
2867
|
}
|
|
2553
2868
|
}
|
|
2554
2869
|
|
|
@@ -3191,7 +3506,7 @@ var TRANSFORMER_API;
|
|
|
3191
3506
|
/** @public */
|
|
3192
3507
|
const name = "html-validate";
|
|
3193
3508
|
/** @public */
|
|
3194
|
-
const version = "7.
|
|
3509
|
+
const version = "7.7.0";
|
|
3195
3510
|
/** @public */
|
|
3196
3511
|
const homepage = "https://html-validate.org";
|
|
3197
3512
|
/** @public */
|
|
@@ -3520,7 +3835,7 @@ function ruleDocumentationUrl(filename) {
|
|
|
3520
3835
|
return `${homepage}/rules/${normalized}.html`;
|
|
3521
3836
|
}
|
|
3522
3837
|
|
|
3523
|
-
const defaults$
|
|
3838
|
+
const defaults$t = {
|
|
3524
3839
|
allowExternal: true,
|
|
3525
3840
|
allowRelative: true,
|
|
3526
3841
|
allowAbsolute: true,
|
|
@@ -3564,7 +3879,7 @@ function matchList(value, list) {
|
|
|
3564
3879
|
}
|
|
3565
3880
|
class AllowedLinks extends Rule {
|
|
3566
3881
|
constructor(options) {
|
|
3567
|
-
super({ ...defaults$
|
|
3882
|
+
super({ ...defaults$t, ...options });
|
|
3568
3883
|
this.allowExternal = parseAllow(this.options.allowExternal);
|
|
3569
3884
|
this.allowRelative = parseAllow(this.options.allowRelative);
|
|
3570
3885
|
this.allowAbsolute = parseAllow(this.options.allowAbsolute);
|
|
@@ -3707,6 +4022,109 @@ class AllowedLinks extends Rule {
|
|
|
3707
4022
|
}
|
|
3708
4023
|
}
|
|
3709
4024
|
|
|
4025
|
+
var RuleContext$1;
|
|
4026
|
+
(function (RuleContext) {
|
|
4027
|
+
RuleContext["MISSING_ALT"] = "missing-alt";
|
|
4028
|
+
RuleContext["MISSING_HREF"] = "missing-href";
|
|
4029
|
+
})(RuleContext$1 || (RuleContext$1 = {}));
|
|
4030
|
+
const defaults$s = {
|
|
4031
|
+
accessible: true,
|
|
4032
|
+
};
|
|
4033
|
+
function findByTarget(target, siblings) {
|
|
4034
|
+
return siblings.filter((it) => it.getAttributeValue("href") === target);
|
|
4035
|
+
}
|
|
4036
|
+
function getAltText(node) {
|
|
4037
|
+
return node.getAttributeValue("alt");
|
|
4038
|
+
}
|
|
4039
|
+
function getDescription(context) {
|
|
4040
|
+
switch (context) {
|
|
4041
|
+
case RuleContext$1.MISSING_ALT:
|
|
4042
|
+
return [
|
|
4043
|
+
"The `alt` attribute must be set (and not empty) when the `href` attribute is present on an `<area>` element.",
|
|
4044
|
+
"",
|
|
4045
|
+
"The attribute is used to provide an alternative text description for the area of the image map.",
|
|
4046
|
+
"The text should describe the purpose of area and the resource referenced by the `href` attribute.",
|
|
4047
|
+
"",
|
|
4048
|
+
"Either add the `alt` attribute or remove the `href` attribute.",
|
|
4049
|
+
];
|
|
4050
|
+
case RuleContext$1.MISSING_HREF:
|
|
4051
|
+
return [
|
|
4052
|
+
"The `alt` attribute must not be set when the `href` attribute is missing on an `<area>` element.",
|
|
4053
|
+
"",
|
|
4054
|
+
"Either add the `href` attribute or remove the `alt` attribute.",
|
|
4055
|
+
];
|
|
4056
|
+
default:
|
|
4057
|
+
return [
|
|
4058
|
+
"The `alt` attribute must only be used together with the `href` attribute.",
|
|
4059
|
+
"It must be set if `href` is present and must be omitted if `href` is missing",
|
|
4060
|
+
"",
|
|
4061
|
+
"The attribute is used to provide an alternative text description for the area of the image map.",
|
|
4062
|
+
"The text should describe the purpose of area and the resource referenced by the `href` attribute.",
|
|
4063
|
+
];
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
class AreaAlt extends Rule {
|
|
4067
|
+
constructor(options) {
|
|
4068
|
+
super({ ...defaults$s, ...options });
|
|
4069
|
+
}
|
|
4070
|
+
static schema() {
|
|
4071
|
+
return {
|
|
4072
|
+
accessible: {
|
|
4073
|
+
type: "boolean",
|
|
4074
|
+
},
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
documentation(context) {
|
|
4078
|
+
return {
|
|
4079
|
+
description: getDescription(context).join("\n"),
|
|
4080
|
+
url: ruleDocumentationUrl("@/rules/area-alt.ts"),
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
setup() {
|
|
4084
|
+
this.on("element:ready", this.isRelevant, (event) => {
|
|
4085
|
+
const { target } = event;
|
|
4086
|
+
const siblings = target.querySelectorAll("area");
|
|
4087
|
+
for (const child of siblings) {
|
|
4088
|
+
this.validateArea(child, siblings);
|
|
4089
|
+
}
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
validateArea(area, siblings) {
|
|
4093
|
+
const { accessible } = this.options;
|
|
4094
|
+
const href = area.getAttribute("href");
|
|
4095
|
+
const alt = area.getAttribute("alt");
|
|
4096
|
+
if (href) {
|
|
4097
|
+
if (alt && alt.isDynamic) {
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
const target = area.getAttributeValue("href");
|
|
4101
|
+
const altTexts = accessible
|
|
4102
|
+
? [getAltText(area)]
|
|
4103
|
+
: findByTarget(target, siblings).map(getAltText);
|
|
4104
|
+
if (!altTexts.some(Boolean)) {
|
|
4105
|
+
this.report({
|
|
4106
|
+
node: area,
|
|
4107
|
+
message: `"alt" attribute must be set and non-empty when the "href" attribute is present`,
|
|
4108
|
+
location: alt ? alt.keyLocation : href.keyLocation,
|
|
4109
|
+
context: RuleContext$1.MISSING_ALT,
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
else if (alt) {
|
|
4114
|
+
this.report({
|
|
4115
|
+
node: area,
|
|
4116
|
+
message: `"alt" attribute cannot be used unless the "href" attribute is present`,
|
|
4117
|
+
location: alt.keyLocation,
|
|
4118
|
+
context: RuleContext$1.MISSING_HREF,
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
isRelevant(event) {
|
|
4123
|
+
const { target } = event;
|
|
4124
|
+
return target.is("map");
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
|
|
3710
4128
|
class AriaHiddenBody extends Rule {
|
|
3711
4129
|
documentation() {
|
|
3712
4130
|
return {
|
|
@@ -4463,6 +4881,16 @@ class AttrSpacing extends Rule {
|
|
|
4463
4881
|
}
|
|
4464
4882
|
}
|
|
4465
4883
|
|
|
4884
|
+
function pick(attr) {
|
|
4885
|
+
const result = {};
|
|
4886
|
+
if (typeof attr.enum !== "undefined") {
|
|
4887
|
+
result.enum = attr.enum;
|
|
4888
|
+
}
|
|
4889
|
+
if (typeof attr.boolean !== "undefined") {
|
|
4890
|
+
result.boolean = attr.boolean;
|
|
4891
|
+
}
|
|
4892
|
+
return result;
|
|
4893
|
+
}
|
|
4466
4894
|
class AttributeAllowedValues extends Rule {
|
|
4467
4895
|
documentation(context) {
|
|
4468
4896
|
const docs = {
|
|
@@ -4513,7 +4941,7 @@ class AttributeAllowedValues extends Rule {
|
|
|
4513
4941
|
element: node.tagName,
|
|
4514
4942
|
attribute: attr.key,
|
|
4515
4943
|
value,
|
|
4516
|
-
allowed: meta.attributes[attr.key],
|
|
4944
|
+
allowed: pick(meta.attributes[attr.key]),
|
|
4517
4945
|
};
|
|
4518
4946
|
const message = this.getMessage(attr);
|
|
4519
4947
|
const location = this.getLocation(attr);
|
|
@@ -4709,6 +5137,54 @@ function reportMessage(attr, style) {
|
|
|
4709
5137
|
return "";
|
|
4710
5138
|
}
|
|
4711
5139
|
|
|
5140
|
+
function ruleDescription(context) {
|
|
5141
|
+
if (context) {
|
|
5142
|
+
const { attr, details } = context;
|
|
5143
|
+
return `The "${attr}" attribute cannot be used in this context: ${details}`;
|
|
5144
|
+
}
|
|
5145
|
+
else {
|
|
5146
|
+
return "This attribute cannot be used in this context.";
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
class AttributeMisuse extends Rule {
|
|
5150
|
+
documentation(context) {
|
|
5151
|
+
return {
|
|
5152
|
+
description: ruleDescription(context),
|
|
5153
|
+
url: ruleDocumentationUrl("@/rules/attribute-misuse.ts"),
|
|
5154
|
+
};
|
|
5155
|
+
}
|
|
5156
|
+
setup() {
|
|
5157
|
+
this.on("element:ready", (event) => {
|
|
5158
|
+
const { target } = event;
|
|
5159
|
+
const { meta } = target;
|
|
5160
|
+
if (!meta) {
|
|
5161
|
+
return;
|
|
5162
|
+
}
|
|
5163
|
+
for (const attr of target.attributes) {
|
|
5164
|
+
const key = attr.key.toLowerCase();
|
|
5165
|
+
this.validateAttr(target, attr, meta.attributes[key]);
|
|
5166
|
+
}
|
|
5167
|
+
});
|
|
5168
|
+
}
|
|
5169
|
+
validateAttr(node, attr, meta) {
|
|
5170
|
+
if (!meta || !meta.allowed) {
|
|
5171
|
+
return;
|
|
5172
|
+
}
|
|
5173
|
+
const details = meta.allowed(node);
|
|
5174
|
+
if (details) {
|
|
5175
|
+
this.report({
|
|
5176
|
+
node,
|
|
5177
|
+
message: `"{{ attr }}" attribute cannot be used in this context: {{ details }}`,
|
|
5178
|
+
location: attr.keyLocation,
|
|
5179
|
+
context: {
|
|
5180
|
+
attr: attr.key,
|
|
5181
|
+
details,
|
|
5182
|
+
},
|
|
5183
|
+
});
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
|
|
4712
5188
|
function parsePattern(pattern) {
|
|
4713
5189
|
switch (pattern) {
|
|
4714
5190
|
case "kebabcase":
|
|
@@ -4995,7 +5471,7 @@ class DeprecatedRule extends Rule {
|
|
|
4995
5471
|
}
|
|
4996
5472
|
}
|
|
4997
5473
|
|
|
4998
|
-
|
|
5474
|
+
let NoStyleTag$1 = class NoStyleTag extends Rule {
|
|
4999
5475
|
documentation() {
|
|
5000
5476
|
return {
|
|
5001
5477
|
description: [
|
|
@@ -5016,7 +5492,7 @@ class NoStyleTag$1 extends Rule {
|
|
|
5016
5492
|
}
|
|
5017
5493
|
});
|
|
5018
5494
|
}
|
|
5019
|
-
}
|
|
5495
|
+
};
|
|
5020
5496
|
|
|
5021
5497
|
const defaults$k = {
|
|
5022
5498
|
style: "uppercase",
|
|
@@ -5414,226 +5890,6 @@ class ElementPermittedOrder extends Rule {
|
|
|
5414
5890
|
}
|
|
5415
5891
|
}
|
|
5416
5892
|
|
|
5417
|
-
const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
|
|
5418
|
-
const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
|
|
5419
|
-
const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
|
|
5420
|
-
/**
|
|
5421
|
-
* Tests if this element is present in the accessibility tree.
|
|
5422
|
-
*
|
|
5423
|
-
* In practice it tests whenever the element or its parents has
|
|
5424
|
-
* `role="presentation"` or `aria-hidden="false"`. Dynamic values counts as
|
|
5425
|
-
* visible since the element might be in the visibility tree sometimes.
|
|
5426
|
-
*/
|
|
5427
|
-
function inAccessibilityTree(node) {
|
|
5428
|
-
return !isAriaHidden(node) && !isPresentation(node);
|
|
5429
|
-
}
|
|
5430
|
-
function isAriaHiddenImpl(node) {
|
|
5431
|
-
const isHidden = (node) => {
|
|
5432
|
-
const ariaHidden = node.getAttribute("aria-hidden");
|
|
5433
|
-
return Boolean(ariaHidden && ariaHidden.value === "true");
|
|
5434
|
-
};
|
|
5435
|
-
return {
|
|
5436
|
-
byParent: node.parent ? isAriaHidden(node.parent) : false,
|
|
5437
|
-
bySelf: isHidden(node),
|
|
5438
|
-
};
|
|
5439
|
-
}
|
|
5440
|
-
function isAriaHidden(node, details) {
|
|
5441
|
-
const cached = node.cacheGet(ARIA_HIDDEN_CACHE);
|
|
5442
|
-
if (cached) {
|
|
5443
|
-
return details ? cached : cached.byParent || cached.bySelf;
|
|
5444
|
-
}
|
|
5445
|
-
const result = node.cacheSet(ARIA_HIDDEN_CACHE, isAriaHiddenImpl(node));
|
|
5446
|
-
return details ? result : result.byParent || result.bySelf;
|
|
5447
|
-
}
|
|
5448
|
-
function isHTMLHiddenImpl(node) {
|
|
5449
|
-
const isHidden = (node) => {
|
|
5450
|
-
const hidden = node.getAttribute("hidden");
|
|
5451
|
-
return hidden !== null && hidden.isStatic;
|
|
5452
|
-
};
|
|
5453
|
-
return {
|
|
5454
|
-
byParent: node.parent ? isHTMLHidden(node.parent) : false,
|
|
5455
|
-
bySelf: isHidden(node),
|
|
5456
|
-
};
|
|
5457
|
-
}
|
|
5458
|
-
function isHTMLHidden(node, details) {
|
|
5459
|
-
const cached = node.cacheGet(HTML_HIDDEN_CACHE);
|
|
5460
|
-
if (cached) {
|
|
5461
|
-
return details ? cached : cached.byParent || cached.bySelf;
|
|
5462
|
-
}
|
|
5463
|
-
const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
|
|
5464
|
-
return details ? result : result.byParent || result.bySelf;
|
|
5465
|
-
}
|
|
5466
|
-
/**
|
|
5467
|
-
* Tests if this element or a parent element has role="presentation".
|
|
5468
|
-
*
|
|
5469
|
-
* Dynamic values yields `false` just as if the attribute wasn't present.
|
|
5470
|
-
*/
|
|
5471
|
-
function isPresentation(node) {
|
|
5472
|
-
if (node.cacheExists(ROLE_PRESENTATION_CACHE)) {
|
|
5473
|
-
return Boolean(node.cacheGet(ROLE_PRESENTATION_CACHE));
|
|
5474
|
-
}
|
|
5475
|
-
let cur = node;
|
|
5476
|
-
do {
|
|
5477
|
-
const role = cur.getAttribute("role");
|
|
5478
|
-
/* role="presentation" */
|
|
5479
|
-
if (role && role.value === "presentation") {
|
|
5480
|
-
return cur.cacheSet(ROLE_PRESENTATION_CACHE, true);
|
|
5481
|
-
}
|
|
5482
|
-
/* sanity check: break if no parent is present, normally not an issue as the
|
|
5483
|
-
* root element should be found first */
|
|
5484
|
-
if (!cur.parent) {
|
|
5485
|
-
break;
|
|
5486
|
-
}
|
|
5487
|
-
/* check parents */
|
|
5488
|
-
cur = cur.parent;
|
|
5489
|
-
} while (!cur.isRootElement());
|
|
5490
|
-
return node.cacheSet(ROLE_PRESENTATION_CACHE, false);
|
|
5491
|
-
}
|
|
5492
|
-
|
|
5493
|
-
const cachePrefix = classifyNodeText.name;
|
|
5494
|
-
const HTML_CACHE_KEY = Symbol(`${cachePrefix}|html`);
|
|
5495
|
-
const A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y`);
|
|
5496
|
-
const IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY = Symbol(`${cachePrefix}|html|ignore-hidden-root`);
|
|
5497
|
-
const IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY = Symbol(`${cachePrefix}|a11y|ignore-hidden-root`);
|
|
5498
|
-
/**
|
|
5499
|
-
* @public
|
|
5500
|
-
*/
|
|
5501
|
-
var TextClassification;
|
|
5502
|
-
(function (TextClassification) {
|
|
5503
|
-
TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
|
|
5504
|
-
TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
|
|
5505
|
-
TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
|
|
5506
|
-
})(TextClassification || (TextClassification = {}));
|
|
5507
|
-
function getCachekey(options = {}) {
|
|
5508
|
-
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
5509
|
-
if (accessible && ignoreHiddenRoot) {
|
|
5510
|
-
return IGNORE_HIDDEN_ROOT_A11Y_CACHE_KEY;
|
|
5511
|
-
}
|
|
5512
|
-
else if (ignoreHiddenRoot) {
|
|
5513
|
-
return IGNORE_HIDDEN_ROOT_HTML_CACHE_KEY;
|
|
5514
|
-
}
|
|
5515
|
-
else if (accessible) {
|
|
5516
|
-
return A11Y_CACHE_KEY;
|
|
5517
|
-
}
|
|
5518
|
-
else {
|
|
5519
|
-
return HTML_CACHE_KEY;
|
|
5520
|
-
}
|
|
5521
|
-
}
|
|
5522
|
-
/* While I cannot find a reference about this in the standard the <select>
|
|
5523
|
-
* element kinda acts as if there is no text content, most particularly it
|
|
5524
|
-
* doesn't receive and accessible name. The `.textContent` property does
|
|
5525
|
-
* however include the <option> childrens text. But for the sake of the
|
|
5526
|
-
* validator it is probably best if the classification acts as if there is no
|
|
5527
|
-
* text as I think that is what is expected of the return values. Might have
|
|
5528
|
-
* to revisit this at some point or if someone could clarify what section of
|
|
5529
|
-
* the standard deals with this. */
|
|
5530
|
-
function isSpecialEmpty(node) {
|
|
5531
|
-
return node.is("select") || node.is("textarea");
|
|
5532
|
-
}
|
|
5533
|
-
/**
|
|
5534
|
-
* Checks text content of an element.
|
|
5535
|
-
*
|
|
5536
|
-
* Any text is considered including text from descendant elements. Whitespace is
|
|
5537
|
-
* ignored.
|
|
5538
|
-
*
|
|
5539
|
-
* If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
|
|
5540
|
-
*
|
|
5541
|
-
* @public
|
|
5542
|
-
*/
|
|
5543
|
-
function classifyNodeText(node, options = {}) {
|
|
5544
|
-
const { accessible = false, ignoreHiddenRoot = false } = options;
|
|
5545
|
-
const cacheKey = getCachekey(options);
|
|
5546
|
-
if (node.cacheExists(cacheKey)) {
|
|
5547
|
-
return node.cacheGet(cacheKey);
|
|
5548
|
-
}
|
|
5549
|
-
if (!ignoreHiddenRoot && isHTMLHidden(node)) {
|
|
5550
|
-
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
5551
|
-
}
|
|
5552
|
-
if (!ignoreHiddenRoot && accessible && isAriaHidden(node)) {
|
|
5553
|
-
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
5554
|
-
}
|
|
5555
|
-
if (isSpecialEmpty(node)) {
|
|
5556
|
-
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
5557
|
-
}
|
|
5558
|
-
const text = findTextNodes(node, {
|
|
5559
|
-
...options,
|
|
5560
|
-
ignoreHiddenRoot: false,
|
|
5561
|
-
});
|
|
5562
|
-
/* if any text is dynamic classify as dynamic */
|
|
5563
|
-
if (text.some((cur) => cur.isDynamic)) {
|
|
5564
|
-
return node.cacheSet(cacheKey, TextClassification.DYNAMIC_TEXT);
|
|
5565
|
-
}
|
|
5566
|
-
/* if any text has non-whitespace character classify as static */
|
|
5567
|
-
if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
|
|
5568
|
-
return node.cacheSet(cacheKey, TextClassification.STATIC_TEXT);
|
|
5569
|
-
}
|
|
5570
|
-
/* default to empty */
|
|
5571
|
-
return node.cacheSet(cacheKey, TextClassification.EMPTY_TEXT);
|
|
5572
|
-
}
|
|
5573
|
-
function findTextNodes(node, options) {
|
|
5574
|
-
const { accessible = false } = options;
|
|
5575
|
-
let text = [];
|
|
5576
|
-
for (const child of node.childNodes) {
|
|
5577
|
-
if (isTextNode(child)) {
|
|
5578
|
-
text.push(child);
|
|
5579
|
-
}
|
|
5580
|
-
else if (isElementNode(child)) {
|
|
5581
|
-
if (isHTMLHidden(child, true).bySelf) {
|
|
5582
|
-
continue;
|
|
5583
|
-
}
|
|
5584
|
-
if (accessible && isAriaHidden(child, true).bySelf) {
|
|
5585
|
-
continue;
|
|
5586
|
-
}
|
|
5587
|
-
text = text.concat(findTextNodes(child, options));
|
|
5588
|
-
}
|
|
5589
|
-
}
|
|
5590
|
-
return text;
|
|
5591
|
-
}
|
|
5592
|
-
|
|
5593
|
-
function hasAltText(image) {
|
|
5594
|
-
const alt = image.getAttribute("alt");
|
|
5595
|
-
/* missing or boolean */
|
|
5596
|
-
if (alt === null || alt.value === null) {
|
|
5597
|
-
return false;
|
|
5598
|
-
}
|
|
5599
|
-
return alt.isDynamic || alt.value.toString() !== "";
|
|
5600
|
-
}
|
|
5601
|
-
|
|
5602
|
-
function hasAriaLabel(node) {
|
|
5603
|
-
const label = node.getAttribute("aria-label");
|
|
5604
|
-
/* missing or boolean */
|
|
5605
|
-
if (label === null || label.value === null) {
|
|
5606
|
-
return false;
|
|
5607
|
-
}
|
|
5608
|
-
return label.isDynamic || label.value.toString() !== "";
|
|
5609
|
-
}
|
|
5610
|
-
|
|
5611
|
-
/**
|
|
5612
|
-
* Joins a list of words into natural language.
|
|
5613
|
-
*
|
|
5614
|
-
* - `["foo"]` becomes `"foo"`
|
|
5615
|
-
* - `["foo", "bar"]` becomes `"foo or bar"`
|
|
5616
|
-
* - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
|
|
5617
|
-
* - and so on...
|
|
5618
|
-
*
|
|
5619
|
-
* @internal
|
|
5620
|
-
* @param values - List of words to join
|
|
5621
|
-
* @param conjunction - Conjunction for the last element.
|
|
5622
|
-
* @returns String with the words naturally joined with a conjunction.
|
|
5623
|
-
*/
|
|
5624
|
-
function naturalJoin(values, conjunction = "or") {
|
|
5625
|
-
switch (values.length) {
|
|
5626
|
-
case 0:
|
|
5627
|
-
return "";
|
|
5628
|
-
case 1:
|
|
5629
|
-
return values[0];
|
|
5630
|
-
case 2:
|
|
5631
|
-
return `${values[0]} ${conjunction} ${values[1]}`;
|
|
5632
|
-
default:
|
|
5633
|
-
return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
|
|
5634
|
-
}
|
|
5635
|
-
}
|
|
5636
|
-
|
|
5637
5893
|
function isCategoryOrTag(value) {
|
|
5638
5894
|
return typeof value === "string";
|
|
5639
5895
|
}
|
|
@@ -6200,11 +6456,6 @@ const restricted = new Map([
|
|
|
6200
6456
|
["capture", ["file"]],
|
|
6201
6457
|
["checked", ["checkbox", "radio"]],
|
|
6202
6458
|
["dirname", ["text", "search"]],
|
|
6203
|
-
["formaction", ["submit", "image"]],
|
|
6204
|
-
["formenctype", ["submit", "image"]],
|
|
6205
|
-
["formmethod", ["submit", "image"]],
|
|
6206
|
-
["formnovalidate", ["submit", "image"]],
|
|
6207
|
-
["formtarget", ["submit", "image"]],
|
|
6208
6459
|
["height", ["image"]],
|
|
6209
6460
|
[
|
|
6210
6461
|
"list",
|
|
@@ -10610,6 +10861,7 @@ const bundledRules$1 = {
|
|
|
10610
10861
|
|
|
10611
10862
|
const bundledRules = {
|
|
10612
10863
|
"allowed-links": AllowedLinks,
|
|
10864
|
+
"area-alt": AreaAlt,
|
|
10613
10865
|
"aria-hidden-body": AriaHiddenBody,
|
|
10614
10866
|
"aria-label-misuse": AriaLabelMisuse,
|
|
10615
10867
|
"attr-case": AttrCase,
|
|
@@ -10620,6 +10872,7 @@ const bundledRules = {
|
|
|
10620
10872
|
"attribute-allowed-values": AttributeAllowedValues,
|
|
10621
10873
|
"attribute-boolean-style": AttributeBooleanStyle,
|
|
10622
10874
|
"attribute-empty-style": AttributeEmptyStyle,
|
|
10875
|
+
"attribute-misuse": AttributeMisuse,
|
|
10623
10876
|
"class-pattern": ClassPattern,
|
|
10624
10877
|
"close-attr": CloseAttr,
|
|
10625
10878
|
"close-order": CloseOrder,
|
|
@@ -10686,6 +10939,7 @@ var defaultConfig = {};
|
|
|
10686
10939
|
|
|
10687
10940
|
const config$3 = {
|
|
10688
10941
|
rules: {
|
|
10942
|
+
"area-alt": ["error", { accessible: true }],
|
|
10689
10943
|
"aria-hidden-body": "error",
|
|
10690
10944
|
"aria-label-misuse": "error",
|
|
10691
10945
|
"deprecated-rule": "warn",
|
|
@@ -10721,6 +10975,7 @@ const config$2 = {
|
|
|
10721
10975
|
|
|
10722
10976
|
const config$1 = {
|
|
10723
10977
|
rules: {
|
|
10978
|
+
"area-alt": ["error", { accessible: true }],
|
|
10724
10979
|
"aria-hidden-body": "error",
|
|
10725
10980
|
"aria-label-misuse": "error",
|
|
10726
10981
|
"attr-case": "error",
|
|
@@ -10730,6 +10985,7 @@ const config$1 = {
|
|
|
10730
10985
|
"attribute-allowed-values": "error",
|
|
10731
10986
|
"attribute-boolean-style": "error",
|
|
10732
10987
|
"attribute-empty-style": "error",
|
|
10988
|
+
"attribute-misuse": "error",
|
|
10733
10989
|
"close-attr": "error",
|
|
10734
10990
|
"close-order": "error",
|
|
10735
10991
|
deprecated: "error",
|
|
@@ -10790,8 +11046,10 @@ const config$1 = {
|
|
|
10790
11046
|
|
|
10791
11047
|
const config = {
|
|
10792
11048
|
rules: {
|
|
11049
|
+
"area-alt": ["error", { accessible: false }],
|
|
10793
11050
|
"attr-spacing": "error",
|
|
10794
11051
|
"attribute-allowed-values": "error",
|
|
11052
|
+
"attribute-misuse": "error",
|
|
10795
11053
|
"close-attr": "error",
|
|
10796
11054
|
"close-order": "error",
|
|
10797
11055
|
deprecated: "error",
|
|
@@ -13327,5 +13585,5 @@ function getFormatter(name) {
|
|
|
13327
13585
|
return (_a = availableFormatters[name]) !== null && _a !== void 0 ? _a : null;
|
|
13328
13586
|
}
|
|
13329
13587
|
|
|
13330
|
-
export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g,
|
|
13588
|
+
export { Config as C, DynamicValue as D, EventHandler as E, FileSystemConfigLoader as F, HtmlValidate as H, MetaTable as M, NodeClosed as N, Parser as P, Rule as R, Severity as S, TextNode as T, UserError as U, WrappedError as W, ConfigError as a, ConfigLoader as b, StaticConfigLoader as c, HtmlElement as d, SchemaValidationError as e, NestedError as f, MetaCopyableProperty as g, defineMetadata as h, classifyNodeText as i, TextClassification as j, Reporter as k, TemplateExtractor as l, metadataHelper as m, getFormatter as n, legacyRequire as o, presets as p, ensureError as q, ruleExists as r, configDataFromFile as s, compatibilityCheck as t, codeframe as u, version as v, name as w, bugs as x };
|
|
13331
13589
|
//# sourceMappingURL=core.js.map
|