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/nowiki/dd.js CHANGED
@@ -1,41 +1,56 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('../..'),
3
+ const Parser = require('../..'),
4
4
  NowikiToken = require('.');
5
5
 
6
6
  /**
7
7
  * :
8
- * @classdesc `{childNodes: [string]}`
8
+ * @classdesc `{childNodes: [AstText]}`
9
9
  */
10
10
  class DdToken extends NowikiToken {
11
11
  type = 'dd';
12
- dt = false;
13
- ul = false;
14
- ol = false;
15
- indent = 0;
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);
12
+
13
+ /** 是否包含<dt> */
14
+ get dt() {
15
+ return String(this).includes(';');
21
16
  }
22
17
 
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);
18
+ /** 是否包含<ul> */
19
+ get ul() {
20
+ return String(this).includes('*');
21
+ }
22
+
23
+ /** 是否包含<ol> */
24
+ get ol() {
25
+ return String(this).includes('#');
26
+ }
27
+
28
+ /** 缩进数 */
29
+ get indent() {
30
+ return String(this).split(':').length - 1;
31
+ }
32
+
33
+ set indent(indent) {
34
+ if (this.type === 'dd') {
35
+ if (typeof indent !== 'number') {
36
+ this.typeError('set indent', 'Number');
37
+ } else if (!Number.isInteger(indent) || indent < 0) {
38
+ throw new RangeError(`indent 应为自然数!${indent}`);
39
+ }
40
+ this.setText(':'.repeat(indent));
41
+ }
30
42
  }
31
43
 
32
- /** @param {string} str */
44
+ /**
45
+ * @override
46
+ * @param {string} str 新文本
47
+ * @throws `RangeError` 错误的列表语法
48
+ */
33
49
  setText(str) {
34
50
  const src = this.type === 'dd' ? ':' : ';:*#';
35
- if (RegExp(`[^${src}]`).test(str)) {
36
- throw new RangeError(`${this.constructor.name} 仅能包含${src.split('').map(c => `"${c}"`).join('、')}!`);
51
+ if (new RegExp(`[^${src}]`, 'u').test(str)) {
52
+ throw new RangeError(`${this.constructor.name} 仅能包含${[...src].map(c => `"${c}"`).join('、')}!`);
37
53
  }
38
- this.#update(str);
39
54
  return super.setText(str);
40
55
  }
41
56
  }
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  const hidden = require('../../mixin/hidden'),
4
- /** @type {Parser} */ Parser = require('../..'),
4
+ Parser = require('../..'),
5
5
  NowikiToken = require('.');
6
6
 
7
7
  /**
8
8
  * 状态开关
9
- * @classdesc `{childNodes: [string]}`
9
+ * @classdesc `{childNodes: [AstText]}`
10
10
  */
