wikiparser-node 0.3.1 → 0.5.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 (80) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +13 -17
  3. package/config/llwiki.json +11 -79
  4. package/config/moegirl.json +7 -1
  5. package/config/zhwiki.json +1269 -0
  6. package/index.js +130 -97
  7. package/lib/element.js +410 -518
  8. package/lib/node.js +493 -115
  9. package/lib/ranges.js +27 -19
  10. package/lib/text.js +175 -0
  11. package/lib/title.js +14 -6
  12. package/mixin/attributeParent.js +70 -24
  13. package/mixin/fixedToken.js +18 -10
  14. package/mixin/hidden.js +6 -4
  15. package/mixin/sol.js +39 -12
  16. package/package.json +17 -4
  17. package/parser/brackets.js +18 -18
  18. package/parser/commentAndExt.js +16 -14
  19. package/parser/converter.js +14 -13
  20. package/parser/externalLinks.js +12 -11
  21. package/parser/hrAndDoubleUnderscore.js +24 -14
  22. package/parser/html.js +8 -7
  23. package/parser/links.js +13 -13
  24. package/parser/list.js +12 -11
  25. package/parser/magicLinks.js +11 -10
  26. package/parser/quotes.js +6 -5
  27. package/parser/selector.js +175 -0
  28. package/parser/table.js +31 -24
  29. package/src/arg.js +91 -43
  30. package/src/atom/hidden.js +5 -2
  31. package/src/atom/index.js +17 -9
  32. package/src/attribute.js +210 -101
  33. package/src/converter.js +78 -43
  34. package/src/converterFlags.js +104 -45
  35. package/src/converterRule.js +136 -78
  36. package/src/extLink.js +81 -27
  37. package/src/gallery.js +63 -20
  38. package/src/heading.js +58 -20
  39. package/src/html.js +138 -48
  40. package/src/imageParameter.js +93 -58
  41. package/src/index.js +314 -186
  42. package/src/link/category.js +22 -54
  43. package/src/link/file.js +83 -32
  44. package/src/link/galleryImage.js +21 -7
  45. package/src/link/index.js +170 -81
  46. package/src/magicLink.js +64 -14
  47. package/src/nowiki/comment.js +36 -10
  48. package/src/nowiki/dd.js +37 -22
  49. package/src/nowiki/doubleUnderscore.js +21 -7
  50. package/src/nowiki/hr.js +11 -7
  51. package/src/nowiki/index.js +16 -9
  52. package/src/nowiki/list.js +2 -2
  53. package/src/nowiki/noinclude.js +8 -4
  54. package/src/nowiki/quote.js +38 -7
  55. package/src/onlyinclude.js +24 -7
  56. package/src/parameter.js +102 -62
  57. package/src/syntax.js +23 -20
  58. package/src/table/index.js +282 -174
  59. package/src/table/td.js +112 -61
  60. package/src/table/tr.js +135 -74
  61. package/src/tagPair/ext.js +30 -23
  62. package/src/tagPair/include.js +26 -11
  63. package/src/tagPair/index.js +72 -29
  64. package/src/transclude.js +235 -127
  65. package/tool/index.js +42 -32
  66. package/util/debug.js +21 -18
  67. package/util/diff.js +76 -0
  68. package/util/lint.js +40 -0
  69. package/util/string.js +56 -26
  70. package/.eslintrc.json +0 -319
  71. package/errors/README +0 -1
  72. package/jsconfig.json +0 -7
  73. package/printed/README +0 -1
  74. package/typings/element.d.ts +0 -28
  75. package/typings/index.d.ts +0 -52
  76. package/typings/node.d.ts +0 -23
  77. package/typings/parser.d.ts +0 -9
  78. package/typings/table.d.ts +0 -14
  79. package/typings/token.d.ts +0 -22
  80. package/typings/tool.d.ts +0 -10
package/src/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  /*
4
4
  * PHP解析器的步骤:
5
5
  * -1. 替换签名和`{{subst:}}`,参见Parser::preSaveTransform;这在revision中不可能保留,可以跳过
6
- * 0. 移除特定字符`\0`和`\x7f`,参见Parser::parse
6
+ * 0. 移除特定字符`\0`和`\x7F`,参见Parser::parse
7
7
  * 1. 注释/扩展标签('<'相关),参见Preprocessor_Hash::buildDomTreeArrayFromText和Sanitizer::decodeTagAttributes
8
8
  * 2. 模板/模板变量/标题,注意rightmost法则,以及`-{`和`[[`可以破坏`{{`或`{{{`语法,
9
9
  * 参见Preprocessor_Hash::buildDomTreeArrayFromText
@@ -19,7 +19,7 @@
19
19
  */
20
20
 
