wikiparser-node 0.0.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.
Files changed (65) hide show
  1. package/.eslintrc.json +229 -0
  2. package/LICENSE +674 -0
  3. package/README.md +1896 -0
  4. package/config/default.json +766 -0
  5. package/config/llwiki.json +686 -0
  6. package/config/moegirl.json +721 -0
  7. package/index.js +159 -0
  8. package/jsconfig.json +7 -0
  9. package/lib/element.js +690 -0
  10. package/lib/node.js +357 -0
  11. package/lib/ranges.js +122 -0
  12. package/lib/title.js +57 -0
  13. package/mixin/attributeParent.js +67 -0
  14. package/mixin/fixedToken.js +32 -0
  15. package/mixin/hidden.js +22 -0
  16. package/package.json +30 -0
  17. package/parser/brackets.js +107 -0
  18. package/parser/commentAndExt.js +61 -0
  19. package/parser/externalLinks.js +30 -0
  20. package/parser/hrAndDoubleUnderscore.js +26 -0
  21. package/parser/html.js +41 -0
  22. package/parser/links.js +92 -0
  23. package/parser/magicLinks.js +40 -0
  24. package/parser/quotes.js +63 -0
  25. package/parser/table.js +97 -0
  26. package/src/arg.js +150 -0
  27. package/src/atom/hidden.js +10 -0
  28. package/src/atom/index.js +33 -0
  29. package/src/attribute.js +342 -0
  30. package/src/extLink.js +116 -0
  31. package/src/heading.js +91 -0
  32. package/src/html.js +144 -0
  33. package/src/imageParameter.js +172 -0
  34. package/src/index.js +602 -0
  35. package/src/link/category.js +88 -0
  36. package/src/link/file.js +201 -0
  37. package/src/link/index.js +214 -0
  38. package/src/listToken.js +47 -0
  39. package/src/magicLink.js +66 -0
  40. package/src/nowiki/comment.js +45 -0
  41. package/src/nowiki/doubleUnderscore.js +42 -0
  42. package/src/nowiki/hr.js +41 -0
  43. package/src/nowiki/index.js +37 -0
  44. package/src/nowiki/noinclude.js +24 -0
  45. package/src/nowiki/quote.js +37 -0
  46. package/src/onlyinclude.js +42 -0
  47. package/src/parameter.js +165 -0
  48. package/src/syntax.js +80 -0
  49. package/src/table/index.js +867 -0
  50. package/src/table/td.js +259 -0
  51. package/src/table/tr.js +244 -0
  52. package/src/tagPair/ext.js +85 -0
  53. package/src/tagPair/include.js +45 -0
  54. package/src/tagPair/index.js +91 -0
  55. package/src/transclude.js +627 -0
  56. package/tool/index.js +898 -0
  57. package/typings/element.d.ts +28 -0
  58. package/typings/index.d.ts +49 -0
  59. package/typings/node.d.ts +23 -0
  60. package/typings/parser.d.ts +9 -0
  61. package/typings/table.d.ts +14 -0
  62. package/typings/token.d.ts +21 -0
  63. package/typings/tool.d.ts +10 -0
  64. package/util/debug.js +70 -0
  65. package/util/string.js +60 -0
