wikiparser-node 0.0.2 → 0.2.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 (49) hide show
  1. package/README.md +226 -7
  2. package/config/default.json +12 -1
  3. package/config/llwiki.json +12 -1
  4. package/config/moegirl.json +9 -1
  5. package/errors/2022-07-04T22:30:41.785Z +1 -0
  6. package/errors/2022-07-04T22:30:41.785Z.err +11 -0
  7. package/errors/2022-07-04T22:30:41.785Z.json +5 -0
  8. package/errors/README +1 -0
  9. package/index.js +85 -9
  10. package/lib/element.js +72 -13
  11. package/lib/node.js +17 -9
  12. package/mixin/sol.js +42 -0
  13. package/package.json +1 -1
  14. package/parser/converter.js +44 -0
  15. package/parser/externalLinks.js +1 -1
  16. package/parser/list.js +58 -0
  17. package/parser/table.js +2 -2
  18. package/printed/README +1 -0
  19. package/src/arg.js +9 -9
  20. package/src/attribute.js +33 -23
  21. package/src/converter.js +135 -0
  22. package/src/converterFlags.js +214 -0
  23. package/src/converterRule.js +209 -0
  24. package/src/extLink.js +23 -10
  25. package/src/heading.js +15 -20
  26. package/src/html.js +4 -3
  27. package/src/imageParameter.js +6 -7
  28. package/src/index.js +38 -23
  29. package/src/link/file.js +9 -9
  30. package/src/link/index.js +9 -11
  31. package/src/magicLink.js +2 -3
  32. package/src/nowiki/comment.js +1 -1
  33. package/src/nowiki/dd.js +49 -0
  34. package/src/nowiki/hr.js +3 -2
  35. package/src/nowiki/list.js +16 -0
  36. package/src/parameter.js +5 -5
  37. package/src/syntax.js +3 -1
  38. package/src/table/index.js +35 -30
  39. package/src/table/td.js +3 -2
  40. package/src/table/tr.js +6 -12
  41. package/src/tagPair/index.js +1 -1
  42. package/src/transclude.js +28 -25
  43. package/tool/index.js +50 -40
  44. package/typings/index.d.ts +3 -0
  45. package/typings/node.d.ts +3 -3
  46. package/typings/token.d.ts +1 -0
  47. package/util/debug.js +3 -3
  48. package/util/string.js +16 -1
  49. package/src/listToken.js +0 -47
package/src/index.js CHANGED
@@ -37,9 +37,11 @@
37
37
  * l: LinkToken
38
38
  * q: QuoteToken
39
39
  * w: ExtLinkToken
40
+ * d: ListToken
41
+ * v: ConverterToken
40
42
  */
41
43
 
42
- const {typeError, externalUse} = require('../util/debug'),
44
+ const {externalUse} = require('../util/debug'),
43
45
  Ranges = require('../lib/ranges'),
44
46
  AstElement = require('../lib/element'),
45
47
  assert = require('assert/strict'),