21
21
  /*
22
- * \0\d+.\x7f标记Token:
22
+ * \0\d+.\x7F标记Token:
23
23
  * e: ExtToken
24
24
  * c: CommentToken、NoIncludeToken和IncludeToken
25
25
  * !: `{{!}}`专用
@@ -42,31 +42,132 @@
42
42
  */
43
43
 
44
44
  const {externalUse} = require('../util/debug'),
45
+ {text} = require('../util/string'),
45
46
  Ranges = require('../lib/ranges'),
46
- AstElement = require('../lib/element'),
47
47
  assert = require('assert/strict'),
48
- /** @type {Parser} */ Parser = require('..'),
49
- {MAX_STAGE} = Parser;
48
+ Parser = require('..'),
49
+ AstElement = require('../lib/element'),
50
+ AstText = require('../lib/text');
51
+ const {MAX_STAGE, aliases} = Parser;
50
52
 
53
+ /**
54
+ * 所有节点的基类
55
+ * @classdesc `{childNodes: ...(AstText|Token)}`
56
+ */
51
57
  class Token extends AstElement {
52
58
  type = 'root';
53
- /** 解析阶段,参见顶部注释。只对plain Token有意义。 */ #stage = 0;
59
+ #stage = 0; // 解析阶段,参见顶部注释。只对plain Token有意义。
54
60
  #config;
55
- /** 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7f/`标记;2. 数组中的Token会依次执行parseOnce和build方法。 */
61
+ // 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7F/`标记;2. 数组中的Token会依次执行parseOnce和build方法。
56
62
  #accum;
57
63
  /** @type {Record<string, Ranges>} */ #acceptable;
58
64
  #protectedChildren = new Ranges();
59
65
  /** @type {boolean} */ #include;
60
66
 
61
67
  /**
62
- * @param {?string} wikitext
68
+ * 保护部分子节点不被移除
69
+ * @param {...string|number|Range} args 子节点范围
70
+ */
71
+ #protectChildren = (...args) => {
72
+ this.#protectedChildren.push(...new Ranges(args));
73
+ };
74
+
75
+ /**
76
+ * 将维基语法替换为占位符
77
+ * @param {number} n 解析阶段
78
+ * @param {boolean} include 是否嵌入
79
+ */
80
+ #parseOnce = (n = this.#stage, include = false) => {
81
+ if (n < this.#stage || !this.isPlain() || this.childNodes.length === 0) {
82
+ return this;
83
+ }
84
+ switch (n) {
85
+ case 0:
86
+ if (this.type === 'root') {
87
+ this.#accum.shift();
88
+ }
89
+ this.#parseCommentAndExt(include);
90
+ break;
91
+ case 1:
92
+ this.#parseBrackets();
93
+ break;
94
+ case 2:
95
+ this.#parseHtml();
96
+ break;
97
+ case 3:
98
+ this.#parseTable();
99
+ break;
100
+ case 4:
101
+ this.#parseHrAndDoubleUndescore();
102
+ break;
103
+ case 5:
104
+ this.#parseLinks();
105
+ break;
106
+ case 6:
107
+ this.#parseQuotes();
108
+ break;
109
+
110
+ case 7:
111
+ this.#parseExternalLinks();
112
+ break;
113
+ case 8:
114
+ this.#parseMagicLinks();
115
+ break;
116
+ case 9:
117
+ this.#parseList();
118
+ break;
119
+ case 10:
120
+ this.#parseConverter();
121
+ // no default
122
+ }
123
+ if (this.type === 'root') {
124
+ for (const token of this.#accum) {
125
+ token.getAttribute('parseOnce')(n, include);
126
+ }
127
+ }
128
+ this.#stage++;
129
+ return this;
130
+ };
131
+
132
+ /**
133
+ * 重建wikitext
134
+ * @param {string} str 半解析的字符串
135
+ * @complexity `n`
136
+ * @returns {(Token|AstText)[]}
137
+ */
138
+ #buildFromStr = str => str.split(/[\0\x7F]/u).map((s, i) => {
139
+ if (i % 2 === 0) {
140
+ return new AstText(s);
141
+ } else if (isNaN(s.at(-1))) {
142
+ return this.#accum[Number(s.slice(0, -1))];
143
+ }
144
+ throw new Error(`解析错误!未正确标记的 Token:${s}`);
145
+ });
146
+
147
+ /** 所有图片,包括图库 */
148
+ get images() {
149
+ return this.querySelectorAll('file, gallery-image');
150
+ }
151
+
152
+ /** 所有内链、外链和自由外链 */
153
+ get links() {
154
+ return this.querySelectorAll('link, ext-link, free-ext-link');
155
+ }
156
+
157
+ /** 所有模板和模块 */
158
+ get embeds() {
159
+ return this.querySelectorAll('template, magic-word#invoke');
160
+ }
161
+
162
+ /**
163
+ * @param {string} wikitext wikitext
63
164
  * @param {accum} accum
64
- * @param {acceptable} acceptable
165
+ * @param {acceptable} acceptable 可接受的子节点设置
65
166
  */