package/src/arg.js ADDED
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const {text} = require('../util/string'),
4
+ /** @type {Parser} */ Parser = require('..'),
5
+ Token = require('.');
6
+
7
+ /**
8
+ * `{{{}}}`包裹的参数
9
+ * @classdesc `{childNodes: [AtomToken, Token, ...HiddenToken]}`
10
+ */
11
+ class ArgToken extends Token {
12
+ type = 'arg';
13
+
14
+ /**
15
+ * @param {string[]} parts
16
+ * @param {accum} accum
17
+ * @complexity `n`
18
+ */
19
+ constructor(parts, config = Parser.getConfig(), accum = []) {
20
+ super(undefined, config, true, accum, {AtomToken: 0, Token: 1, HiddenToken: '2:'});
21
+ for (const [i, part] of parts.entries()) {
22
+ if (i === 0 || i > 1) {
23
+ const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden'),
24
+ token = new AtomToken(part, `arg-${i === 0 ? 'name' : 'redundant'}`, config, accum, {
25
+ 'Stage-2': ':', '!HeadingToken': '',
26
+ });
27
+ this.appendChild(token);
28
+ } else {
29
+ const token = new Token(part, config, true, accum);
30
+ token.type = 'arg-default';
31
+ this.appendChild(token.setAttribute('stage', 2));
32
+ }
33
+ }
34
+ this.protectChildren(0);
35
+ }
36
+
37
+ cloneNode() {
38
+ const [name, ...cloned] = this.cloneChildren();
39
+ return Parser.run(() => {
40
+ const token = new ArgToken([''], this.getAttribute('config'));
41
+ token.firstElementChild.safeReplaceWith(name);
42
+ token.append(...cloned);
43
+ return token.afterBuild();
44
+ });
45
+ }
46
+
47
+ afterBuild() {
48
+ this.setAttribute('name', this.firstElementChild.text().trim());
49
+ const that = this,
50
+ /** @type {AstListener} */ argListener = ({prevTarget}) => {
51
+ if (prevTarget === that.firstElementChild) {
52
+ that.setAttribute('name', prevTarget.text().trim());
53
+ }
54
+ };
55
+ this.addEventListener(['remove', 'insert', 'replace', 'text'], argListener);
56
+ return this;
57
+ }
58
+
59
+ toString() {
60
+ return `{{{${super.toString('|')}}}}`;
61
+ }
62
+
63
+ getPadding() {
64
+ return 3;
65
+ }
66
+
67
+ getGaps() {
68
+ return 1;
69
+ }
70
+
71
+ /** @returns {string} */
72
+ text() {
73
+ return `{{{${text(this.children.slice(0, 2), '|')}}}}`;
74
+ }
75
+
76
+ /** @returns {[number, string][]} */
77
+ plain() {
78
+ return this.childElementCount > 1 ? this.children[1].plain() : [];
79
+ }
80
+
81
+ /** @complexity `n` */
82
+ removeRedundant() {
83
+ Parser.run(() => {
84
+ for (let i = this.childElementCount - 1; i > 1; i--) {
85
+ super.removeAt(i);
86
+ }
87
+ });
88
+ }
89
+
90
+ /**
91
+ * 删除`arg-default`子节点时自动删除全部`arg-redundant`子节点
92
+ * @param {number} i
93
+ * @returns {Token}
94
+ */
95
+ removeAt(i) {
96
+ if (i === 1) {
97
+ this.removeRedundant();
98
+ }
99
+ return super.removeAt(i);
100
+ }
101
+
102
+ /** @param {Token} token */
103
+ insertAt(token, i = this.childElementCount) {
104
+ const j = i < 0 ? i + this.childElementCount : i;
105
+ if (j > 1 && !Parser.running) {
106
+ throw new RangeError(`${this.constructor.name} 不可插入 arg-redundant 子节点!`);
107
+ }
108
+ super.insertAt(token, i);
109
+ if (j === 1) {
110
+ token.type = 'arg-default';
111
+ }
112
+ return token;
113
+ }
114
+
115
+ /** @param {string} name */
116
+ setName(name) {
117
+ name = String(name);
118
+ const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
119
+ {childNodes: {length}, firstElementChild} = root;
120
+ if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childElementCount !== 1) {
121
+ throw new SyntaxError(`非法的参数名称:${name.replaceAll('\n', '\\n')}`);
122
+ }
123
+ const newName = firstElementChild.firstElementChild;
124
+ root.destroy();
125
+ firstElementChild.destroy();
126
+ this.firstElementChild.safeReplaceWith(newName);
127
+ }
128
+
129
+ /** @param {string} value */
130
+ setDefault(value) {
131
+ value = String(value);
132
+ const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
133
+ {childNodes: {length}, firstElementChild} = root;
134
+ if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childElementCount !== 2) {
135
+ throw new SyntaxError(`非法的参数预设值:${value.replaceAll('\n', '\\n')}`);
136
+ }
137
+ const [, oldDefault] = this.children,
138
+ newDefault = firstElementChild.lastElementChild;
139
+ root.destroy();
140
+ firstElementChild.destroy();
141
+ if (oldDefault) {
142
+ oldDefault.safeReplaceWith(newDefault);
143
+ } else {
144
+ this.appendChild(newDefault);
145
+ }
146
+ }
147
+ }
148
+
149
+ Parser.classes.ArgToken = __filename;
150
+ module.exports = ArgToken;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const hidden = require('../../mixin/hidden'),
4
+ /** @type {Parser} */ Parser = require('../..'),
5
+ AtomToken = require('.');
6
+
7
+ class HiddenToken extends hidden(AtomToken) {}
8
+
9
+ Parser.classes.HiddenToken = __filename;
10
+ module.exports = HiddenToken;
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('../..'),
4
+ Token = require('..');
5
+
6
+ /**
7
+ * 不会被继续解析的plain Token
8
+ * @classdesc `{childNodes: (string|Token)[]}`
9
+ */
10
+ class AtomToken extends Token {
11
+ /**
12
+ * @param {?string} wikitext
13
+ * @param {accum} accum
14
+ * @param {acceptable} acceptable
15
+ */
16
+ constructor(wikitext, type = 'plain', config = Parser.getConfig(), accum = [], acceptable = null) {
17
+ super(wikitext, config, true, accum, acceptable);
18
+ this.type = type;
19
+ }
20
+
21
+ cloneNode() {
22
+ const cloned = this.cloneChildren(),
23
+ /** @type {typeof AtomToken} */ Constructor = this.constructor,
24
+ config = this.getAttribute('config'),
25
+ acceptable = this.getAttribute('acceptable'),
26
+ token = Parser.run(() => new Constructor(undefined, this.type, config, [], acceptable));
27
+ token.append(...cloned);
28
+ return token;
29
+ }
30
+ }
31
+
32
+ Parser.classes.AtomToken = __filename;
33
+ module.exports = AtomToken;
@@ -0,0 +1,342 @@
1
+ 'use strict';
2
+
3
+ const {typeError, externalUse} = require('../util/debug'),
4
+ {toCase, removeComment} = require('../util/string'),
5
+ /** @type {Parser} */ Parser = require('..'),
6
+ Token = require('.');
7
+
8
+ const stages = {'ext-attr': 0, 'html-attr': 2, 'table-attr': 3};
9
+
10
+ /**
11
+ * 扩展和HTML标签属性
12
+ * @classdesc `{childNodes: [string]|(string|ArgToken|TranscludeToken)[]}`
13
+ */
14
+ class AttributeToken extends Token {
15
+ /** @type {Map<string, string|true>} */ #attr = new Map();
16
+ #sanitized = true;
17
+
18
+ /**
19
+ * 从`this.#attr`更新`childNodes`
20
+ * @complexity `n`
21
+ */
22
+ #updateFromAttr() {
23
+ let equal = '=';
24
+ const ParameterToken = require('./parameter'),
25
+ parent = this.closest('ext, parameter');
26
+ if (parent instanceof ParameterToken && parent.anon
27
+ && parent.parentNode?.matches('template, magic-word#invoke')
28
+ ) {
29
+ equal = '{{=}}';
30
+ }
31
+ const str = [...this.#attr].map(([k, v]) => {
32
+ if (v === true) {
33
+ return k;
34
+ }
35
+ const quote = v.includes('"') ? "'" : '"';
36
+ return `${k}${equal}${quote}${v}${quote}`;
37
+ }).join(' ');
38
+ return str && ` ${str}`;
39
+ }
40
+
41
+ /** @complexity `n` */
42
+ sanitize() {
43
+ if (!Parser.running && !this.#sanitized) {
44
+ Parser.warn(`${this.constructor.name}.sanitize 方法将清理无效属性!`);
45
+ }
46
+ const token = Parser.parse(this.#updateFromAttr(), false, stages[this.type], this.getAttribute('config'));
47
+ Parser.run(() => {
48
+ this.replaceChildren(...token.childNodes, true);
49
+ });
50
+ this.#sanitized = true;
51
+ }
52
+
53
+ /**
54
+ * 从`childNodes`更新`this.#attr`
55
+ * @complexity `n`
56
+ */
57
+ #parseAttr() {
58
+ this.#attr.clear();
59
+ const config = this.getAttribute('config'),
60
+ include = this.getAttribute('include'),
61
+ /** @type {Token & {firstChild: string}} */ token = this.type !== 'ext-attr' && !Parser.running
62
+ ? Parser.run(() => new Token(string, config).parseOnce(0, include).parseOnce())
63
+ : undefined,
64
+ string = removeComment(token?.firstChild ?? this.toString()).replace(/\x00\d+~\x7f/g, '=');
65
+ const build = /** @param {string|boolean} str */ str => {
66
+ return typeof str === 'boolean' || !(token instanceof Token)
67
+ ? str
68
+ : token.buildFromStr(str).map(String).join('');
69
+ };
70
+ for (const [, key,, quoted, unquoted] of string
71
+ .matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(?:\2|$)|(\S*)))?/sg)
72
+ ) {
73
+ if (!this.setAttr(build(key), build(quoted ?? unquoted ?? true), true)) {
74
+ this.#sanitized = false;
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @param {string} attr
81
+ * @param {'ext-attr'|'html-attr'|'table-attr'} type
82
+ * @param {string} name
83
+ * @param {accum} accum
84
+ */
85
+ constructor(attr, type, name, config = Parser.getConfig(), accum = []) {
86
+ super(attr, config, type !== 'ext-attr', accum, {[`Stage-${stages[type]}`]: ':'});
87
+ this.type = type;
88
+ this.setAttribute('name', name).#parseAttr();
89
+ }
90
+
91
+ cloneNode() {
92
+ const cloned = this.cloneChildren();
93
+ return Parser.run(() => {
94
+ const token = new AttributeToken(undefined, this.type, this.name, this.getAttribute('config'));
95
+ token.append(...cloned);
96
+ return token.afterBuild();
97
+ });
98
+ }
99
+
100
+ /**
101
+ * @template {string} T
102
+ * @param {T} key
103
+ * @returns {TokenAttribute<T>}
104
+ */
105
+ getAttribute(key) {
106
+ if (key === 'attr') {
107
+ return new Map(this.#attr);
108
+ }
109
+ return super.getAttribute(key);
110
+ }
111
+
112
+ /** @complexity `n` */
113
+ build() {
114
+ super.build();
115
+ if (this.type === 'ext-attr') {
116
+ return this;
117
+ }
118
+ for (let [key, text] of this.#attr) {
119
+ let built = false;
120
+ if (key.includes('\x00')) {
121
+ this.#attr.delete(key);
122
+ key = this.buildFromStr(key).map(String).join('');
123
+ built = true;
124
+ }
125
+ if (typeof text === 'string' && text.includes('\x00')) {
126
+ text = this.buildFromStr(text).map(String).join('');
127
+ built = true;
128
+ }
129
+ if (built) {
130
+ this.#attr.set(key, text);
131
+ }
132
+ }
133
+ return this;
134
+ }
135
+
136
+ afterBuild() {
137
+ const that = this,
138
+ /** @type {AstListener} */ attributeListener = ({type, target}) => {
139
+ if (type === 'text' || target !== that) {
140
+ that.#parseAttr();
141
+ }
142
+ };
143
+ this.addEventListener(['remove', 'insert', 'replace', 'text'], attributeListener);
144
+ return this;
145
+ }
146
+
147
+ /** @param {string} key */
148
+ hasAttr(key) {
149
+ if (typeof key !== 'string') {
150
+ typeError(this, 'hasAttr', 'String');
151
+ }
152
+ return this.#attr.has(key.toLowerCase().trim());
153
+ }
154
+
155
+ /**
156
+ * @template {string|undefined} T
157
+ * @param {T} key
158
+ * @returns {T extends string ? string|true : Record<string, string|true>}
159
+ */
160
+ getAttr(key) {
161
+ if (key === undefined) {
162
+ return Object.fromEntries(this.#attr);
163
+ } else if (typeof key !== 'string') {
164
+ typeError(this, 'getAttr', 'String');
165
+ }
166
+ return this.#attr.get(key.toLowerCase().trim());
167
+ }
168
+
169
+ getAttrNames() {
170
+ return [...this.#attr.keys()];
171
+ }
172
+
173
+ hasAttrs() {
174
+ return this.getAttrNames().length > 0;
175
+ }
176
+
177
+ /**
178
+ * @param {string} key
179
+ * @param {string|boolean} value
180
+ * @complexity `n`
181
+ */
182
+ setAttr(key, value, init = false) {
183
+ init &&= !externalUse('setAttr');
184
+ if (typeof key !== 'string' || !['string', 'boolean'].includes(typeof value)) {
185
+ typeError(this, 'setValue', 'String', 'Boolean');
186
+ } else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
187
+ throw new RangeError('扩展标签属性不能包含 ">"!');
188
+ }
189
+ key = key.toLowerCase().trim();
190
+ const config = this.getAttribute('config'),
191
+ include = this.getAttribute('include'),
192
+ parsedKey = this.type !== 'ext-attr' && !init
193
+ ? Parser.run(() => new Token(key, config).parseOnce(0, include).parseOnce().firstChild)
194
+ : key;
195
+ if (!/^(?:[\w:]|\x00\d+[t!~{}+-]\x7f)(?:[\w:.-]|\x00\d+[t!~{}+-]\x7f)*$/.test(parsedKey)) {
196
+ if (init) {
197
+ return false;
198
+ }
199
+ throw new RangeError(`无效的属性名:${key}!`);
200
+ } else if (value === false) {
201
+ this.#attr.delete(key);
202
+ } else {
203
+ this.#attr.set(key, value === true ? true : value.replace(/\s/g, ' ').trim());
204
+ }
205
+ if (!init) {
206
+ this.sanitize();
207
+ }
208
+ return true;
209
+ }
210
+
211
+ /**
212
+ * @param {string} key
213
+ * @complexity `n`
214
+ */
215
+ removeAttr(key) {
216
+ if (typeof key !== 'string') {
217
+ typeError(this, 'removeAttr', 'String');
218
+ }
219
+ key = key.toLowerCase().trim();
220
+ if (this.#attr.delete(key)) {
221
+ this.sanitize();
222
+ }
223
+ }
224
+
225
+ /**
226
+ * @param {string} key
227
+ * @param {boolean|undefined} force
228
+ * @complexity `n`
229
+ */
230
+ toggleAttr(key, force) {
231
+ if (typeof key !== 'string') {
232
+ typeError(this, 'toggleAttr', 'String');
233
+ } else if (force !== undefined) {
234
+ force = Boolean(force);
235
+ }
236
+ key = key.toLowerCase().trim();
237
+ const value = this.#attr.has(key) ? this.#attr.get(key) : false;
238
+ if (typeof value !== 'boolean') {
239
+ throw new RangeError(`${key} 属性的值不为 Boolean!`);
240
+ }
241
+ this.setAttr(key, force === true || force === undefined && value === false);
242
+ }
243
+
244
+ toString() {
245
+ const str = super.toString();
246
+ return this.type === 'table-attr' ? str.replaceAll('\n', ' ') : str;
247
+ }
248
+
249
+ text() {
250
+ return this.#updateFromAttr();
251
+ }
252
+
253
+ /** @returns {[number, string][]} */
254
+ plain() {
255
+ return [];
256
+ }
257
+
258
+ /**
259
+ * @param {number} i
260
+ * @complexity `n`
261
+ */
262
+ removeAt(i, done = false) {
263
+ done &&= !externalUse('removeAt');
264
+ done ||= Parser.running;
265
+ const token = super.removeAt(i);
266
+ if (!done) {
267
+ this.#parseAttr();
268
+ }
269
+ return token;
270
+ }
271
+
272
+ /**
273
+ * @template {string|Token} T
274
+ * @param {T} token
275
+ * @complexity `n`
276
+ */
277
+ insertAt(token, i = this.childNodes.length, done = false) {
278
+ done &&= !externalUse('insertAt');
279
+ done ||= Parser.running;
280
+ super.insertAt(token, i);
281
+ if (!done) {
282
+ this.#parseAttr();
283
+ }
284
+ return token;
285
+ }
286
+
287
+ /**
288
+ * @param {...string|Token} elements
289
+ * @complexity `n²`
290
+ */
291
+ replaceChildren(...elements) {
292
+ let done = false;
293
+ if (typeof elements.at(-1) === 'boolean') {
294
+ done = elements.pop();
295
+ }
296
+ done &&= !externalUse('replaceChildren');
297
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
298
+ this.removeAt(i, done);
299
+ }
300
+ for (const element of elements) {
301
+ this.insertAt(element, undefined, done);
302
+ }
303
+ }
304
+
305
+ /**
306
+ * @param {string} key
307
+ * @param {string|undefined} equal - `equal`存在时`val`和`i`也一定存在
308
+ * @param {string|undefined} val
309
+ * @param {string|undefined} i
310
+ */
311
+ matchesAttr(key, equal, val, i) {
312
+ if (externalUse('matchesAttr')) {
313
+ throw new Error(`禁止外部调用 ${this.constructor.name}.matchesAttr 方法!`);
314
+ } else if (!equal) {
315
+ return this.hasAttr(key);
316
+ } else if (!this.hasAttr(key)) {
317
+ return equal === '!=';
318
+ }
319
+ val = toCase(val, i);
320
+ const attr = this.getAttr(key),
321
+ thisVal = toCase(attr === true ? '' : attr, i);
322
+ switch (equal) {
323
+ case '~=':
324
+ return attr !== true && thisVal.split(/\s/).some(v => v === val);
325
+ case '|=': // 允许`val === ''`
326
+ return thisVal === val || thisVal.startsWith(`${val}-`);
327
+ case '^=':
328
+ return attr !== true && thisVal.startsWith(val);
329
+ case '$=':
330
+ return attr !== true && thisVal.endsWith(val);
331
+ case '*=':
332
+ return attr !== true && thisVal.includes(val);
333
+ case '!=':
334
+ return thisVal !== val;
335
+ default: // `=`
336
+ return thisVal === val;
337
+ }
338
+ }
339
+ }
340
+
341
+ Parser.classes.AttributeToken = __filename;
342
+ module.exports = AttributeToken;
package/src/extLink.js ADDED
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ 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
+ /** @this {ExtLinkToken & {firstElementChild: MagicLinkToken}} */
16
+ get protocol() {
17
+ return this.firstElementChild.protocol;
18
+ }
19
+ /**
20
+ * @this {ExtLinkToken & {firstElementChild: MagicLinkToken}}
21
+ * @param {string} value
22
+ */
23
+ set protocol(value) {
24
+ this.firstElementChild.protocol = value;
25
+ }
26
+
27
+ /**
28
+ * @param {string} url
29
+ * @param {string} space
30
+ * @param {string} text
31
+ * @param {accum} accum
32
+ */
33
+ constructor(url, space, text, config = Parser.getConfig(), accum = []) {
34
+ super(undefined, config, true, accum, {AtomToken: 0, Token: 1});
35
+ this.appendChild(new MagicLinkToken(url, true, config, accum));
36
+ this.#space = space;
37
+ if (text) {
38
+ const inner = new Token(text, config, true, accum);
39
+ inner.type = 'ext-link-text';
40
+ this.appendChild(inner.setAttribute('stage', 8));
41
+ }
42
+ this.protectChildren(0);
43
+ }
44
+
45
+ cloneNode() {
46
+ const [url, text] = this.cloneChildren(),
47
+ token = Parser.run(() => new ExtLinkToken(undefined, '', '', this.getAttribute('config')));
48
+ token.firstElementChild.safeReplaceWith(url);
49
+ if (text) {
50
+ token.appendChild(text);
51
+ }
52
+ }
53
+
54
+ toString() {
55
+ return `[${super.toString(this.#space)}]`;
56
+ }
57
+
58
+ getPadding() {
59
+ return 1;
60
+ }
61
+
62
+ getGaps() {
63
+ return this.#space.length;
64
+ }
65
+
66
+ text() {
67
+ return `[${super.text(' ')}]`;
68
+ }
69
+
70
+ /**
71
+ * @returns {[number, string][]}
72
+ * @complexity `n`
73
+ */
74
+ plain() {
75
+ return this.childElementCount === 1 ? [] : this.lastElementChild.plain();
76
+ }
77
+
78
+ /** @this {ExtLinkToken & {firstElementChild: MagicLinkToken}} */
79
+ getUrl() {
80
+ return this.firstElementChild.getUrl();
81
+ }
82
+
83
+ /** @param {string|URL} url */
84
+ setTarget(url) {
85
+ url = String(url);
86
+ const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
87
+ {childNodes: {length}, firstElementChild} = root;
88
+ if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 1) {
89
+ throw new SyntaxError(`非法的外链目标:${url}`);
90
+ }
91
+ const {firstChild} = firstElementChild;
92
+ root.destroy();
93
+ firstElementChild.destroy();
94
+ this.firstElementChild.safeReplaceWith(firstChild);
95
+ }
96
+
97
+ /** @param {string} text */
98
+ setLinkText(text) {
99
+ text = String(text);
100
+ const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
101
+ {childNodes: {length}, firstElementChild} = root;
102
+ if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 2) {
103
+ throw new SyntaxError(`非法的外链文字:${text.replaceAll('\n', '\\n')}`);
104
+ }
105
+ const {lastChild} = firstElementChild;
106
+ if (this.childElementCount === 1) {
107
+ this.appendChild(lastChild);
108
+ } else {
109
+ this.lastElementChild.replaceWith(lastChild);
110
+ }
111
+ this.#space ||= ' ';
112
+ }
113
+ }
114
+
115
+ Parser.classes.ExtLinkToken = __filename;
116
+ module.exports = ExtLinkToken;