@@ -201,9 +203,13 @@ class Token extends AstElement {
201
203
  * @complexity `n`
202
204
  */
203
205
  removeAt(i) {
206
+ if (typeof i !== 'number') {
207
+ this.typeError('removeAt', 'Number');
208
+ }
209
+ const iPos = i < 0 ? i + this.childNodes.length : i;
204
210
  if (!Parser.running) {
205
211
  const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
206
- if (protectedIndices.includes(i)) {
212
+ if (protectedIndices.includes(iPos)) {
207
213
  throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
208
214
  } else if (this.#acceptable) {
209
215
  const acceptableIndices = Object.fromEntries(
@@ -255,7 +261,7 @@ class Token extends AstElement {
255
261
  if (!parentNode) {
256
262
  throw new Error('不存在父节点!');
257
263
  } else if (token.constructor !== this.constructor) {
258
- typeError(this, 'safeReplaceWith', this.constructor.name);
264
+ this.typeError('safeReplaceWith', this.constructor.name);
259
265
  }
260
266
  try {
261
267
  assert.deepEqual(token.getAttribute('acceptable'), this.#acceptable);
@@ -320,7 +326,7 @@ class Token extends AstElement {
320
326
  */
321
327
  section(n) {
322
328
  if (typeof n !== 'number') {
323
- typeError(this, 'section', 'Number');
329
+ this.typeError('section', 'Number');
324
330
  }
325
331
  return this.sections()[n];
326
332
  }
@@ -332,7 +338,7 @@ class Token extends AstElement {
332
338
  */
333
339
  findEnclosingHtml(tag) {
334
340
  if (tag !== undefined && typeof tag !== 'string') {
335
- typeError(this, 'findEnclosingHtml', 'String');
341
+ this.typeError('findEnclosingHtml', 'String');
336
342
  }
337
343
  tag = tag?.toLowerCase();
338
344
  if (tag !== undefined && !this.#config.html.slice(0, 2).flat().includes(tag)) {
@@ -436,11 +442,7 @@ class Token extends AstElement {
436
442
  this.#parseLinks();
437
443
  break;
438
444
  case 6: {
439
- const lines = this.firstChild.split('\n');
440
- for (let i = 0; i < lines.length; i++) {
441
- lines[i] = this.#parseQuotes(lines[i]);
442
- }
443
- this.setText(lines.join('\n'));
445
+ this.#parseQuotes();
444
446
  break;
445
447
  }
446
448
  case 7:
@@ -450,8 +452,10 @@ class Token extends AstElement {
450
452
  this.#parseMagicLinks();
451
453
  break;
452
454
  case 9:
455
+ this.#parseList();
453
456
  break;
454
457
  case 10:
458
+ this.#parseConverter();
455
459
  // no default
456
460
  }
457
461
  if (this.type === 'root') {
@@ -519,7 +523,7 @@ class Token extends AstElement {
519
523
  /** 解析、重构、生成部分Token的`name`属性 */
520
524
  parse(n = MAX_STAGE, include = false) {
521
525
  if (typeof n !== 'number') {
522
- typeError(this, 'parse', 'Number');
526
+ this.typeError('parse', 'Number');
523
527
  } else if (n < MAX_STAGE && !Parser.debugging && externalUse('parse')) {
524
528
  Parser.warn('指定解析层级的方法仅供熟练用户使用!');
525
529
  }
@@ -530,25 +534,21 @@ class Token extends AstElement {
530
534
  return n ? this.build().afterBuild() : this;
531
535
  }
532
536
 
533
- /** @this {Token & {firstChild: string}} */
534
537
  #parseCommentAndExt(includeOnly = false) {
535
538
  const parseCommentAndExt = require('../parser/commentAndExt');
536
539
  this.setText(parseCommentAndExt(this.firstChild, this.#config, this.#accum, includeOnly));
537
540
  }
538
541
 
539
- /** @this {Token & {firstChild: string}} */
540
542
  #parseBrackets() {
541
543
  const parseBrackets = require('../parser/brackets');
542
544
  this.setText(parseBrackets(this.firstChild, this.#config, this.#accum));
543
545
  }
544
546
 
545
- /** @this {Token & {firstChild: string}} */
546
547
  #parseHtml() {
547
548
  const parseHtml = require('../parser/html');
548
549
  this.setText(parseHtml(this.firstChild, this.#config, this.#accum));
549
550
  }
550
551
 
551
- /** @this {Token & {firstChild: string}} */
552
552
  #parseTable() {
553
553
  const parseTable = require('../parser/table'),
554
554
  TableToken = require('./table');
@@ -567,35 +567,50 @@ class Token extends AstElement {
567
567
  }
568
568
  }
569
569
 
570
- /** @this {Token & {firstChild: string}} */
571
570
  #parseHrAndDoubleUndescore() {
572
571
  const parseHrAndDoubleUnderscore = require('../parser/hrAndDoubleUnderscore');
573
572
  this.setText(parseHrAndDoubleUnderscore(this.firstChild, this.#config, this.#accum));
574
573
  }
575
574
 
576
- /** @this {Token & {firstChild: string}} */
577
575
  #parseLinks() {
578
576
  const parseLinks = require('../parser/links');
579
577
  this.setText(parseLinks(this.firstChild, this.#config, this.#accum));
580
578
  }
581
579
 
582
- /** @param {string} text */
583
- #parseQuotes(text) {
584
- const parseQuotes = require('../parser/quotes');
585
- return parseQuotes(text, this.#config, this.#accum);
580
+ /** @this {Token & {firstChild: string}} */
581
+ #parseQuotes() {
582
+ const parseQuotes = require('../parser/quotes'),
583
+ lines = this.firstChild.split('\n');
584
+ for (let i = 0; i < lines.length; i++) {
585
+ lines[i] = parseQuotes(lines[i], this.#config, this.#accum);
586
+ }
587
+ this.setText(lines.join('\n'));
586
588
  }
587
589
 
588
- /** @this {Token & {firstChild: string}} */
589
590
  #parseExternalLinks() {
590
591
  const parseExternalLinks = require('../parser/externalLinks');
591
592
  this.setText(parseExternalLinks(this.firstChild, this.#config, this.#accum));
592
593
  }
593
594
 
594
- /** @this {Token & {firstChild: string}} */
595
595
  #parseMagicLinks() {
596
596
  const parseMagicLinks = require('../parser/magicLinks');
597
597
  this.setText(parseMagicLinks(this.firstChild, this.#config, this.#accum));
598
598
  }
599
+
600
+ /** @this {Token & {firstChild: string}} */
601
+ #parseList() {
602
+ const parseList = require('../parser/list'),
603
+ lines = this.firstChild.split('\n');
604
+ for (let i = this.type === 'root' ? 0 : 1; i < lines.length; i++) {
605
+ lines[i] = parseList(lines[i], this.#config, this.#accum);
606
+ }
607
+ this.setText(lines.join('\n'));
608
+ }
609
+
610
+ #parseConverter() {
611
+ const parseConverter = require('../parser/converter');
612
+ this.setText(parseConverter(this.firstChild, this.#config, this.#accum));
613
+ }
599
614
  }
600
615
 
601
616
  Parser.classes.Token = __filename;
package/src/link/file.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const {explode} = require('../../util/string'),
4
- {typeError, externalUse} = require('../../util/debug'),
3
+ const {explode, noWrap} = require('../../util/string'),
4
+ {externalUse} = require('../../util/debug'),
5
5
  /** @type {Parser} */ Parser = require('../..'),
6
6
  LinkToken = require('.'),
7
7
  ImageParameterToken = require('../imageParameter');
@@ -84,7 +84,7 @@ class FileToken extends LinkToken {
84
84
  * @param {ImageParameterToken} token
85
85
  * @complexity `n`
86
86
  */
87
- insertAt(token, i = this.childElementCount) {
87
+ insertAt(token, i = this.childNodes.length) {
88
88
  if (!Parser.running) {
89
89
  this.getArgs(token.name, false, false).add(token);
90
90
  this.#keys.add(token.name);
@@ -102,7 +102,7 @@ class FileToken extends LinkToken {
102
102
  const args = this.getAllArgs()
103
103
  .filter(({name}) => ['manualthumb', 'frameless', 'framed', 'thumbnail'].includes(name));
104
104
  if (args.length > 1) {
105
- Parser.error(`警告:图片 ${this.name} 带有 ${args.length} 个框架参数,只有第 1 个 ${args[0].name} 会生效!`);
105
+ Parser.error(`图片 ${this.name} 带有 ${args.length} 个框架参数,只有第 1 个 ${args[0].name} 会生效!`);
106
106
  }
107
107
  return args;
108
108
  }
@@ -113,7 +113,7 @@ class FileToken extends LinkToken {
113
113
  */
114
114
  getArgs(key, copy = true) {
115
115
  if (typeof key !== 'string') {
116
- typeError(this, 'getArgs', 'String');
116
+ this.typeError('getArgs', 'String');
117
117
  } else if (!copy && !Parser.debugging && externalUse('getArgs')) {
118
118
  this.debugOnly('getArgs');
119
119
  }
@@ -190,7 +190,7 @@ class FileToken extends LinkToken {
190
190
  */
191
191
  setValue(key, value) {
192
192
  if (typeof key !== 'string') {
193
- typeError(this, 'setValue', 'String');
193
+ this.typeError('setValue', 'String');
194
194
  } else if (value === false) {
195
195
  this.removeArg(key);
196
196
  return;
@@ -211,7 +211,7 @@ class FileToken extends LinkToken {
211
211
  }
212
212
  if (value === true) {
213
213
  if (syntax.includes('$1')) {
214
- typeError(this, 'setValue', 'Boolean');
214
+ this.typeError('setValue', 'Boolean');
215
215
  }
216
216
  const newArg = Parser.run(() => new ImageParameterToken(syntax, config));
217
217
  this.appendChild(newArg);
@@ -221,9 +221,9 @@ class FileToken extends LinkToken {
221
221
  root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
222
222
  {childNodes: {length}, firstElementChild} = root;
223
223
  if (length !== 1 || !firstElementChild?.matches('file#File:F')
224
- || firstElementChild.childElementCount !== 2 || firstElementChild.lastElementChild.name !== key
224
+ || firstElementChild.childNodes.length !== 2 || firstElementChild.lastElementChild.name !== key
225
225
  ) {
226
- throw new SyntaxError(`非法的 ${key} 参数:${value.replaceAll('\n', '\\n')}`);
226
+ throw new SyntaxError(`非法的 ${key} 参数:${noWrap(value)}`);
227
227
  }
228
228
  this.appendChild(firstElementChild.lastChild);
229
229
  }
package/src/link/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const Title = require('../../lib/title'), // eslint-disable-line no-unused-vars
4
- {text} = require('../../util/string'),
4
+ {text, noWrap} = require('../../util/string'),
5
5
  {undo} = require('../../util/debug'),
6
6
  /** @type {Parser} */ Parser = require('../..'),
7
7
  Token = require('..');
@@ -29,9 +29,9 @@ class LinkToken extends Token {
29
29
  'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
30
30
  }));
31
31
  if (linkText !== undefined) {
32
- const inner = new Token(linkText, config, true, accum);
32
+ const inner = new Token(linkText, config, true, accum, {'Stage-5': ':', ConverterToken: ':'});
33
33
  inner.type = 'link-text';
34
- this.appendChild(inner.setAttribute('stage', 7));
34
+ this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
35
35
  }
36
36
  this.selfLink = !title.title;
37
37
  this.fragment = title.fragment;
@@ -107,7 +107,7 @@ class LinkToken extends Token {
107
107
 
108
108
  /** @returns {[number, string][]} */
109
109
  plain() {
110
- return this.childElementCount === 1 ? [] : this.lastElementChild.plain();
110
+ return this.childNodes.length === 1 ? [] : this.lastElementChild.plain();
111
111
  }
112
112
 
113
113
  /** @param {string} link */
@@ -118,7 +118,7 @@ class LinkToken extends Token {
118
118
  }
119
119
  const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
120
120
  {childNodes: {length}, firstElementChild} = root;
121
- if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childElementCount !== 1) {
121
+ if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childNodes.length !== 1) {
122
122
  const msgs = {link: '内链', file: '文件链接', category: '分类'};
123
123
  throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
124
124
  }
@@ -135,7 +135,7 @@ class LinkToken extends Token {
135
135
  config = this.getAttribute('config'),
136
136
  root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
137
137
  {childNodes: {length}, firstElementChild} = root;
138
- if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childElementCount !== 1) {
138
+ if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childNodes.length !== 1) {
139
139
  throw new SyntaxError(`非法的 fragment:${fragment}`);
140
140
  }
141
141
  if (page) {
@@ -169,17 +169,15 @@ class LinkToken extends Token {
169
169
  this.type === 'category' ? 'Category:' : ''
170
170
  }L|${linkText}]]`, this.getAttribute('include'), 6, config),
171
171
  {childNodes: {length}, firstElementChild} = root;
172
- if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childElementCount !== 2) {
173
- throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${
174
- linkText.replaceAll('\n', '\\n')
175
- }`);
172
+ if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childNodes.length !== 2) {
173
+ throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
176
174
  }
177
175
  ({lastElementChild} = firstElementChild);
178
176
  } else {
179
177
  lastElementChild = Parser.run(() => new Token('', config));
180
178
  lastElementChild.setAttribute('stage', 7).type = 'link-text';
181
179
  }
182
- if (this.childElementCount === 1) {
180
+ if (this.childNodes.length === 1) {
183
181
  this.appendChild(lastElementChild);
184
182
  } else {
185
183
  this.lastElementChild.safeReplaceWith(lastElementChild);
package/src/magicLink.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../util/debug'),
4
- /** @type {Parser} */ Parser = require('..'),
3
+ const /** @type {Parser} */ Parser = require('..'),
5
4
  Token = require('.');
6
5
 
7
6
  /**
@@ -17,7 +16,7 @@ class MagicLinkToken extends Token {
17
16
  }
18
17
  set protocol(value) {
19
18
  if (typeof value !== 'string') {
20
- typeError(this, 'protocol', 'String');
19
+ this.typeError('protocol', 'String');
21
20
  }
22
21
  if (!new RegExp(`${this.#protocolRegex.source}$`, 'i').test(value)) {
23
22
  throw new RangeError(`非法的外链协议:${value}`);
@@ -30,7 +30,7 @@ class CommentToken extends hidden(NowikiToken) {
30
30
  toString() {
31
31
  const {firstChild, closed, nextSibling} = this;
32
32
  if (!closed && nextSibling) {
33
- Parser.error('自动闭合HTML注释', firstChild.replaceAll('\n', '\\n'));
33
+ Parser.error('自动闭合HTML注释', this);
34
34
  this.closed = true;
35
35
  }
36
36
  return `<!--${firstChild}${this.closed ? '-->' : ''}`;
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('../..'),
4
+ NowikiToken = require('.');
5
+
6
+ /**
7
+ * :
8
+ * @classdesc `{childNodes: [string]}`
9
+ */
10
+ class DdToken extends NowikiToken {
11
+ type = 'dd';
12
+ dt;
13
+ ul;
14
+ ol;
15
+ indent;
16
+
17
+ /** @param {string} str */
18
+ #update(str) {
19
+ this.setAttribute('ul', str.includes('*')).setAttribute('ol', str.includes('#'))
20
+ .setAttribute('dt', str.includes(';')).setAttribute('indent', str.split(':').length - 1);
21
+ }
22
+
23
+ /**
24
+ * @param {string} str
25
+ * @param {accum} accum
26
+ */
27
+ constructor(str, config = Parser.getConfig(), accum = []) {
28
+ super(str, config, accum);
29
+ this.seal(['dt', 'ul', 'ol', 'indent']).#update(str);
30
+ }
31
+
32
+ /** @returns {[number, string][]} */
33
+ plain() {
34
+ return [];
35
+ }
36
+
37
+ /** @param {string} str */
38
+ setText(str) {
39
+ const src = this.type === 'dd' ? ':' : ';:*#';
40
+ if (new RegExp(`[^${src}]`).test(str)) {
41
+ throw new RangeError(`${this.constructor.name} 仅能包含${src.split('').map(c => `"${c}"`).join('、')}!`);
42
+ }
43
+ this.#update(str);
44
+ return super.setText(str);
45
+ }
46
+ }
47
+
48
+ Parser.classes.DdToken = __filename;
49
+ module.exports = DdToken;
package/src/nowiki/hr.js CHANGED
@@ -1,13 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('../..'),
3
+ const sol = require('../../mixin/sol'),
4
+ /** @type {Parser} */ Parser = require('../..'),
4
5
  NowikiToken = require('.');
5
6
 
6
7
  /**
7
8
  * `<hr>`
8
9
  * @classdesc `{childNodes: [string]}`
9
10
  */
10
- class HrToken extends NowikiToken {
11
+ class HrToken extends sol(NowikiToken) {
11
12
  type = 'hr';
12
13
 
13
14
  /**
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const sol = require('../../mixin/sol'),
4
+ /** @type {Parser} */ Parser = require('../..'),
5
+ DdToken = require('./dd');
6
+
7
+ /**
8
+ * ;:*#
9
+ * @classdesc `{childNodes: [string]}`
10
+ */
11
+ class ListToken extends sol(DdToken) {
12
+ type = 'list';
13
+ }
14
+
15
+ Parser.classes.ListToken = __filename;
16
+ module.exports = ListToken;
package/src/parameter.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../util/debug'),
3
+ const {noWrap} = require('../util/string'),
4
4
  fixedToken = require('../mixin/fixedToken'),
5
5
  /** @type {Parser} */ Parser = require('..'),
6
6
  Token = require('.');
@@ -113,10 +113,10 @@ class ParameterToken extends fixedToken(Token) {
113
113
  {childNodes: {length}, firstElementChild} = root,
114
114
  /** @type {ParameterToken} */ lastElementChild = firstElementChild?.lastElementChild;
115
115
  if (length !== 1 || !firstElementChild?.matches(templateLike ? 'template#T' : 'magic-word#lc')
116
- || firstElementChild.childElementCount !== 2
116
+ || firstElementChild.childNodes.length !== 2
117
117
  || lastElementChild.anon !== this.anon || lastElementChild.name !== '1'
118
118
  ) {
119
- throw new SyntaxError(`非法的模板参数:${value.replaceAll('\n', '\\n')}`);
119
+ throw new SyntaxError(`非法的模板参数:${noWrap(value)}`);
120
120
  }
121
121
  const newValue = lastElementChild.lastChild;
122
122
  root.destroy();
@@ -128,7 +128,7 @@ class ParameterToken extends fixedToken(Token) {
128
128
  /** @param {string} key */
129
129
  rename(key, force = false) {
130
130
  if (typeof key !== 'string') {
131
- typeError(this, 'rename', 'String');
131
+ this.typeError('rename', 'String');
132
132
  }
133
133
  const {parentNode} = this;
134
134
  // 必须检测是否是TranscludeToken
@@ -139,7 +139,7 @@ class ParameterToken extends fixedToken(Token) {
139
139
  }
140
140
  const root = Parser.parse(`{{:T|${key}=}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
141
141
  {childNodes: {length}, firstElementChild} = root;
142
- if (length !== 1 || !firstElementChild?.matches('template#T') || firstElementChild.childElementCount !== 2) {
142
+ if (length !== 1 || !firstElementChild?.matches('template#T') || firstElementChild.childNodes.length !== 2) {
143
143
  throw new SyntaxError(`非法的模板参数名:${key}`);
144
144
  }
145
145
  const {lastElementChild} = firstElementChild,
package/src/syntax.js CHANGED
@@ -38,8 +38,10 @@ class SyntaxToken extends Token {
38
38
  afterBuild() {
39
39
  const that = this,
40
40
  /** @type {AstListener} */ syntaxListener = (e, data) => {
41
- if (!Parser.running && !that.#pattern.test(that.text())) {
41
+ const pattern = that.#pattern;
42
+ if (!Parser.running && !pattern.test(that.text())) {
42
43
  undo(e, data);
44
+ throw new Error(`不可修改 ${that.constructor.name} 的语法:/${pattern.source}/${pattern.flags}`);
43
45
  }
44
46
  };
45
47
  this.addEventListener(['remove', 'insert', 'replace', 'text'], syntaxListener);
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../../util/debug'),
4
- assert = require('assert/strict'),
3
+ const assert = require('assert/strict'),
4
+ {noWrap} = require('../../util/string'),
5
5
  /** @type {Parser} */ Parser = require('../..'),
6
6
  Token = require('..'), // eslint-disable-line no-unused-vars
7
7
  TrToken = require('./tr'),
@@ -70,40 +70,45 @@ class TableToken extends TrToken {
70
70
  }
71
71
 
72
72
  /**
73
- * @template {string|Token} T
73
+ * @template {TrToken|SyntaxToken} T
74
74
  * @param {T} token
75
75
  * @returns {T}
76
76
  * @complexity `n`
77
77
  */
78
78
  insertAt(token, i = this.childNodes.length) {
79
- const previous = this.childNodes.at(i - 1),
79
+ const previous = this.children.at(i - 1),
80
80
  {closingPattern} = TableToken;
81
- if (token instanceof TrToken && token.type === 'td' && previous instanceof TrToken && previous.type === 'tr') {
81
+ if (token.type === 'td' && previous.type === 'tr') {
82
82
  Parser.warn('改为将单元格插入当前行。');
83
83
  return previous.appendChild(token);
84
84
  } else if (!Parser.running && i === this.childNodes.length && token instanceof SyntaxToken
85
85
  && (token.getAttribute('pattern') !== closingPattern || !closingPattern.test(token.text()))
86
86
  ) {
87
- throw new SyntaxError(`表格的闭合部分不符合语法!${token.toString().replaceAll('\n', '\\n')}`);
87
+ throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(token.toString())}`);
88
88
  }
89
89
  return super.insertAt(token, i);
90
90
  }
91
91
 
92
92
  /** @complexity `n` */
93
- close(syntax = '\n|}') {
93
+ close(syntax = '\n|}', halfParsed = false) {
94
+ halfParsed &&= Parser.running;
94
95
  const config = this.getAttribute('config'),
95
- inner = Parser.parse(syntax, this.getAttribute('include'), 2, config),
96
- {lastElementChild} = this;
97
- if (!TableToken.closingPattern.test(inner.text())) {
98
- throw new SyntaxError(`表格的闭合部分不符合语法!${syntax.replaceAll('\n', '\\n')}`);
96
+ accum = this.getAttribute('accum'),
97
+ inner = !halfParsed && Parser.parse(syntax, this.getAttribute('include'), 2, config),
98
+ {lastElementChild} = this,
99
+ {closingPattern} = TableToken;
100
+ if (!halfParsed && !closingPattern.test(inner.text())) {
101
+ throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(syntax)}`);
99
102
  } else if (lastElementChild instanceof SyntaxToken) {
100
103
  lastElementChild.replaceChildren(...inner.childNodes);
101
104
  } else {
102
105
  this.appendChild(Parser.run(() => {
103
- const token = new SyntaxToken(undefined, TableToken.closingPattern, 'table-syntax', config, [], {
106
+ const token = new SyntaxToken(syntax, closingPattern, 'table-syntax', config, accum, {
104
107
  'Stage-1': ':', '!ExtToken': '', TranscludeToken: ':',
105
108
  });
106
- token.replaceChildren(...inner.childNodes);
109
+ if (inner) {
110
+ token.replaceChildren(...inner.childNodes);
111
+ }
107
112
  return token;
108
113
  }));
109
114
  }
@@ -132,7 +137,7 @@ class TableToken extends TrToken {
132
137
  */
133
138
  getNthRow(n, force = false, insert = false) {
134
139
  if (typeof n !== 'number') {
135
- typeError(this, 'getNthRow', 'Number');
140
+ this.typeError('getNthRow', 'Number');
136
141
  }
137
142
  const nRows = this.getRowCount(),
138
143
  isRow = super.getRowCount();
@@ -240,7 +245,7 @@ class TableToken extends TrToken {
240
245
  */
241
246
  toRenderedCoords({row, column}) {
242
247
  if (typeof row !== 'number' || typeof column !== 'number') {
243
- typeError(this, 'toRenderedCoords', 'Number');
248
+ this.typeError('toRenderedCoords', 'Number');
244
249
  }
245
250
  const rowLayout = this.getLayout({row, column})[row],
246
251
  x = rowLayout?.findIndex(coords => cmpCoords(coords, {row, column}) === 0);
@@ -253,7 +258,7 @@ class TableToken extends TrToken {
253
258
  */
254
259
  toRawCoords({x, y}) {
255
260
  if (typeof x !== 'number' || typeof y !== 'number') {
256
- typeError(this, 'toRawCoords', 'Number');
261
+ this.typeError('toRawCoords', 'Number');
257
262
  }
258
263
  const rowLayout = this.getLayout({x, y})[y],
259
264
  coords = rowLayout?.[x];
@@ -272,7 +277,7 @@ class TableToken extends TrToken {
272
277
  */
273
278
  getFullRow(y) {
274
279
  if (typeof y !== 'number') {
275
- typeError(this, 'getFullRow', 'Number');
280
+ this.typeError('getFullRow', 'Number');
276
281
  }
277
282
  const rows = this.getAllRows();
278
283
  return new Map(
@@ -286,7 +291,7 @@ class TableToken extends TrToken {
286
291
  */
287
292
  getFullCol(x) {
288
293
  if (typeof x !== 'number') {
289
- typeError(this, 'getFullCol', 'Number');
294
+ this.typeError('getFullCol', 'Number');
290
295
  }
291
296
  const layout = this.getLayout(),
292
297
  colLayout = layout.map(row => row[x]).filter(coords => coords),
@@ -412,14 +417,14 @@ class TableToken extends TrToken {
412
417
  /** @complexity `n` */
413
418
  #prependTableRow() {
414
419
  const row = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config'))),
415
- {childNodes} = this,
416
- [,, plain] = childNodes,
417
- start = typeof plain === 'string' || plain.isPlain() ? 3 : 2,
418
- /** @type {TdToken[]} */ children = childNodes.slice(start),
419
- index = children.findIndex(({type}) => type !== 'td');
420
+ {children} = this,
421
+ [,, plain] = children,
422
+ start = plain?.isPlain() ? 3 : 2,
423
+ /** @type {TdToken[]} */ tdChildren = children.slice(start),
424
+ index = tdChildren.findIndex(({type}) => type !== 'td');
420
425
  this.insertAt(row, index === -1 ? -1 : index + start);
421
426
  Parser.run(() => {
422
- for (const cell of children.slice(0, index === -1 ? undefined : index)) {
427
+ for (const cell of tdChildren.slice(0, index === -1 ? undefined : index)) {
423
428
  if (cell.subtype !== 'caption') {
424
429
  row.appendChild(cell);
425
430
  }
@@ -438,7 +443,7 @@ class TableToken extends TrToken {
438
443
  */
439
444
  insertTableRow(y, attr = {}, inner = undefined, subtype = 'td', innerAttr = {}) {
440
445
  if (typeof attr !== 'object') {
441
- typeError(this, 'insertTableRow', 'Object');
446
+ this.typeError('insertTableRow', 'Object');
442
447
  }
443
448
  let reference = this.getNthRow(y, false, true);
444
449
  /** @type {TrToken & AttributeToken}} */
@@ -482,7 +487,7 @@ class TableToken extends TrToken {
482
487
  */
483
488
  insertTableCol(x, inner, subtype = 'td', attr = {}) {
484
489
  if (typeof x !== 'number') {
485
- typeError(this, 'insertTableCol', 'Number');
490
+ this.typeError('insertTableCol', 'Number');
486
491
  }
487
492
  const layout = this.getLayout(),
488
493
  rowLength = layout.map(({length}) => length),
@@ -565,7 +570,7 @@ class TableToken extends TrToken {
565
570
  */
566
571
  mergeCells(xlim, ylim) {
567
572
  if ([...xlim, ...ylim].some(arg => typeof arg !== 'number')) {
568
- typeError(this, 'mergeCells', 'Number');
573
+ this.typeError('mergeCells', 'Number');
569
574
  }
570
575
  const layout = this.getLayout(),
571
576
  maxCol = Math.max(...layout.map(({length}) => length));
@@ -711,7 +716,7 @@ class TableToken extends TrToken {
711
716
  */
712
717
  moveTableRowBefore(y, before) {
713
718
  if (typeof y !== 'number' || typeof before !== 'number') {
714
- typeError(this, 'moveTableRowBefore', 'Number');
719
+ this.typeError('moveTableRowBefore', 'Number');
715
720
  }
716
721
  const layout = this.getLayout(),
717
722
  /**
@@ -748,7 +753,7 @@ class TableToken extends TrToken {
748
753
  */
749
754
  moveTableRowAfter(y, after) {
750
755
  if (typeof y !== 'number' || typeof after !== 'number') {
751
- typeError(this, 'moveTableRowAfter', 'Number');
756
+ this.typeError('moveTableRowAfter', 'Number');
752
757
  }
753
758
  const layout = this.getLayout(),
754
759
  afterToken = this.getNthRow(after),
@@ -797,7 +802,7 @@ class TableToken extends TrToken {
797
802
  */
798
803
  #moveCol(x, reference, after = false) {
799
804
  if (typeof x !== 'number' || typeof reference !== 'number') {
800
- typeError(this, `moveTableCol${after ? 'After' : 'Before'}`, 'Number');
805
+ this.typeError(`moveTableCol${after ? 'After' : 'Before'}`, 'Number');
801
806
  }
802
807
  const layout = this.getLayout(),
803
808
  /** @type {(rowLayout: TableCoords[], i: number, oneCol?: boolean) => boolean} */