wikiparser-node 0.7.1-b → 0.8.0-m

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/config/default.json +832 -0
  2. package/config/llwiki.json +630 -0
  3. package/config/minimum.json +142 -0
  4. package/config/moegirl.json +728 -0
  5. package/config/zhwiki.json +1269 -0
  6. package/index.js +79 -0
  7. package/lib/element.js +137 -0
  8. package/lib/node.js +226 -0
  9. package/lib/text.js +123 -0
  10. package/lib/title.js +60 -0
  11. package/mixin/hidden.js +18 -0
  12. package/package.json +9 -11
  13. package/parser/brackets.js +119 -0
  14. package/parser/commentAndExt.js +61 -0
  15. package/parser/converter.js +45 -0
  16. package/parser/externalLinks.js +32 -0
  17. package/parser/hrAndDoubleUnderscore.js +37 -0
  18. package/parser/html.js +41 -0
  19. package/parser/links.js +93 -0
  20. package/parser/list.js +58 -0
  21. package/parser/magicLinks.js +40 -0
  22. package/parser/quotes.js +63 -0
  23. package/parser/table.js +113 -0
  24. package/src/arg.js +89 -0
  25. package/src/atom/hidden.js +11 -0
  26. package/src/atom/index.js +26 -0
  27. package/src/attribute.js +277 -0
  28. package/src/attributes.js +150 -0
  29. package/src/converter.js +70 -0
  30. package/src/converterFlags.js +97 -0
  31. package/src/converterRule.js +75 -0
  32. package/src/extLink.js +60 -0
  33. package/src/gallery.js +101 -0
  34. package/src/hasNowiki/index.js +32 -0
  35. package/src/hasNowiki/pre.js +28 -0
  36. package/src/heading.js +83 -0
  37. package/src/html.js +133 -0
  38. package/src/imageParameter.js +106 -0
  39. package/src/imagemap.js +140 -0
  40. package/src/imagemapLink.js +29 -0
  41. package/src/index.js +407 -0
  42. package/src/link/category.js +13 -0
  43. package/src/link/file.js +125 -0
  44. package/src/link/galleryImage.js +62 -0
  45. package/src/link/index.js +125 -0
  46. package/src/magicLink.js +68 -0
  47. package/src/nested/choose.js +23 -0
  48. package/src/nested/combobox.js +22 -0
  49. package/src/nested/index.js +69 -0
  50. package/src/nested/references.js +22 -0
  51. package/src/nowiki/comment.js +47 -0
  52. package/src/nowiki/dd.js +13 -0
  53. package/src/nowiki/doubleUnderscore.js +26 -0
  54. package/src/nowiki/hr.js +22 -0
  55. package/src/nowiki/index.js +34 -0
  56. package/src/nowiki/list.js +13 -0
  57. package/src/nowiki/noinclude.js +14 -0
  58. package/src/nowiki/quote.js +55 -0
  59. package/src/onlyinclude.js +39 -0
  60. package/src/paramTag/index.js +66 -0
  61. package/src/paramTag/inputbox.js +32 -0
  62. package/src/parameter.js +97 -0
  63. package/src/syntax.js +23 -0
  64. package/src/table/index.js +46 -0
  65. package/src/table/td.js +119 -0
  66. package/src/table/tr.js +74 -0
  67. package/src/tagPair/ext.js +121 -0
  68. package/src/tagPair/include.js +26 -0
  69. package/src/tagPair/index.js +77 -0
  70. package/src/transclude.js +323 -0
  71. package/util/base.js +17 -0
  72. package/util/diff.js +76 -0
  73. package/util/lint.js +54 -0
  74. package/util/string.js +60 -0
  75. package/bundle/bundle.min.js +0 -40
  76. package/extensions/editor.css +0 -60
  77. package/extensions/editor.js +0 -324
  78. package/extensions/ui.css +0 -119
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const {text} = require('../util/string'),
4
+ Parser = require('..'),
5
+ Token = require('.'),
6
+ ConverterFlagsToken = require('./converterFlags'),
7
+ ConverterRuleToken = require('./converterRule');
8
+
9
+ /**
10
+ * 转换
11
+ * @classdesc `{childNodes: [ConverterFlagsToken, ...ConverterRuleToken]}`
12
+ */
13
+ class ConverterToken extends Token {
14
+ type = 'converter';
15
+
16
+ /**
17
+ * @param {string[]} flags 转换类型标记
18
+ * @param {string[]} rules 转换规则
19
+ * @param {accum} accum
20
+ */
21
+ constructor(flags, rules, config = Parser.getConfig(), accum = []) {
22
+ super(undefined, config, true, accum);
23
+ this.append(new ConverterFlagsToken(flags, config, accum));
24
+ const [firstRule] = rules,
25
+ hasColon = firstRule.includes(':'),
26
+ firstRuleToken = new ConverterRuleToken(firstRule, hasColon, config, accum);
27
+ if (hasColon && firstRuleToken.length === 1) {
28
+ this.insertAt(new ConverterRuleToken(rules.join(';'), false, config, accum));
29
+ } else {
30
+ this.append(
31
+ firstRuleToken,
32
+ ...rules.slice(1).map(rule => new ConverterRuleToken(rule, true, config, accum)),
33
+ );
34
+ }
35
+ }
36
+
37
+ /**
38
+ * @override
39
+ * @returns {string}
40
+ */
41
+ toString(selector) {
42
+ const {childNodes: [flags, ...rules]} = this;
43
+ return `-{${flags.toString()}${flags.length > 0 ? '|' : ''}${rules.map(String).join(';')}}-`;
44
+ }
45
+
46
+ /**
47
+ * @override
48
+ * @returns {string}
49
+ */
50
+ text() {
51
+ const {childNodes: [flags, ...rules]} = this;
52
+ return `-{${flags.text()}|${text(rules, ';')}}-`;
53
+ }
54
+
55
+ /** @override */
56
+ getPadding() {
57
+ return 2;
58
+ }
59
+
60
+ /**
61
+ * @override
62
+ * @param {number} i 子节点位置
63
+ */
64
+ getGaps(i = 0) {
65
+ i = i < 0 ? i + this.length : i;
66
+ return i || this.firstChild.length > 0 ? 1 : 0;
67
+ }
68
+ }
69
+
70
+ module.exports = ConverterToken;
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const {generateForChild} = require('../util/lint'),
4
+ Parser = require('..'),
5
+ Token = require('.'),
6
+ AtomToken = require('./atom');
7
+
8
+ const definedFlags = new Set(['A', 'T', 'R', 'D', '-', 'H', 'N']);
9
+
10
+ /**
11
+ * 转换flags
12
+ * @classdesc `{childNodes: ...AtomToken}`
13
+ */
14
+ class ConverterFlagsToken extends Token {
15
+ type = 'converter-flags';
16
+ /** @type {string[]} */ #flags;
17
+
18
+ /**
19
+ * @param {string[]} flags 转换类型标记
20
+ * @param {accum} accum
21
+ */
22
+ constructor(flags, config = Parser.getConfig(), accum = []) {
23
+ super(undefined, config, true, accum, {
24
+ });
25
+ this.append(...flags.map(flag => new AtomToken(flag, 'converter-flag', config, accum)));
26
+ }
27
+
28
+ /**
29
+ * @override
30
+ * @complexity `n`
31
+ */
32
+ afterBuild() {
33
+ this.#flags = this.childNodes.map(child => child.text().trim());
34
+ }
35
+
36
+ /**
37
+ * @override
38
+ */
39
+ toString(selector) {
40
+ return super.toString(selector, ';');
41
+ }
42
+
43
+ /** @override */
44
+ text() {
45
+ return super.text(';');
46
+ }
47
+
48
+ /** @override */
49
+ getGaps() {
50
+ return 1;
51
+ }
52
+
53
+ /**
54
+ * @override
55
+ * @param {number} start 起始位置
56
+ */
57
+ lint(start = 0) {
58
+ const variantFlags = this.getVariantFlags(),
59
+ unknownFlags = this.getUnknownFlags(),
60
+ validFlags = new Set(this.#flags.filter(flag => definedFlags.has(flag))),
61
+ {length: emptyFlagCount} = this.#flags.filter(flag => !flag),
62
+ knownFlagCount = this.#flags.length - unknownFlags.size - emptyFlagCount,
63
+ errors = super.lint(start);
64
+ if (variantFlags.size === knownFlagCount || validFlags.size === knownFlagCount) {
65
+ return errors;
66
+ }
67
+ const rect = {start, ...this.getRootNode().posFromIndex(start)},
68
+ {childNodes, length} = this;
69
+ for (let i = 0; i < length; i++) {
70
+ const child = childNodes[i],
71
+ flag = child.text().trim();
72
+ if (flag && !variantFlags.has(flag) && !unknownFlags.has(flag)
73
+ && (variantFlags.size > 0 || !validFlags.has(flag))
74
+ ) {
75
+ const error = generateForChild(child, rect, '无效的转换标记');
76
+ errors.push({...error, excerpt: childNodes.slice(0, i + 1).map(String).join(';').slice(-50)});
77
+ }
78
+ }
79
+ return errors;
80
+ }
81
+
82
+ /**
83
+ * 获取未知转换类型标记
84
+ * @complexity `n`
85
+ */
86
+ getUnknownFlags() {
87
+ return new Set(this.#flags.filter(flag => /\{\{[^{}]+\}\}/u.test(flag)));
88
+ }
89
+
90
+ /** 获取指定语言变体的转换标记 */
91
+ getVariantFlags() {
92
+ const variants = new Set(this.getAttribute('config').variants);
93
+ return new Set(this.#flags.filter(flag => variants.has(flag)));
94
+ }
95
+ }
96
+
97
+ module.exports = ConverterFlagsToken;
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ Token = require('.'),
5
+ AtomToken = require('./atom');
6
+
7
+ /**
8
+ * 转换规则
9
+ * @classdesc `{childNodes: ...AtomToken)}`
10
+ */
11
+ class ConverterRuleToken extends Token {
12
+ type = 'converter-rule';
13
+
14
+ /**
15
+ * @param {string} rule 转换规则
16
+ * @param {boolean} hasColon 是否带有":"
17
+ * @param {accum} accum
18
+ */
19
+ constructor(rule, hasColon = true, config = Parser.getConfig(), accum = []) {
20
+ super(undefined, config, true, accum);
21
+ if (hasColon) {
22
+ const i = rule.indexOf(':'),
23
+ j = rule.slice(0, i).indexOf('=>'),
24
+ v = j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i),
25
+ {variants} = config;
26
+ if (variants.includes(v.trim())) {
27
+ super.insertAt(new AtomToken(v, 'converter-rule-variant', config, accum));
28
+ super.insertAt(new AtomToken(rule.slice(i + 1), 'converter-rule-to', config, accum));
29
+ if (j !== -1) {
30
+ super.insertAt(new AtomToken(rule.slice(0, j), 'converter-rule-from', config, accum), 0);
31
+ }
32
+ } else {
33
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
34
+ }
35
+ } else {
36
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
37
+ }
38
+ }
39
+
40
+ /**
41
+ * @override
42
+ * @returns {string}
43
+ */
44
+ toString(selector) {
45
+ if (this.length === 3) {
46
+ const {childNodes: [from, variant, to]} = this;
47
+ return `${from.toString(selector)}=>${variant.toString(selector)}:${to.toString(selector)}`;
48
+ }
49
+ return super.toString(selector, ':');
50
+ }
51
+
52
+ /**
53
+ * @override
54
+ * @returns {string}
55
+ */
56
+ text() {
57
+ if (this.length === 3) {
58
+ const {childNodes: [from, variant, to]} = this;
59
+ return `${from.text()}=>${variant.text()}:${to.text()}`;
60
+ }
61
+ return super.text(':');
62
+ }
63
+
64
+ /**
65
+ * @override
66
+ * @param {number} i 子节点序号
67
+ */
68
+ getGaps(i = 0) {
69
+ const {length} = this;
70
+ i = i < 0 ? i + length : i;
71
+ return i === 0 && length === 3 ? 2 : 1;
72
+ }
73
+ }
74
+
75
+ module.exports = ConverterRuleToken;
package/src/extLink.js ADDED
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ Token = require('.'),
5
+ MagicLinkToken = require('./magicLink');
6
+
7
+ /**
8
+ * 外链
9
+ * @classdesc `{childNodes: [MagicLinkToken, ?Token]}`
10
+ */
11
+ class ExtLinkToken extends Token {
12
+ type = 'ext-link';
13
+ #space;
14
+
15
+ /**
16
+ * @param {string} url 网址
17
+ * @param {string} space 空白字符
18
+ * @param {string} text 链接文字
19
+ * @param {accum} accum
20
+ */
21
+ constructor(url, space, text, config = Parser.getConfig(), accum = []) {
22
+ super(undefined, config, true, accum, {
23
+ });
24
+ this.insertAt(new MagicLinkToken(url, true, config, accum));
25
+ this.#space = space;
26
+ if (text) {
27
+ const inner = new Token(text, config, true, accum, {
28
+ });
29
+ inner.type = 'ext-link-text';
30
+ this.insertAt(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @override
36
+ */
37
+ toString(selector) {
38
+ if (this.length === 1) {
39
+ return `[${super.toString(selector)}${this.#space}]`;
40
+ }
41
+ return `[${super.toString(selector, this.#space)}]`;
42
+ }
43
+
44
+ /** @override */
45
+ text() {
46
+ return `[${super.text(' ')}]`;
47
+ }
48
+
49
+ /** @override */
50
+ getPadding() {
51
+ return 1;
52
+ }
53
+
54
+ /** @override */
55
+ getGaps() {
56
+ return this.#space.length;
57
+ }
58
+ }
59
+
60
+ module.exports = ExtLinkToken;
package/src/gallery.js ADDED
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ Token = require('.'),
5
+ GalleryImageToken = require('./link/galleryImage'),
6
+ HiddenToken = require('./atom/hidden');
7
+
8
+ /**
9
+ * gallery标签
10
+ * @classdesc `{childNodes: ...(GalleryImageToken|HiddenToken|AstText)}`
11
+ */
12
+ class GalleryToken extends Token {
13
+ type = 'ext-inner';
14
+ name = 'gallery';
15
+
16
+ /**
17
+ * @param {string} inner 标签内部wikitext
18
+ * @param {accum} accum
19
+ */
20
+ constructor(inner, config = Parser.getConfig(), accum = []) {
21
+ super(undefined, config, true, accum, {
22
+ });
23
+ const /** @type {ParserConfig} */ newConfig = {...config, img: {...config.img}};
24
+ for (const [k, v] of Object.entries(config.img)) {
25
+ if (v === 'width') {
26
+ delete newConfig.img[k];
27
+ }
28
+ }
29
+ for (const line of inner?.split('\n') ?? []) {
30
+ const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
31
+ if (!matches) {
32
+ super.insertAt(line.trim()
33
+ ? new HiddenToken(line, undefined, newConfig, [], {
34
+ })
35
+ : line);
36
+ continue;
37
+ }
38
+ const [, file, alt] = matches,
39
+ title = this.normalizeTitle(file, 6, true, true);
40
+ if (title.valid) {
41
+ super.insertAt(new GalleryImageToken(file, alt, title, newConfig, accum));
42
+ } else {
43
+ super.insertAt(new HiddenToken(line, undefined, newConfig, [], {
44
+ }));
45
+ }
46
+ }
47
+ }
48
+
49
+ /**
50
+ * @override
51
+ */
52
+ toString(selector) {
53
+ return super.toString(selector, '\n');
54
+ }
55
+
56
+ /** @override */
57
+ text() {
58
+ return super.text('\n').replace(/\n\s*\n/gu, '\n');
59
+ }
60
+
61
+ /** @override */
62
+ getGaps() {
63
+ return 1;
64
+ }
65
+
66
+ /**
67
+ * @override
68
+ * @param {number} start 起始位置
69
+ */
70
+ lint(start = 0) {
71
+ const {top, left} = this.getRootNode().posFromIndex(start),
72
+ /** @type {LintError[]} */ errors = [];
73
+ for (let i = 0, startIndex = start; i < this.length; i++) {
74
+ const child = this.childNodes[i],
75
+ str = String(child),
76
+ {length} = str,
77
+ trimmed = str.trim(),
78
+ startLine = top + i,
79
+ startCol = i ? 0 : left;
80
+ if (child.type === 'hidden' && trimmed && !/^<!--.*-->$/u.test(trimmed)) {
81
+ errors.push({
82
+ message: '图库中的无效内容',
83
+ severity: 'error',
84
+ startIndex,
85
+ endIndex: startIndex + length,
86
+ startLine,
87
+ endLine: startLine,
88
+ startCol,
89
+ endCol: startCol + length,
90
+ excerpt: String(child).slice(0, 50),
91
+ });
92
+ } else if (child.type !== 'hidden' && child.type !== 'text') {
93
+ errors.push(...child.lint(startIndex));
94
+ }
95
+ startIndex += length + 1;
96
+ }
97
+ return errors;
98
+ }
99
+ }
100
+
101
+ module.exports = GalleryToken;
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ Token = require('..'),
5
+ NoincludeToken = require('../nowiki/noinclude');
6
+
7
+ /**
8
+ * `<pre>`
9
+ * @classdesc `{childNodes: [...AstText|NoincludeToken]}`
10
+ */
11
+ class HasNowikiToken extends Token {
12
+ /**
13
+ * @param {string} wikitext wikitext
14
+ * @param {string} type type
15
+ * @param {accum} accum
16
+ */
17
+ constructor(wikitext, type, config = Parser.getConfig(), accum = []) {
18
+ wikitext = wikitext.replace(
19
+ /(<nowiki>)(.*?)(<\/nowiki>)/giu,
20
+ /** @type {function(...string): string} */ (_, opening, inner, closing) => {
21
+ new NoincludeToken(opening, config, accum);
22
+ new NoincludeToken(closing, config, accum);
23
+ return `\0${accum.length - 1}c\x7F${inner}\0${accum.length}c\x7F`;
24
+ },
25
+ );
26
+ super(wikitext, config, true, accum, {
27
+ });
28
+ this.type = type;
29
+ }
30
+ }
31
+
32
+ module.exports = HasNowikiToken;
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('../..'),
4
+ HasNowikiToken = require('.');
5
+
6
+ /**
7
+ * `<pre>`
8
+ * @classdesc `{childNodes: [...AstText|NoincludeToken|ConverterToken]}`
9
+ */
10
+ class PreToken extends HasNowikiToken {
11
+ name = 'pre';
12
+
13
+ /**
14
+ * @param {string} wikitext wikitext
15
+ * @param {accum} accum
16
+ */
17
+ constructor(wikitext, config = Parser.getConfig(), accum = []) {
18
+ super(wikitext, 'ext-inner', config, accum);
19
+ this.setAttribute('stage', Parser.MAX_STAGE - 1);
20
+ }
21
+
22
+ /** @override */
23
+ isPlain() {
24
+ return true;
25
+ }
26
+ }
27
+
28
+ module.exports = PreToken;
package/src/heading.js ADDED
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const {generateForSelf} = require('../util/lint'),
4
+ Parser = require('..'),
5
+ Token = require('.'),
6
+ SyntaxToken = require('./syntax');
7
+
8
+ /**
9
+ * 章节标题
10
+ * @classdesc `{childNodes: [Token, SyntaxToken]}`
11
+ */
12
+ class HeadingToken extends Token {
13
+ type = 'heading';
14
+
15
+ /**
16
+ * @param {number} level 标题层级
17
+ * @param {string[]} input 标题文字
18
+ * @param {accum} accum
19
+ */
20
+ constructor(level, input, config = Parser.getConfig(), accum = []) {
21
+ super(undefined, config, true, accum);
22
+ this.setAttribute('name', String(level));
23
+ const token = new Token(input[0], config, true, accum);
24
+ token.type = 'heading-title';
25
+ token.setAttribute('stage', 2);
26
+ const trail = new SyntaxToken(input[1], /^[^\S\n]*$/u, 'heading-trail', config, accum, {
27
+ });
28
+ this.append(token, trail);
29
+ }
30
+
31
+ /**
32
+ * @override
33
+ * @returns {string}
34
+ */
35
+ toString(selector) {
36
+ const equals = '='.repeat(Number(this.name));
37
+ return `${equals}${this.firstChild.toString()}${equals}${this.lastChild.toString()}`;
38
+ }
39
+
40
+ /**
41
+ * @override
42
+ * @returns {string}
43
+ */
44
+ text() {
45
+ const equals = '='.repeat(Number(this.name));
46
+ return `${equals}${this.firstChild.text()}${equals}`;
47
+ }
48
+
49
+ /** @override */
50
+ getPadding() {
51
+ return super.getPadding() + Number(this.name);
52
+ }
53
+
54
+ /** @override */
55
+ getGaps() {
56
+ return Number(this.name);
57
+ }
58
+
59
+ /**
60
+ * @override
61
+ * @param {number} start 起始位置
62
+ */
63
+ lint(start = 0) {
64
+ const errors = super.lint(start),
65
+ innerText = String(this.firstChild);
66
+ let refError;
67
+ if (this.name === '1') {
68
+ refError = generateForSelf(this, {start}, '<h1>');
69
+ errors.push(refError);
70
+ }
71
+ if (innerText[0] === '=' || innerText.endsWith('=')) {
72
+ refError ||= generateForSelf(this, {start}, '');
73
+ errors.push({...refError, message: '段落标题中不平衡的"="'});
74
+ }
75
+ if (this.closest('html-attrs, table-attrs')) {
76
+ refError ||= generateForSelf(this, {start}, '');
77
+ errors.push({...refError, message: 'HTML标签属性中的段落标题'});
78
+ }
79
+ return errors;
80
+ }
81
+ }
82
+
83
+ module.exports = HeadingToken;
package/src/html.js ADDED
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ const {generateForSelf} = require('../util/lint'),
4
+ Parser = require('..'),
5
+ Token = require('.');
6
+
7
+ /**
8
+ * HTML标签
9
+ * @classdesc `{childNodes: [AttributesToken]}`
10
+ */
11
+ class HtmlToken extends Token {
12
+ type = 'html';
13
+ #closing;
14
+ #selfClosing;
15
+ #tag;
16
+
17
+ /** getter */
18
+ get closing() {
19
+ return this.#closing;
20
+ }
21
+
22
+ /**
23
+ * @param {string} name 标签名
24
+ * @param {AttributesToken} attr 标签属性
25
+ * @param {boolean} closing 是否闭合
26
+ * @param {boolean} selfClosing 是否自封闭
27
+ * @param {accum} accum
28
+ */
29
+ constructor(name, attr, closing, selfClosing, config = Parser.getConfig(), accum = []) {
30
+ super(undefined, config, true, accum);
31
+ this.insertAt(attr);
32
+ this.setAttribute('name', name.toLowerCase());
33
+ this.#closing = closing;
34
+ this.#selfClosing = selfClosing;
35
+ this.#tag = name;
36
+ }
37
+
38
+ /**
39
+ * @override
40
+ */
41
+ toString(selector) {
42
+ return `<${this.#closing ? '/' : ''}${this.#tag}${super.toString()}${this.#selfClosing ? '/' : ''}>`;
43
+ }
44
+
45
+ /** @override */
46
+ text() {
47
+ return `<${this.#closing ? '/' : ''}${this.#tag}${
48
+ this.#closing ? '' : super.text()
49
+ }${this.#selfClosing ? '/' : ''}>`;
50
+ }
51
+
52
+ /** @override */
53
+ getPadding() {
54
+ return this.#tag.length + (this.#closing ? 2 : 1);
55
+ }
56
+
57
+ /**
58
+ * @override
59
+ * @param {number} start 起始位置
60
+ */
61
+ lint(start = 0) {
62
+ const errors = super.lint(start);
63
+ let wikitext, /** @type {LintError} */ refError;
64
+ if (this.name === 'h1' && !this.#closing) {
65
+ wikitext = String(this.getRootNode());
66
+ refError = generateForSelf(this, {start}, '<h1>');
67
+ errors.push({...refError, excerpt: wikitext.slice(start, start + 50)});
68
+ }
69
+ if (this.closest('table-attrs')) {
70
+ wikitext ||= String(this.getRootNode());
71
+ refError ||= generateForSelf(this, {start}, '');
72
+ const excerpt = wikitext.slice(Math.max(0, start - 25), start + 25);
73
+ errors.push({...refError, message: '表格属性中的HTML标签', excerpt});
74
+ }
75
+ try {
76
+ this.findMatchingTag();
77
+ } catch ({message: errorMsg}) {
78
+ wikitext ||= String(this.getRootNode());
79
+ refError ||= generateForSelf(this, {start}, '');
80
+ const [message] = errorMsg.split(':'),
81
+ error = {...refError, message, severity: message === '未闭合的标签' ? 'warning' : 'error'};
82
+ if (message === '未闭合的标签') {
83
+ error.excerpt = wikitext.slice(start, start + 50);
84
+ } else if (message === '未匹配的闭合标签') {
85
+ const end = start + String(this).length;
86
+ error.excerpt = wikitext.slice(Math.max(0, end - 50), end);
87
+ }
88
+ errors.push(error);
89
+ }
90
+ return errors;
91
+ }
92
+
93
+ /**
94
+ * 搜索匹配的标签
95
+ * @complexity `n`
96
+ * @throws `SyntaxError` 同时闭合和自封闭的标签
97
+ * @throws `SyntaxError` 无效自封闭标签
98
+ * @throws `SyntaxError` 未闭合的标签
99
+ */
100
+ findMatchingTag() {
101
+ const {html} = this.getAttribute('config'),
102
+ {name: tagName, parentNode} = this,
103
+ string = String(this);
104
+ if (this.#closing && (this.#selfClosing || html[2].includes(tagName))) {
105
+ throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
106
+ } else if (html[2].includes(tagName) || this.#selfClosing && html[1].includes(tagName)) { // 自封闭标签
107
+ return this;
108
+ } else if (this.#selfClosing && html[0].includes(tagName)) {
109
+ throw new SyntaxError(`无效自封闭标签:${string}`);
110
+ } else if (!parentNode) {
111
+ return undefined;
112
+ }
113
+ const {childNodes} = parentNode,
114
+ i = childNodes.indexOf(this),
115
+ siblings = this.#closing
116
+ ? childNodes.slice(0, i).reverse().filter(({type, name}) => type === 'html' && name === tagName)
117
+ : childNodes.slice(i + 1).filter(({type, name}) => type === 'html' && name === tagName);
118
+ let imbalance = this.#closing ? -1 : 1;
119
+ for (const token of siblings) {
120
+ if (token.closing) {
121
+ imbalance--;
122
+ } else {
123
+ imbalance++;
124
+ }
125
+ if (imbalance === 0) {
126
+ return token;
127
+ }
128
+ }
129
+ throw new SyntaxError(`未${this.#closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
130
+ }
131
+ }
132
+
133
+ module.exports = HtmlToken;