11
11
  class DoubleUnderscoreToken extends hidden(NowikiToken) {
12
12
  type = 'double-underscore';
13
13
 
14
14
  /**
15
- * @param {string} word
15
+ * @param {string} word 状态开关名
16
16
  * @param {accum} accum
17
17
  */
18
18
  constructor(word, config = Parser.getConfig(), accum = []) {
@@ -20,19 +20,33 @@ class DoubleUnderscoreToken extends hidden(NowikiToken) {
20
20
  this.setAttribute('name', word.toLowerCase());
21
21
  }
22
22
 
23
+ /** @override */
23
24
  cloneNode() {
24
- return Parser.run(() => new DoubleUnderscoreToken(this.firstChild, this.getAttribute('config')));
25
+ return Parser.run(() => new DoubleUnderscoreToken(String(this.firstChild), this.getAttribute('config')));
25
26
  }
26
27
 
27
- /** @this {DoubleUnderscoreToken & {firstChild: string}} */
28
- toString() {
29
- return `__${this.firstChild}__`;
28
+ /**
29
+ * @override
30
+ * @param {string} selector
31
+ */
32
+ toString(selector) {
33
+ return selector && this.matches(selector) ? '' : `__${String(this.firstChild)}__`;
30
34
  }
31
35
 
36
+ /** @override */
32
37
  getPadding() {
33
38
  return 2;
34
39
  }
35
40
 
41
+ /** @override */
42
+ print() {
43
+ return super.print({pre: '__', post: '__'});
44
+ }
45
+
46
+ /**
47
+ * @override
48
+ * @throws `Error` 禁止修改
49
+ */
36
50
  setText() {
37
51
  throw new Error(`禁止修改 ${this.constructor.name}!`);
38
52
  }
package/src/nowiki/hr.js CHANGED
@@ -1,32 +1,36 @@
1
1
  'use strict';
2
2
 
3
3
  const sol = require('../../mixin/sol'),
4
- /** @type {Parser} */ Parser = require('../..'),
4
+ Parser = require('../..'),
5
5
  NowikiToken = require('.');
6
6
 
7
7
  /**
8
8
  * `<hr>`
9
- * @classdesc `{childNodes: [string]}`
9
+ * @classdesc `{childNodes: [AstText]}`
10
10
  */
11
11
  class HrToken extends sol(NowikiToken) {
12
12
  type = 'hr';
13
13
 
14
14
  /**
15
- * @param {number} n
15
+ * @param {number} n 字符串长度
16
16
  * @param {accum} accum
17
17
  */
18
18
  constructor(n, config = Parser.getConfig(), accum = []) {
19
19
  super('-'.repeat(n), config, accum);
20
20
  }
21
21
 
22
- /** @this {HrToken & {firstChild: string}} */
22
+ /** @override */
23
23
  cloneNode() {
24
- return Parser.run(() => new HrToken(this.firstChild.length, this.getAttribute('config')));
24
+ return Parser.run(() => new HrToken(String(this).length, this.getAttribute('config')));
25
25
  }
26
26
 
27
- /** @param {string} str */
27
+ /**
28
+ * @override
29
+ * @param {string} str 新文本
30
+ * @throws `RangeError` 错误的\<hr\>语法
31
+ */
28
32
  setText(str) {
29
- if (!/^-{4,}$/.test(str)) {
33
+ if (str.length < 4 || /[^-]/u.test(str)) {
30
34
  throw new RangeError('<hr>总是写作不少于4个的连续"-"!');
31
35
  }
32
36
  return super.setText(str);
@@ -1,33 +1,40 @@
1
1
  'use strict';
2
2
 
3
3
  const fixedToken = require('../../mixin/fixedToken'),
4
- /** @type {Parser} */ Parser = require('../..'),
5
- Token = require('..');
4
+ Parser = require('../..'),
5
+ Token = require('..'),
6
+ AstText = require('../../lib/text');
6
7
 
7
8
  /**
8
9
  * 纯文字Token,不会被解析
9
- * @classdesc `{childNodes: [string]}`
10
+ * @classdesc `{childNodes: [AstText]}`
10
11
  */
11
12
  class NowikiToken extends fixedToken(Token) {
12
13
  type = 'ext-inner';
13
14
 
14
15
  /**
15
- * @param {string} wikitext
16
+ * @param {string} wikitext wikitext
16
17
  * @param {accum} accum
17
18
  */
18
19
  constructor(wikitext, config = Parser.getConfig(), accum = []) {
19
20
  super(wikitext, config, true, accum);
20
21
  }
21
22
 
22
- /** @this {NowikiToken & {firstChild: string}} */
23
+ /**
24
+ * @override
25
+ * @this {NowikiToken & {firstChild: AstText, constructor: typeof NowikiToken}}
26
+ */
23
27
  cloneNode() {
24
- const /** @type {typeof NowikiToken} */ Constructor = this.constructor,
25
- token = Parser.run(() => new Constructor(this.firstChild, this.getAttribute('config')));
26
- token.type = this.type;
28
+ const {constructor, firstChild: {data}, type} = this,
29
+ token = Parser.run(() => new constructor(data, this.getAttribute('config')));
30
+ token.type = type;
27
31
  return token;
28
32
  }
29
33
 
30
- /** @param {string} str */
34
+ /**
35
+ * @override
36
+ * @param {string} str 新文本
37
+ */
31
38
  setText(str) {
32
39
  return super.setText(str, 0);
33
40
  }
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const sol = require('../../mixin/sol'),
4
- /** @type {Parser} */ Parser = require('../..'),
4
+ Parser = require('../..'),
5
5
  DdToken = require('./dd');
6
6
 
7
7
  /**
8
8
  * ;:*#
9
- * @classdesc `{childNodes: [string]}`
9
+ * @classdesc `{childNodes: [AstText]}`
10
10
  */
11
11
  class ListToken extends sol(DdToken) {
12
12
  type = 'list';
@@ -1,19 +1,23 @@
1
1
  'use strict';
2
2
 
3
3
  const hidden = require('../../mixin/hidden'),
4
- /** @type {Parser} */ Parser = require('../..'),
4
+ Parser = require('../..'),
5
5
  NowikiToken = require('.');
6
6
 
7
7
  /**
8
8
  * `<noinclude>`和`</noinclude>`,不可进行任何更改
9
- * @classdesc `{childNodes: [string]}`
9
+ * @classdesc `{childNodes: [AstText]}`
10
10
  */
11
11
  class NoincludeToken extends hidden(NowikiToken) {
12
12
  type = 'noinclude';
13
13
 
14
- /** @param {string} str */
14
+ /**
15
+ * @override
16
+ * @param {string} str 新文本
17
+ * @throws `Error` 不可更改
18
+ */
15
19
  setText(str) {
16
- if (/^<\/?(?:(?:no|only)include|includeonly)(?:\s.*)?\/?>$/is.test(this.toString())) {
20
+ if (/^<\/?(?:(?:no|only)include|includeonly)(?:\s.*)?\/?>$/isu.test(String(this))) {
17
21
  throw new Error(`${this.constructor.name} 不可更改文字内容!`);
18
22
  }
19
23
  return super.setText(str);
@@ -1,17 +1,19 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('../..'),
3
+ const {generateForSelf} = require('../../util/lint'),
4
+ Parser = require('../..'),
5
+ AstText = require('../../lib/text'),
4
6
  NowikiToken = require('.');
5
7
 
6
8
  /**
7
9
  * `<hr>`
8
- * @classdesc `{childNodes: [string]}`
10
+ * @classdesc `{childNodes: [AstText]}`
9
11
  */
10
12
  class QuoteToken extends NowikiToken {
11
13
  type = 'quote';
12
14
 
13
15
  /**
14
- * @param {number} n
16
+ * @param {number} n 字符串长度
15
17
  * @param {accum} accum
16
18
  */
17
19
  constructor(n, config = Parser.getConfig(), accum = []) {
@@ -19,12 +21,41 @@ class QuoteToken extends NowikiToken {
19
21
  this.setAttribute('name', String(n));
20
22
  }
21
23
 
22
- /** @param {string} str */
24
+ /**
25
+ * @override
26
+ * @this {AstText}
27
+ * @param {number} start 起始位置
28
+ */
29
+ lint(start = 0) {
30
+ const {previousSibling, nextSibling} = this,
31
+ message = `孤立的"'"`,
32
+ /** @type {LintError[]} */ errors = [];
33
+ let /** @type {LintError} */ refError;
34
+ if (previousSibling?.type === 'text' && previousSibling.data.at(-1) === "'") {
35
+ refError = generateForSelf(this, this.getRootNode().posFromIndex(start), '');
36
+ const {startLine, startCol} = refError,
37
+ [{length}] = previousSibling.data.match(/(?<!')'+$/u);
38
+ errors.push({message, startLine, startCol: startCol - length, endLine: startLine, endCol: startCol});
39
+ }
40
+ if (nextSibling?.type === 'text' && nextSibling.data[0] === "'") {
41
+ refError ||= generateForSelf(this, this.getRootNode().posFromIndex(start), '');
42
+ const {endLine, endCol} = refError,
43
+ [{length}] = nextSibling.data.match(/^'+/u);
44
+ errors.push({message, startLine: endLine, startCol: endCol, endLine, endCol: endCol + length});
45
+ }
46
+ return errors;
47
+ }
48
+
49
+ /**
50
+ * @override
51
+ * @param {string} str 新文本
52
+ * @throws `RangeError` 错误的单引号语法
53
+ */
23
54
  setText(str) {
24
- if (!["''", "'''", "'''''"].includes(str)) {
25
- throw new RangeError(`${this.constructor.name} 的内部文本只能为连续 2/3/5 个"'"!`);
55
+ if (str === "''" || str === "'''" || str === "'''''") {
56
+ return super.setText(str);
26
57
  }
27
- return super.setText(str);
58
+ throw new RangeError(`${this.constructor.name} 的内部文本只能为连续 2/3/5 个"'"!`);
28
59
  }
29
60
  }
30
61
 
@@ -1,40 +1,57 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..'),
3
+ const Parser = require('..'),
4
4
  Token = require('.');
5
5
 
6
6
  /**
7
7
  * 嵌入时的`<onlyinclude>`
8
- * @classdesc `{childNodes: ...string|Token}`
8
+ * @classdesc `{childNodes: ...AstText|Token}`
9
9
  */
10
10
  class OnlyincludeToken extends Token {
11
11
  type = 'onlyinclude';
12
12
 
13
+ /** 内部wikitext */
14
+ get innerText() {
15
+ return this.text();
16
+ }
17
+
13
18
  /**
14
- * @param {string} inner
19
+ * @param {string} inner 标签内部wikitext
15
20
  * @param {accum} accum
16
21
  */
17
22
  constructor(inner, config = Parser.getConfig(), accum = []) {
18
23
  super(inner, config, true, accum);
19
24
  }
20
25
 
26
+ /** @override */
21
27
  cloneNode() {
22
- const cloned = this.cloneChildren(),
28
+ const cloned = this.cloneChildNodes(),
23
29
  token = Parser.run(() => new OnlyincludeToken(undefined, this.getAttribute('config')));
24
30
  token.append(...cloned);
25
31
  return token;
26
32
  }
27
33
 
28
- toString() {
29
- return `<onlyinclude>${super.toString()}</onlyinclude>`;
34
+ /**
35
+ * @override
36
+ * @param {string} selector
37
+ */
38
+ toString(selector) {
39
+ return selector && this.matches(selector) ? '' : `<onlyinclude>${super.toString(selector)}</onlyinclude>`;
30
40
  }
31
41
 
42
+ /** @override */
32
43
  getPadding() {
33
44
  return 13;
34
45
  }
35
46
 
47
+ /** @override */
48
+ print() {
49
+ return super.print({pre: '&lt;onlyinclude&gt;', post: '&lt;/onlyinclude&gt;'});
50
+ }
51
+
52
+ /** @override */
36
53
  isPlain() {
37
- return this.constructor === OnlyincludeToken;
54
+ return true;
38
55
  }
39
56
  }
40
57
 
package/src/parameter.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const {noWrap} = require('../util/string'),
4
4
  fixedToken = require('../mixin/fixedToken'),
5
- /** @type {Parser} */ Parser = require('..'),
5
+ Parser = require('..'),
6
6
  Token = require('.');
7
7
 
8
8
  /**
@@ -11,18 +11,30 @@ const {noWrap} = require('../util/string'),
11
11
  */
12
12
  class ParameterToken extends fixedToken(Token) {
13
13
  type = 'parameter';
14
- anon;
14
+
15
+ /** 是否是匿名参数 */
16
+ get anon() {
17
+ return this.firstChild.childNodes.length === 0;
18
+ }
19
+
20
+ /** getValue()的getter */
21
+ get value() {
22
+ return this.getValue();
23
+ }
24
+
25
+ set value(value) {
26
+ this.setValue(value);
27
+ }
15
28
 
16
29
  /**
17
- * @param {string|number} key
18
- * @param {string} value
30
+ * @param {string|number} key 参数名
31
+ * @param {string} value 参数值
19
32
  * @param {accum} accum
20
33
  */
21
34
  constructor(key, value, config = Parser.getConfig(), accum = []) {
22
35
  super(undefined, config, true, accum);
23
- this.anon = typeof key === 'number';
24
- const AtomToken = require('./atom'),
25
- keyToken = new AtomToken(this.anon ? undefined : key, 'parameter-key', config, accum, {
36
+ const AtomToken = require('./atom');
37
+ const keyToken = new AtomToken(typeof key === 'number' ? undefined : key, 'parameter-key', config, accum, {
26
38
  'Stage-2': ':', '!HeadingToken': '',
27
39
  }),
28
40
  token = new Token(value, config, true, accum);
@@ -30,20 +42,22 @@ class ParameterToken extends fixedToken(Token) {
30
42
  this.append(keyToken, token.setAttribute('stage', 2));
31
43
  }
32
44
 
45
+ /** @override */
33
46
  cloneNode() {
34
- const [key, value] = this.cloneChildren(),
47
+ const [key, value] = this.cloneChildNodes(),
35
48
  config = this.getAttribute('config');
36
49
  return Parser.run(() => {
37
50
  const token = new ParameterToken(this.anon ? Number(this.name) : undefined, undefined, config);
38
- token.firstElementChild.safeReplaceWith(key);
39
- token.lastElementChild.safeReplaceWith(value);
51
+ token.firstChild.safeReplaceWith(key);
52
+ token.lastChild.safeReplaceWith(value);
40
53
  return token.afterBuild();
41
54
  });
42
55
  }
43
56
 
57
+ /** @override */
44
58
  afterBuild() {
45
59
  if (!this.anon) {
46
- const name = this.firstElementChild.text().trim(),
60
+ const name = this.firstChild.text().trim(),
47
61
  {parentNode} = this;
48
62
  this.setAttribute('name', name);
49
63
  if (parentNode && parentNode instanceof require('./transclude')) {
@@ -51,19 +65,14 @@ class ParameterToken extends fixedToken(Token) {
51
65
  parentNode.getArgs(name, false, false).add(this);
52
66
  }
53
67
  }
54
- const that = this;
55
- /**
56
- * 在AstEventData中记录`oldKey`和`newKey`
57
- * @type {AstListener}
58
- */
59
- const parameterListener = ({prevTarget}, data) => {
60
- if (!that.anon) { // 匿名参数不管怎么变动还是匿名
61
- const {firstElementChild} = that;
62
- if (prevTarget === firstElementChild) {
63
- const newKey = firstElementChild.text().trim();
64
- data.oldKey = that.name;
68
+ const /** @type {AstListener} */ parameterListener = ({prevTarget}, data) => {
69
+ if (!this.anon) { // 匿名参数不管怎么变动还是匿名
70
+ const {firstChild, name} = this;
71
+ if (prevTarget === firstChild) {
72
+ const newKey = firstChild.text().trim();
73
+ data.oldKey = name;
65
74
  data.newKey = newKey;
66
- that.setAttribute('name', newKey);
75
+ this.setAttribute('name', newKey);
67
76
  }
68
77
  }
69
78
  };
@@ -71,22 +80,38 @@ class ParameterToken extends fixedToken(Token) {
71
80
  return this;
72
81
  }
73
82
 
74
- /** @returns {string} */
75
- toString() {
76
- return this.anon ? this.lastElementChild.toString() : super.toString('=');
83
+ /**
84
+ * @override
85
+ * @param {string} selector
86
+ * @returns {string}
87
+ */
88
+ toString(selector) {
89
+ return this.anon && !(selector && this.matches(selector))
90
+ ? this.lastChild.toString(selector)
91
+ : super.toString(selector, '=');
77
92
  }
78
93
 
94
+ /** @override */
79
95
  getGaps() {
80
96
  return this.anon ? 0 : 1;
81
97
  }
82
98
 
83
- /** @returns {string} */
99
+ /** @override */
100
+ print() {
101
+ return super.print({sep: this.anon ? '' : '='});
102
+ }
103
+
104
+ /**
105
+ * @override
106
+ * @returns {string}
107
+ */
84
108
  text() {
85
- return this.anon ? this.lastElementChild.text() : super.text('=');
109
+ return this.anon ? this.lastChild.text() : super.text('=');
86
110
  }
87
111
 
88
112
  /**
89
- * @param {ParameterToken} token
113
+ * @override
114
+ * @param {ParameterToken} token 待替换的节点
90
115
  * @complexity `n`
91
116
  */
92
117
  safeReplaceWith(token) {
@@ -94,65 +119,80 @@ class ParameterToken extends fixedToken(Token) {
94
119
  return this.replaceWith(token);
95
120
  }
96
121
 
122
+ /**
123
+ * 获取参数值
124
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
125
+ */
97
126
  getValue() {
98
- const value = this.lastElementChild.text();
99
- return this.anon && this.parentNode?.matches('template, magic-word#invoke') ? value : value.trim();
127
+ const TranscludeToken = require('./transclude');
128
+ const value = this.lastChild.text();
129
+ return this.anon && this.parentNode?.isTemplate() ? value : value.trim();
100
130
  }
101
131
 
102
- /** @param {string} value */
132
+ /**
133
+ * 设置参数值
134
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
135
+ * @param {string} value 参数值
136
+ * @throws `SyntaxError` 非法的模板参数
137
+ */
103
138
  setValue(value) {
104
139
  value = String(value);
105
- const templateLike = this.parentElement?.matches('template, magic-word#invoke'),
140
+ const TranscludeToken = require('./transclude');
141
+ const templateLike = this.parentNode?.isTemplate(),
106
142
  wikitext = `{{${templateLike ? ':T|' : 'lc:'}${this.anon ? '' : '1='}${value}}}`,
107
143
  root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
108
- {childNodes: {length}, firstElementChild} = root,
109
- /** @type {ParameterToken} */ lastElementChild = firstElementChild?.lastElementChild;
110
- if (length !== 1 || !firstElementChild?.matches(templateLike ? 'template#T' : 'magic-word#lc')
111
- || firstElementChild.childNodes.length !== 2
112
- || lastElementChild.anon !== this.anon || lastElementChild.name !== '1'
144
+ {childNodes: {length}, firstChild: transclude} = root,
145
+ /** @type {Token & {lastChild: ParameterToken}} */
146
+ {lastChild: parameter, type, name, childNodes: {length: transcludeLength}} = transclude,
147
+ targetType = templateLike ? 'template' : 'magic-word',
148
+ targetName = templateLike ? 'T' : 'lc';
149
+ if (length !== 1 || type !== targetType || name !== targetName || transcludeLength !== 2
150
+ || parameter.anon !== this.anon || parameter.name !== '1'
113
151
  ) {
114
152
  throw new SyntaxError(`非法的模板参数:${noWrap(value)}`);
115
153
  }
116
- const newValue = lastElementChild.lastChild;
117
- root.destroy();
118
- firstElementChild.destroy();
119
- lastElementChild.destroy();
120
- this.lastElementChild.safeReplaceWith(newValue);
154
+ const {lastChild} = parameter;
155
+ parameter.destroy(true);
156
+ this.lastChild.safeReplaceWith(lastChild);
121
157
  }
122
158
 
123
- /** @param {string} key */
124
- rename(key, force = false) {
159
+ /**
160
+ * 修改参数名
161
+ * @this {ParameterToken & {parentNode: TranscludeToken}}
162
+ * @param {string} key 新参数名
163
+ * @param {boolean} force 是否无视冲突命名
164
+ * @throws `Error` 仅用于模板参数
165
+ * @throws `SyntaxError` 非法的模板参数名
166
+ * @throws `RangeError` 更名造成重复参数
167
+ */
168
+ rename(key, force) {
125
169
  if (typeof key !== 'string') {
126
170
  this.typeError('rename', 'String');
127
171
  }
172
+ const TranscludeToken = require('./transclude');
128
173
  const {parentNode} = this;
129
174
  // 必须检测是否是TranscludeToken
130
- if (!parentNode || !parentNode.matches('template, magic-word#invoke')
131
- || !(parentNode instanceof require('./transclude'))
132
- ) {
175
+ if (!parentNode?.isTemplate() || !(parentNode instanceof require('./transclude'))) {
133
176
  throw new Error(`${this.constructor.name}.rename 方法仅用于模板参数!`);
134
177
  }
135
178
  const root = Parser.parse(`{{:T|${key}=}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
136
- {childNodes: {length}, firstElementChild} = root;
137
- if (length !== 1 || !firstElementChild?.matches('template#T') || firstElementChild.childNodes.length !== 2) {
179
+ {childNodes: {length}, firstChild: template} = root,
180
+ {type, name, lastChild: parameter, childNodes: {length: templateLength}} = template;
181
+ if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2) {
138
182
  throw new SyntaxError(`非法的模板参数名:${key}`);
139
183
  }
140
- const {lastElementChild} = firstElementChild,
141
- {name} = lastElementChild,
142
- keyToken = lastElementChild.firstChild;
143
- if (this.name === name) {
144
- Parser.warn('未改变实际参数名', name);
145
- } else if (parentNode.hasArg(name)) {
184
+ const {name: parameterName, firstChild} = parameter;
185
+ if (this.name === parameterName) {
186
+ Parser.warn('未改变实际参数名', parameterName);
187
+ } else if (parentNode.hasArg(parameterName)) {
146
188
  if (force) {
147
- Parser.warn('参数更名造成重复参数', name);
189
+ Parser.warn('参数更名造成重复参数', parameterName);
148
190
  } else {
149
- throw new RangeError(`参数更名造成重复参数:${name}`);
191
+ throw new RangeError(`参数更名造成重复参数:${parameterName}`);
150
192
  }
151
193
  }
152
- root.destroy();
153
- firstElementChild.destroy();
154
- lastElementChild.destroy();
155
- this.firstElementChild.safeReplaceWith(keyToken);
194
+ parameter.destroy(true);
195
+ this.firstChild.safeReplaceWith(firstChild);
156
196
  }
157
197
  }
158
198