66
167
  constructor(wikitext, config = Parser.getConfig(), halfParsed = false, accum = [], acceptable = null) {
67
168
  super();
68
169
  if (typeof wikitext === 'string') {
69
- this.appendChild(halfParsed ? wikitext : wikitext.replace(/[\0\x7f]/g, ''));
170
+ this.appendChild(halfParsed ? wikitext : wikitext.replaceAll(/[\0\x7F]/gu, ''));
70
171
  }
71
172
  this.#config = config;
72
173
  this.#accum = accum;
@@ -74,32 +175,38 @@ class Token extends AstElement {
74
175
  accum.push(this);
75
176
  }
76
177
 
77
- /** @complexity `n` */
78
- cloneChildren() {
79
- if (!Parser.debugging && externalUse('cloneChildren')) {
80
- this.debugOnly('cloneChildren');
81
- }
82
- return this.childNodes.map(child => typeof child === 'string' ? child : child.cloneNode());
178
+ /**
179
+ * 深拷贝所有子节点
180
+ * @complexity `n`
181
+ * @returns {(AstText|Token)[]}
182
+ */
183
+ cloneChildNodes() {
184
+ return this.childNodes.map(child => child.cloneNode());
83
185
  }
84
186
 
85
- /** @complexity `n` */
187
+ /**
188
+ * 深拷贝节点
189
+ * @complexity `n`
190
+ * @throws `Error` 未定义复制方法
191
+ */
86
192
  cloneNode() {
87
193
  if (!this.isPlain()) {
88
194
  throw new Error(`未定义 ${this.constructor.name} 的复制方法!`);
89
195
  }
90
- const cloned = this.cloneChildren();
196
+ const cloned = this.cloneChildNodes();
91
197
  return Parser.run(() => {
92
198
  const token = new Token(undefined, this.#config, false, [], this.#acceptable);
93
199
  token.type = this.type;
94
200
  token.append(...cloned);
95
- token.protectChildren(...this.#protectedChildren);
201
+ token.getAttribute('protectChildren')(...this.#protectedChildren);
96
202
  return token;
97
203
  });
98
204
  }
99
205
 
100
206
  /**
207
+ * @override
101
208
  * @template {string} T
102
- * @param {T} key
209
+ * @param {T} key 属性键
103
210
  * @returns {TokenAttribute<T>}
104
211
  */
105
212
  getAttribute(key) {
@@ -112,18 +219,28 @@ class Token extends AstElement {
112
219
  return this.#accum;
113
220
  case 'acceptable':
114
221
  return this.#acceptable ? {...this.#acceptable} : null;
222
+ case 'protectChildren':
223
+ return this.#protectChildren;
115
224
  case 'protectedChildren':
116
225
  return new Ranges(this.#protectedChildren);
226
+ case 'parseOnce':
227
+ return this.#parseOnce;
228
+ case 'buildFromStr':
229
+ return this.#buildFromStr;
117
230
  case 'include': {
118
231
  if (this.#include !== undefined) {
119
232
  return this.#include;
120
233
  }
121
- const includeToken = this.querySelector('include');
234
+ const root = this.getRootNode();
235
+ if (root.type === 'root' && root !== this) {
236
+ return root.getAttribute('include');
237
+ }
238
+ const includeToken = root.querySelector('include');
122
239
  if (includeToken) {
123
240
  return includeToken.name === 'noinclude';
124
241
  }
125
- const noincludeToken = this.querySelector('noinclude');
126
- return Boolean(noincludeToken) && !/^<\/?noinclude(?:\s[^>]*)?\/?>$/i.test(noincludeToken.toString());
242
+ const noincludeToken = root.querySelector('noinclude');
243
+ return Boolean(noincludeToken) && !/^<\/?noinclude(?:\s[^>]*)?\/?>$/iu.test(String(noincludeToken));
127
244
  }
128
245
  default:
129
246
  return super.getAttribute(key);
@@ -131,14 +248,16 @@ class Token extends AstElement {
131
248
  }
132
249
 
133
250
  /**
251
+ * @override
134
252
  * @template {string} T
135
- * @param {T} key
136
- * @param {TokenAttribute<T>} value
253
+ * @param {T} key 属性键
254
+ * @param {TokenAttribute<T>} value 属性值
255
+ * @throws `RangeError` 禁止手动指定私有属性
137
256
  */
138
257
  setAttribute(key, value) {
139
- if (key === 'include' || !Parser.running && ['config', 'accum'].includes(key)) {
258
+ if (key === 'include' || !Parser.running && (key === 'config' || key === 'accum')) {
140
259
  throw new RangeError(`禁止手动指定私有的 #${key} 属性!`);
141
- } else if (!Parser.debugging && ['stage', 'acceptable', 'protectedChildren'].includes(key)
260
+ } else if (!Parser.debugging && (key === 'stage' || key === 'acceptable' || key === 'protectedChildren')
142
261
  && externalUse('setAttribute')
143
262
  ) {
144
263
  throw new RangeError(`使用 ${this.constructor.name}.setAttribute 方法设置私有属性 #${key} 仅用于代码调试!`);
@@ -165,11 +284,11 @@ class Token extends AstElement {
165
284
  for (const [k, v] of Object.entries(value)) {
166
285
  if (k.startsWith('Stage-')) {
167
286
  for (let i = 0; i <= Number(k.slice(6)); i++) {
168
- for (const type of Parser.aliases[i]) {
287
+ for (const type of aliases[i]) {
169
288
  acceptable[type] = new Ranges(v);
170
289
  }
171
290
  }
172
- } else if (k.startsWith('!')) { // `!`项必须放在最后
291
+ } else if (k[0] === '!') { // `!`项必须放在最后
173
292
  delete acceptable[k.slice(1)];
174
293
  } else {
175
294
  acceptable[k] = new Ranges(v);
@@ -184,22 +303,17 @@ class Token extends AstElement {
184
303
  }
185
304
  }
186
305
 
306
+ /** 是否是普通节点 */
187
307
  isPlain() {
188
308
  return this.constructor === Token;
189
309
  }
190
310
 
191
- /** @param {...string|number|Range} args */
192
- protectChildren(...args) {
193
- if (!Parser.debugging && externalUse('protectChildren')) {
194
- this.debugOnly('protectChildren');
195
- }
196
- this.#protectedChildren.push(...new Ranges(args));
197
- }
198
-
199
311
  /**
200
- * @param {number} i
201
- * @returns {string|Token}
312
+ * @override
313
+ * @param {number} i 移除位置
314
+ * @returns {Token}
202
315
  * @complexity `n`
316
+ * @throws `Error` 不可移除的子节点
203
317
  */
204
318
  removeAt(i) {
205
319
  if (typeof i !== 'number') {
@@ -225,18 +339,25 @@ class Token extends AstElement {
225
339
  }
226
340
 
227
341
  /**
342
+ * @override
228
343
  * @template {string|Token} T
229
- * @param {T} token
344
+ * @param {T} token 待插入的子节点
345
+ * @param {number} i 插入位置
230
346
  * @complexity `n`
347
+ * @returns {T extends Token ? Token : AstText}
348
+ * @throws `RangeError` 不可插入的子节点
231
349
  */
232
350
  insertAt(token, i = this.childNodes.length) {
351
+ if (typeof token === 'string') {
352
+ token = new AstText(token);
353
+ }
233
354
  if (!Parser.running && this.#acceptable) {
234
355
  const acceptableIndices = Object.fromEntries(
235
356
  Object.entries(this.#acceptable)
236
357
  .map(([str, ranges]) => [str, ranges.applyTo(this.childNodes.length + 1)]),
237
358
  ),
238
359
  nodesAfter = this.childNodes.slice(i),
239
- insertedName = token.constructor.name,
360
+ {constructor: {name: insertedName}} = token,
240
361
  k = i < 0 ? i + this.childNodes.length : i;
241
362
  if (!acceptableIndices[insertedName].includes(k)) {
242
363
  throw new RangeError(`${this.constructor.name} 的第 ${k} 个子节点不能为 ${insertedName}!`);
@@ -245,15 +366,18 @@ class Token extends AstElement {
245
366
  }
246
367
  }
247
368
  super.insertAt(token, i);
248
- if (token instanceof Token && token.type === 'root') {
369
+ if (token.type === 'root') {
249
370
  token.type = 'plain';
250
371
  }
251
372
  return token;
252
373
  }
253
374
 
254
375
  /**
255
- * @param {Token} token
376
+ * 替换为同类节点
377
+ * @param {Token} token 待替换的节点
256
378
  * @complexity `n`
379
+ * @throws `Error` 不存在父节点
380
+ * @throws `Error` 待替换的节点具有不同属性
257
381
  */
258
382
  safeReplaceWith(token) {
259
383
  const {parentNode} = this;
@@ -280,28 +404,78 @@ class Token extends AstElement {
280
404
  token.dispatchEvent(e, {position: i, oldToken: this, newToken: token});
281
405
  }
282
406
 
283
- /** @param {string} title */
407
+ /**
408
+ * 创建HTML注释
409
+ * @param {string} data 注释内容
410
+ */
411
+ createComment(data = '') {
412
+ if (typeof data === 'string') {
413
+ const CommentToken = require('./nowiki/comment');
414
+ const config = this.getAttribute('config');
415
+ return Parser.run(() => new CommentToken(data.replaceAll('-->', '--&gt;'), true, config));
416
+ }
417
+ return this.typeError('createComment', 'String');
418
+ }
419
+
420
+ /**
421
+ * 创建标签
422
+ * @param {string} tagName 标签名
423
+ * @param {{selfClosing: boolean, closing: boolean}} options 选项
424
+ * @throws `RangeError` 非法的标签名
425
+ */
426
+ createElement(tagName, {selfClosing, closing} = {}) {
427
+ if (typeof tagName !== 'string') {
428
+ this.typeError('createElement', 'String');
429
+ }
430
+ const config = this.getAttribute('config'),
431
+ include = this.getAttribute('include');
432
+ if (tagName === (include ? 'noinclude' : 'includeonly')) {
433
+ const IncludeToken = require('./tagPair/include');
434
+ return Parser.run(
435
+ () => new IncludeToken(tagName, '', undefined, selfClosing ? undefined : tagName, config),
436
+ );
437
+ } else if (config.ext.includes(tagName)) {
438
+ const ExtToken = require('./tagPair/ext');
439
+ return Parser.run(() => new ExtToken(tagName, '', '', selfClosing ? undefined : '', config));
440
+ } else if (config.html.flat().includes(tagName)) {
441
+ const HtmlToken = require('./html');
442
+ return Parser.run(() => new HtmlToken(tagName, '', closing, selfClosing, config));
443
+ }
444
+ throw new RangeError(`非法的标签名!${tagName}`);
445
+ }
446
+
447
+ /**
448
+ * 判断标题是否是跨维基链接
449
+ * @param {string} title 标题
450
+ */
284
451
  isInterwiki(title) {
285
452
  return Parser.isInterwiki(title, this.#config);
286
453
  }
287
454
 
288
- /** @param {string} title */
455
+ /**
456
+ * 规范化页面标题
457
+ * @param {string} title 标题(含或不含命名空间前缀)
458
+ * @param {number} defaultNs 命名空间
459
+ */
289
460
  normalizeTitle(title, defaultNs = 0, halfParsed = false) {
290
461
  return Parser.normalizeTitle(title, defaultNs, this.#include, this.#config, halfParsed);
291
462
  }
292
463
 
293
- /** @complexity `n` */
464
+ /**
465
+ * 获取全部章节
466
+ * @complexity `n`
467
+ */
294
468
  sections() {
295
469
  if (this.type !== 'root') {
296
- return;
470
+ return undefined;
297
471
  }
298
472
  const {childNodes} = this,
299
- headings = [...childNodes.entries()]
300
- .filter(([, child]) => child instanceof Token && child.type === 'heading')
301
- .map(/** @param {[number, Token]} */ ([i, {name}]) => [i, Number(name)]),
302
- lastHeading = [-1, -1, -1, -1, -1, -1];
303
- const /** @type {(string|Token)[][]} */ sections = new Array(headings.length);
304
- for (const [i, [index, level]] of headings.entries()) {
473
+ headings = [...childNodes.entries()].filter(([, {type}]) => type === 'heading')
474
+ .map(([i, {name}]) => [i, Number(name)]),
475
+ lastHeading = [-1, -1, -1, -1, -1, -1],
476
+ /** @type {(AstText|Token)[][]} */ sections = new Array(headings.length);
477
+ for (let i = 0; i < headings.length; i++) {
478
+ const [index, level] = headings[i];
305
479
  for (let j = level; j < 6; j++) {
306
480
  const last = lastHeading[j];
307
481
  if (last >= 0) {
@@ -320,20 +494,20 @@ class Token extends AstElement {
320
494
  }
321
495
 
322
496
  /**
323
- * @param {number} n
497
+ * 获取指定章节
498
+ * @param {number} n 章节序号
324
499
  * @complexity `n`
325
500
  */
326
501
  section(n) {
327
- if (typeof n !== 'number') {
328
- this.typeError('section', 'Number');
329
- }
330
- return this.sections()[n];
502
+ return typeof n === 'number' ? this.sections()?.[n] : this.typeError('section', 'Number');
331
503
  }
332
504
 
333
505
  /**
334
- * @param {string|undefined} tag
506
+ * 获取指定的外层HTML标签
507
+ * @param {string|undefined} tag HTML标签名
335
508
  * @returns {[Token, Token]}
336
509
  * @complexity `n`
510
+ * @throws `RangeError` 非法的标签或空标签
337
511
  */
338
512
  findEnclosingHtml(tag) {
339
513
  if (tag !== undefined && typeof tag !== 'string') {
@@ -343,38 +517,46 @@ class Token extends AstElement {
343
517
  if (tag !== undefined && !this.#config.html.slice(0, 2).flat().includes(tag)) {
344
518
  throw new RangeError(`非法的标签或空标签:${tag}`);
345
519
  }
346
- const {parentElement} = this;
347
- if (!parentElement) {
348
- return;
520
+ const {parentNode} = this;
521
+ if (!parentNode) {
522
+ return undefined;
349
523
  }
350
- const {children} = parentElement,
351
- index = children.indexOf(this);
524
+ const {childNodes} = parentNode,
525
+ index = childNodes.indexOf(this);
352
526
  let i;
353
527
  for (i = index - 1; i >= 0; i--) {
354
- if (children[i].matches(`html${tag && '#'}${tag ?? ''}[selfClosing=false][closing=false]`)) {
528
+ const {type, name, selfClosing, closing} = childNodes[i];
529
+ if (type === 'html' && (!tag || name === tag) && selfClosing === false && closing === false) {
355
530
  break;
356
531
  }
357
532
  }
358
533
  if (i === -1) {
359
- return parentElement.findEnclosingHtml(tag);
534
+ return parentNode.findEnclosingHtml(tag);
360
535
  }
361
- const opening = children[i],
362
- {name} = opening;
363
- for (i = index + 1; i < children.length; i++) {
364
- if (children[i].matches(`html#${name}[selfClosing=false][closing=true]`)) {
536
+ const opening = childNodes[i];
537
+ for (i = index + 1; i < childNodes.length; i++) {
538
+ const {type, name, selfClosing, closing} = childNodes[i];
539
+ if (type === 'html' && name === opening.name && selfClosing === false && closing === true) {
365
540
  break;
366
541
  }
367
542
  }
368
- return i === children.length
369
- ? parentElement.findEnclosingHtml(tag)
370
- : [opening, children[i]];
543
+ return i === childNodes.length
544
+ ? parentNode.findEnclosingHtml(tag)
545
+ : [opening, childNodes[i]];
371
546
  }
372
547
 
373
- /** @complexity `n` */
548
+ /**
549
+ * 获取全部分类
550
+ * @complexity `n`
551
+ */
374
552
  getCategories() {
375
553
  return this.querySelectorAll('category').map(({name, sortkey}) => [name, sortkey]);
376
554
  }
377
555
 
556
+ /**
557
+ * 重新解析单引号
558
+ * @throws `Error` 不接受QuoteToken作为子节点
559
+ */
378
560
  redoQuotes() {
379
561
  const acceptable = this.getAttribute('acceptable');
380
562
  if (acceptable && !acceptable.QuoteToken?.some(
@@ -383,24 +565,24 @@ class Token extends AstElement {
383
565
  throw new Error(`${this.constructor.name} 不接受 QuoteToken 作为子节点!`);
384
566
  }
385
567
  for (const quote of this.childNodes) {
386
- if (quote instanceof Token && quote.type === 'quote') {
387
- quote.replaceWith(quote.firstChild);
568
+ if (quote.type === 'quote') {
569
+ quote.replaceWith(String(quote));
388
570
  }
389
571
  }
390
572
  this.normalize();
391
- /** @type {[number, string][]} */
392
- const textNodes = [...this.childNodes.entries()].filter(([, child]) => typeof child === 'string'),
573
+ /** @type {[number, AstText][]} */
574
+ const textNodes = [...this.childNodes.entries()].filter(([, {type}]) => type === 'text'),
393
575
  indices = textNodes.map(([i]) => this.getRelativeIndex(i)),
394
576
  token = Parser.run(() => {
395
- const root = new Token(textNodes.map(([, str]) => str).join(''), this.getAttribute('config'));
577
+ const root = new Token(text(textNodes.map(([, str]) => str)), this.getAttribute('config'));
396
578
  return root.setAttribute('stage', 6).parse(7);
397
579
  });
398
580
  for (const quote of token.children.reverse()) {
399
581
  if (quote.type === 'quote') {
400
582
  const index = quote.getRelativeIndex(),
401
583
  n = indices.findLastIndex(textIndex => textIndex <= index);
402
- this.splitText(n, index - indices[n]);
403
- this.splitText(n + 1, Number(quote.name));
584
+ this.childNodes[n].splitText(index - indices[n]);
585
+ this.childNodes[n + 1].splitText(Number(quote.name));
404
586
  this.removeAt(n + 1);
405
587
  this.insertAt(quote, n + 1);
406
588
  }
@@ -408,82 +590,6 @@ class Token extends AstElement {
408
590
  this.normalize();
409
591
  }
410
592
 
411
- /**
412
- * 将维基语法替换为占位符
413
- * @this {Token & {firstChild: string}}
414
- */
415
- parseOnce(n = this.#stage, include = false) {
416
- if (!Parser.debugging && externalUse('parseOnce')) {
417
- this.debugOnly('parseOnce');
418
- } else if (n < this.#stage || !this.isPlain() || this.childNodes.length === 0) {
419
- return this;
420
- }
421
- switch (n) {
422
- case 0:
423
- if (this.type === 'root') {
424
- this.#accum.shift();
425
- }
426
- this.#parseCommentAndExt(include);
427
- break;
428
- case 1:
429
- this.#parseBrackets();
430
- break;
431
- case 2:
432
- this.#parseHtml();
433
- break;
434
- case 3:
435
- this.#parseTable();
436
- break;
437
- case 4:
438
- this.#parseHrAndDoubleUndescore();
439
- break;
440
- case 5:
441
- this.#parseLinks();
442
- break;
443
- case 6: {
444
- this.#parseQuotes();
445
- break;
446
- }
447
- case 7:
448
- this.#parseExternalLinks();
449
- break;
450
- case 8:
451
- this.#parseMagicLinks();
452
- break;
453
- case 9:
454
- this.#parseList();
455
- break;
456
- case 10:
457
- this.#parseConverter();
458
- // no default
459
- }
460
- if (this.type === 'root') {
461
- for (const token of this.#accum) {
462
- token.parseOnce(n, include);
463
- }
464
- }
465
- this.#stage++;
466
- return this;
467
- }
468
-
469
- /**
470
- * @param {string} str
471
- * @complexity `n`
472
- */
473
- buildFromStr(str) {
474
- if (!Parser.debugging && externalUse('buildFromStr')) {
475
- this.debugOnly('buildFromStr');
476
- }
477
- return str.split(/[\0\x7f]/).map((s, i) => {
478
- if (i % 2 === 0) {
479
- return s;
480
- } else if (!isNaN(s.at(-1))) {
481
- throw new Error(`解析错误!未正确标记的 Token:${s}`);
482
- }
483
- return this.#accum[Number(s.slice(0, -1))];
484
- });
485
- }
486
-
487
593
  /**
488
594
  * 将占位符替换为子Token
489
595
  * @complexity `n`
@@ -493,15 +599,15 @@ class Token extends AstElement {
493
599
  this.debugOnly('build');
494
600
  }
495
601
  this.#stage = MAX_STAGE;
496
- const {childNodes: {length}, firstChild} = this;
497
- if (length !== 1 || typeof firstChild !== 'string' || !firstChild.includes('\0')) {
498
- return this;
499
- }
500
- this.replaceChildren(...this.buildFromStr(firstChild));
501
- this.normalize();
502
- if (this.type === 'root') {
503
- for (const token of this.#accum) {
504
- token.build();
602
+ const {childNodes: {length}, firstChild} = this,
603
+ str = String(firstChild);
604
+ if (length === 1 && firstChild.type === 'text' && str.includes('\0')) {
605
+ this.replaceChildren(...this.#buildFromStr(str));
606
+ this.normalize();
607
+ if (this.type === 'root') {
608
+ for (const token of this.#accum) {
609
+ token.build();
610
+ }
505
611
  }
506
612
  }
507
613
  return this;
@@ -519,35 +625,48 @@ class Token extends AstElement {
519
625
  return this;
520
626
  }
521
627
 
522
- /** 解析、重构、生成部分Token的`name`属性 */
628
+ /**
629
+ * 解析、重构、生成部分Token的`name`属性
630
+ * @param {number} n 最大解析层级
631
+ * @param {boolean} include 是否嵌入
632
+ */
523
633
  parse(n = MAX_STAGE, include = false) {
524
634
  if (typeof n !== 'number') {
525
635
  this.typeError('parse', 'Number');
526
- } else if (n < MAX_STAGE && !Parser.debugging && externalUse('parse')) {
636
+ } else if (n < MAX_STAGE && !Parser.debugging && Parser.warning && externalUse('parse')) {
527
637
  Parser.warn('指定解析层级的方法仅供熟练用户使用!');
528
638
  }
529
639
  this.#include = Boolean(include);
530
640
  while (this.#stage < n) {
531
- this.parseOnce(this.#stage, include);
641
+ this.#parseOnce(this.#stage, include);
532
642
  }
533
643
  return n ? this.build().afterBuild() : this;
534
644
  }
535
645
 
536
- #parseCommentAndExt(includeOnly = false) {
646
+ /**
647
+ * 解析HTML注释和扩展标签
648
+ * @param {boolean} includeOnly 是否嵌入
649
+ */
650
+ #parseCommentAndExt(includeOnly) {
537
651
  const parseCommentAndExt = require('../parser/commentAndExt');
538
- this.setText(parseCommentAndExt(this.firstChild, this.#config, this.#accum, includeOnly));
652
+ this.setText(parseCommentAndExt(String(this), this.#config, this.#accum, includeOnly));
539
653
  }
540
654
 
655
+ /** 解析花括号 */
541
656
  #parseBrackets() {
542
657
  const parseBrackets = require('../parser/brackets');
543
- this.setText(parseBrackets(this.firstChild, this.#config, this.#accum));
658
+ const str = this.type === 'root' ? String(this) : `\0${String(this)}`,
659
+ parsed = parseBrackets(str, this.#config, this.#accum);
660
+ this.setText(this.type === 'root' ? parsed : parsed.slice(1));
544
661
  }
545
662
 
663
+ /** 解析HTML标签 */
546
664
  #parseHtml() {
547
665
  const parseHtml = require('../parser/html');
548
- this.setText(parseHtml(this.firstChild, this.#config, this.#accum));
666
+ this.setText(parseHtml(String(this), this.#config, this.#accum));
549
667
  }
550
668
 
669
+ /** 解析表格 */
551
670
  #parseTable() {
552
671
  const parseTable = require('../parser/table'),
553
672
  TableToken = require('./table');
@@ -555,7 +674,7 @@ class Token extends AstElement {
555
674
  for (const table of this.#accum) {
556
675
  if (table instanceof TableToken && table.type !== 'td') {
557
676
  table.normalize();
558
- const [, child] = table.childNodes;
677
+ const {childNodes: [, child]} = table;
559
678
  if (typeof child === 'string' && child.includes('\0')) {
560
679
  table.removeAt(1);
561
680
  const inner = new Token(child, this.#config, true, this.#accum);
@@ -566,49 +685,58 @@ class Token extends AstElement {
566
685
  }
567
686
  }
568
687
 
688
+ /** 解析\<hr\>和状态开关 */
569
689
  #parseHrAndDoubleUndescore() {
570
690
  const parseHrAndDoubleUnderscore = require('../parser/hrAndDoubleUnderscore');
571
- this.setText(parseHrAndDoubleUnderscore(this.firstChild, this.#config, this.#accum));
691
+ this.setText(parseHrAndDoubleUnderscore(this, this.#config, this.#accum));
572
692
  }
573
693
 
694
+ /** 解析内部链接 */
574
695
  #parseLinks() {
575
696
  const parseLinks = require('../parser/links');
576
- this.setText(parseLinks(this.firstChild, this.#config, this.#accum));
697
+ this.setText(parseLinks(String(this), this.#config, this.#accum));
577
698
  }
578
699
 
579
- /** @this {Token & {firstChild: string}} */
700
+ /** 解析单引号 */
580
701
  #parseQuotes() {
581
- const parseQuotes = require('../parser/quotes'),
582
- lines = this.firstChild.split('\n');
702
+ const parseQuotes = require('../parser/quotes');
703
+ const lines = String(this).split('\n');
583
704
  for (let i = 0; i < lines.length; i++) {
584
705
  lines[i] = parseQuotes(lines[i], this.#config, this.#accum);
585
706
  }
586
707
  this.setText(lines.join('\n'));
587
708
  }
588
709
 
710
+ /** 解析外部链接 */
589
711
  #parseExternalLinks() {
590
712
  const parseExternalLinks = require('../parser/externalLinks');
591
- this.setText(parseExternalLinks(this.firstChild, this.#config, this.#accum));
713
+ this.setText(parseExternalLinks(String(this), this.#config, this.#accum));
592
714
  }
593
715
 
716
+ /** 解析自由外链 */
594
717
  #parseMagicLinks() {
595
718
  const parseMagicLinks = require('../parser/magicLinks');
596
- this.setText(parseMagicLinks(this.firstChild, this.#config, this.#accum));
719
+ this.setText(parseMagicLinks(String(this), this.#config, this.#accum));
597
720
  }
598
721
 
599
- /** @this {Token & {firstChild: string}} */
722
+ /** 解析列表 */
600
723
  #parseList() {
601
- const parseList = require('../parser/list'),
602
- lines = this.firstChild.split('\n');
603
- for (let i = this.type === 'root' ? 0 : 1; i < lines.length; i++) {
724
+ if (this.type === 'image-parameter') {
725
+ return;
726
+ }
727
+ const parseList = require('../parser/list');
728
+ const lines = String(this).split('\n');
729
+ let i = this.type === 'root' || this.type === 'ext-inner' && this.type === 'poem' ? 0 : 1;
730
+ for (; i < lines.length; i++) {
604
731
  lines[i] = parseList(lines[i], this.#config, this.#accum);
605
732
  }
606
733
  this.setText(lines.join('\n'));
607
734
  }
608
735
 
736
+ /** 解析语言变体转换 */
609
737
  #parseConverter() {
610
738
  const parseConverter = require('../parser/converter');
611
- this.setText(parseConverter(this.firstChild, this.#config, this.#accum));
739
+ this.setText(parseConverter(String(this), this.#config, this.#accum));
612
740
  }
613
741
  }
614
742