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
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('..'),
4
+ Token = require('.'),
5
+ AtomToken = require('./atom');
6
+
7
+ /**
8
+ * 转换flags
9
+ * @classdesc `{childNodes: ...AtomToken}`
10
+ */
11
+ class ConverterFlagsToken extends Token {
12
+ type = 'converter-flags';
13
+ /** @type {string[]} */ #flags;
14
+
15
+ /**
16
+ * @param {string[]} flags
17
+ * @param {accum} accum
18
+ */
19
+ constructor(flags, config = Parser.getConfig(), accum = []) {
20
+ super(undefined, config, false, accum, {AtomToken: ':'});
21
+ this.append(...flags.map(flag => new AtomToken(flag, 'converter-flag', config, accum)));
22
+ }
23
+
24
+ cloneNode() {
25
+ const cloned = this.cloneChildren(),
26
+ token = Parser.run(() => new ConverterFlagsToken([], this.getAttribute('config')));
27
+ token.append(...cloned);
28
+ token.afterBuild();
29
+ return token;
30
+ }
31
+
32
+ /** @complexity `n` */
33
+ afterBuild() {
34
+ this.#flags = this.children.map(child => child.text().trim());
35
+ const that = this,
36
+ /** @type {AstListener} */ converterFlagsListener = ({prevTarget}) => {
37
+ if (prevTarget) {
38
+ that.#flags[that.childNodes.indexOf(prevTarget)] = prevTarget.text().trim();
39
+ }
40
+ };
41
+ this.addEventListener(['remove', 'insert', 'text', 'replace'], converterFlagsListener);
42
+ }
43
+
44
+ /**
45
+ * @template {string} T
46
+ * @param {T} key
47
+ * @returns {TokenAttribute<T>}
48
+ */
49
+ getAttribute(key) {
50
+ if (key === 'flags') {
51
+ return Parser.debugging ? this.#flags : [...this.#flags];
52
+ }
53
+ return super.getAttribute(key);
54
+ }
55
+
56
+ /**
57
+ * @param {number} i
58
+ * @complexity `n`
59
+ */
60
+ removeAt(i) {
61
+ const /** @type {AtomToken} */ token = super.removeAt(i);
62
+ this.#flags?.splice(i, 1);
63
+ return token;
64
+ }
65
+
66
+ /**
67
+ * @param {AtomToken} token
68
+ * @complexity `n`
69
+ */
70
+ insertAt(token, i = this.childNodes.length) {
71
+ super.insertAt(token, i);
72
+ this.#flags?.splice(i, 0, token.text());
73
+ return token;
74
+ }
75
+
76
+ toString() {
77
+ return super.toString(';');
78
+ }
79
+
80
+ getGaps() {
81
+ return 1;
82
+ }
83
+
84
+ text() {
85
+ return super.text(';');
86
+ }
87
+
88
+ /** @returns {[number, string][]} */
89
+ plain() {
90
+ return [];
91
+ }
92
+
93
+ /**
94
+ * @param {string} flag
95
+ * @returns {AtomToken[]}
96
+ * @complexity `n`
97
+ */
98
+ getFlagToken(flag) {
99
+ return this.#flags.includes(flag) ? this.children.filter(child => child.text().trim() === flag) : [];
100
+ }
101
+
102
+ getAllFlags() {
103
+ return new Set(this.#flags);
104
+ }
105
+
106
+ /** @complexity `n` */
107
+ getEffectiveFlags() {
108
+ const {variants} = this.getAttribute('config'),
109
+ variantFlags = this.#flags.filter(flag => variants.includes(flag)),
110
+ unknownFlags = this.#flags.filter(flag => /{{.+}}/.test(flag));
111
+ if (variantFlags.length) {
112
+ return new Set([...variantFlags, ...unknownFlags]);
113
+ }
114
+ const validFlags = ['A', 'T', 'R', 'D', '-', 'H', 'N'],
115
+ flags = new Set([...this.#flags.filter(flag => validFlags.includes(flag)), ...unknownFlags]);
116
+ if (flags.size === 0) {
117
+ return new Set('S');
118
+ } else if (flags.has('R')) {
119
+ return new Set('R');
120
+ } else if (flags.has('N')) {
121
+ return new Set('N');
122
+ } else if (flags.has('-')) {
123
+ return new Set('-');
124
+ } else if (flags.has('H')) {
125
+ const hasT = flags.has('T'),
126
+ hasD = flags.has('D');
127
+ if (hasT && hasD) {
128
+ return new Set(['+', 'H', 'T', 'D']);
129
+ }
130
+ return new Set(['+', 'H', ...hasT ? ['T'] : [], ...hasD ? ['D'] : [], ...unknownFlags]);
131
+ }
132
+ if (flags.size === 1 && flags.has('T')) {
133
+ flags.add('H');
134
+ }
135
+ if (flags.has('A')) {
136
+ flags.add('+');
137
+ flags.add('S');
138
+ }
139
+ if (flags.has('D')) {
140
+ flags.delete('S');
141
+ }
142
+ return flags;
143
+ }
144
+
145
+ /** @complexity `n` */
146
+ getUnknownFlags() {
147
+ return [...this.getFlags()].filter(flag => /{{.+}}/.test(flag));
148
+ }
149
+
150
+ /** @param {string} flag */
151
+ hasFlag(flag) {
152
+ if (typeof flag !== 'string') {
153
+ this.typeError('hasFlag', 'String');
154
+ }
155
+ return this.#flags.includes(flag);
156
+ }
157
+
158
+ /**
159
+ * @param {string} flag
160
+ * @complexity `n`
161
+ */
162
+ hasEffectiveFlag(flag) {
163
+ if (typeof flag !== 'string') {
164
+ this.typeError('hasFlag', 'String');
165
+ }
166
+ return this.getEffectiveFlags().has(flag);
167
+ }
168
+
169
+ /**
170
+ * @param {string} flag
171
+ * @complexity `n²`
172
+ */
173
+ removeFlag(flag) {
174
+ for (const token of this.getFlagToken(flag)) {
175
+ token.remove();
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @param {string} flag
181
+ * @complexity `n`
182
+ */
183
+ #newFlag(flag) {
184
+ const token = Parser.run(() => new AtomToken(flag, 'converter-flag', this.getAttribute('config')));
185
+ this.appendChild(token);
186
+ }
187
+
188
+ /**
189
+ * @param {string} flag
190
+ * @complexity `n`
191
+ */
192
+ setFlag(flag) {
193
+ if (!this.#flags.includes(flag)) {
194
+ this.#newFlag(flag);
195
+ } else if (!this.getEffectiveFlags().has(flag)) {
196
+ Parser.error('此 flag 不会生效', flag);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @param {string} flag
202
+ * @complexity `n²`
203
+ */
204
+ toggleFlag(flag) {
205
+ if (!this.#flags.includes(flag)) {
206
+ this.#newFlag(flag);
207
+ } else {
208
+ this.removeFlag(flag);
209
+ }
210
+ }
211
+ }
212
+
213
+ Parser.classes.ConverterFlagsToken = __filename;
214
+ module.exports = ConverterFlagsToken;
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ const {undo} = require('../util/debug'),
4
+ {noWrap} = require('../util/string'),
5
+ /** @type {Parser} */ Parser = require('..'),
6
+ Token = require('.'),
7
+ AtomToken = require('./atom');
8
+
9
+ /**
10
+ * 转换规则
11
+ * @classdesc `{childNodes: ...AtomToken)}`
12
+ */
13
+ class ConverterRuleToken extends Token {
14
+ type = 'converter-rule';
15
+ variant = '';
16
+ unidirectional = false;
17
+ bidirectional = false;
18
+
19
+ /**
20
+ * @param {string} rule
21
+ * @param {accum} accum
22
+ */
23
+ constructor(rule, hasColon = true, config = Parser.getConfig(), accum = []) {
24
+ super(undefined, config, true, accum, {AtomToken: ':'});
25
+ if (!hasColon) {
26
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
27
+ } else {
28
+ const i = rule.indexOf(':'),
29
+ j = rule.slice(0, i).indexOf('=>'),
30
+ v = (j === -1 ? rule.slice(0, i) : rule.slice(j + 2, i)).trim(),
31
+ {variants} = config;
32
+ if (!variants.includes(v)) {
33
+ super.insertAt(new AtomToken(rule, 'converter-rule-noconvert', config, accum));
34
+ } else {
35
+ super.insertAt(new AtomToken(v, 'converter-rule-variant', config, accum));
36
+ super.insertAt(new AtomToken(rule.slice(i + 1), 'converter-rule-to', config, accum));
37
+ if (j === -1) {
38
+ this.bidirectional = true;
39
+ } else {
40
+ super.insertAt(new AtomToken(rule.slice(0, j), 'converter-rule-from', config, accum), 0);
41
+ this.unidirectional = true;
42
+ }
43
+ }
44
+ }
45
+ this.seal(['variant', 'unidirectional', 'bidirectional']);
46
+ }
47
+
48
+ cloneNode() {
49
+ const cloned = this.cloneChildren(),
50
+ placeholders = ['', ':', '=>:'],
51
+ placeholder = placeholders[cloned.length - 1],
52
+ token = Parser.run(() => new ConverterRuleToken(placeholder, placeholder, this.getAttribute('config')));
53
+ for (let i = 0; i < cloned.length; i++) {
54
+ token.children[i].safeReplaceWith(cloned[i]);
55
+ }
56
+ token.afterBuild();
57
+ return token;
58
+ }
59
+
60
+ afterBuild() {
61
+ if (this.childNodes.length > 1) {
62
+ this.setAttribute('variant', this.children.at(-2).text().trim());
63
+ }
64
+ const that = this,
65
+ /** @type {AstListener} */ converterRuleListener = (e, data) => {
66
+ const {childNodes} = that,
67
+ {prevTarget} = e;
68
+ if (childNodes.length > 1 && childNodes.at(-2) === prevTarget) {
69
+ const v = prevTarget.text().trim(),
70
+ {variants} = that.getAttribute('config');
71
+ if (variants.includes(v)) {
72
+ that.setAttribute('variant', v);
73
+ } else {
74
+ undo(e, data);
75
+ throw new Error(`无效的语言变体:${v}`);
76
+ }
77
+ }
78
+ };
79
+ this.addEventListener(['remove', 'insert', 'text', 'replace'], converterRuleListener);
80
+ }
81
+
82
+ /**
83
+ * @param {number} i
84
+ * @returns {AtomToken}
85
+ */
86
+ removeAt(i) {
87
+ if (i !== 0 && i !== -this.childNodes.length) {
88
+ throw new RangeError(`${this.constructor.name} 禁止移除第 ${i} 个子节点!`);
89
+ }
90
+ return super.removeAt(i);
91
+ }
92
+
93
+ insertAt() {
94
+ throw new Error('转换规则语法复杂,请勿尝试手动插入子节点!');
95
+ }
96
+
97
+ /** @returns {string} */
98
+ toString() {
99
+ if (this.childNodes.length === 3) {
100
+ const [from, variant, to] = this.children;
101
+ return `${from.toString()}=>${variant.toString()}:${to.toString()}`;
102
+ }
103
+ return super.toString(':');
104
+ }
105
+
106
+ /** @param {number} i */
107
+ getGaps(i = 0) {
108
+ const {length} = this.childNodes;
109
+ i = i < 0 ? i + length : i;
110
+ return i === 0 && length === 3 ? 2 : 1;
111
+ }
112
+
113
+ /** @returns {string} */
114
+ text() {
115
+ if (this.childNodes.length === 3) {
116
+ const [from, variant, to] = this.children;
117
+ return `${from.text()}=>${variant.text()}:${to.text()}`;
118
+ }
119
+ return super.text(':');
120
+ }
121
+
122
+ /** @returns {[number, string][]} */
123
+ plain() {
124
+ const {firstElementChild, lastElementChild, unidirectional} = this;
125
+ if (unidirectional) {
126
+ return [...firstElementChild.plain(), ...lastElementChild.plain()];
127
+ }
128
+ return lastElementChild.plain();
129
+ }
130
+
131
+ noConvert() {
132
+ for (let i = this.childNodes.length - 2; i >= 0; i--) {
133
+ super.removeAt(i);
134
+ }
135
+ this.setAttribute('unidirectional', false).setAttribute('bidirectional', false).setAttribute('variant', '');
136
+ }
137
+
138
+ /** @param {string} to */
139
+ setTo(to) {
140
+ to = String(to);
141
+ const config = this.getAttribute('config'),
142
+ root = Parser.parse(`-{|${config.variants[0]}:${to}}-`, this.getAttribute('include'), undefined, config),
143
+ {childNodes: {length}, firstElementChild} = root;
144
+ if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
145
+ || firstElementChild.lastElementChild.childNodes.length !== 2
146
+ ) {
147
+ throw new SyntaxError(`非法的转换目标:${noWrap(to)}`);
148
+ }
149
+ const {lastChild} = firstElementChild.lastElementChild;
150
+ firstElementChild.lastElementChild.removeAt(0);
151
+ this.lastElementChild.safeReplaceWith(lastChild);
152
+ }
153
+
154
+ /** @param {string} variant */
155
+ setVariant(variant) {
156
+ if (typeof variant !== 'string') {
157
+ this.typeError('setVariant', 'String');
158
+ }
159
+ const config = this.getAttribute('config'),
160
+ v = variant.trim();
161
+ if (!config.variants.includes(v)) {
162
+ throw new RangeError(`无效的语言变体:${v}`);
163
+ } else if (this.childNodes.length === 1) {
164
+ super.insertAt(Parser.run(() => new AtomToken(variant, 'converter-rule-variant', config)), 0);
165
+ this.setAttribute('bidirectional', true);
166
+ } else {
167
+ this.children.at(-2).setText(variant);
168
+ }
169
+ this.setAttribute('variant', v);
170
+ }
171
+
172
+ /** @param {string} from */
173
+ setFrom(from) {
174
+ const {variant} = this;
175
+ if (!variant) {
176
+ throw new Error('请先指定语言变体!');
177
+ }
178
+ from = String(from);
179
+ const config = this.getAttribute('config'),
180
+ root = Parser.parse(`-{|${from}=>${variant}:}-`, this.getAttribute('include'), undefined, config),
181
+ {childNodes: {length}, firstElementChild} = root;
182
+ if (length !== 1 || firstElementChild.type !== 'converter' || firstElementChild.childNodes.length !== 2
183
+ || firstElementChild.lastElementChild.childNodes.length !== 3
184
+ ) {
185
+ throw new SyntaxError(`非法的转换原文:${noWrap(from)}`);
186
+ }
187
+ if (this.unidirectional) {
188
+ this.firstElementChild.safeReplaceWith(firstElementChild.lastElementChild.firstChild);
189
+ } else {
190
+ super.insertAt(firstElementChild.lastElementChild.firstChild, 0);
191
+ this.setAttribute('unidirectional', true).setAttribute('bidirectional', false);
192
+ }
193
+ }
194
+
195
+ /** @param {string} from */
196
+ makeUnidirectional(from) {
197
+ this.setFrom(from);
198
+ }
199
+
200
+ makeBidirectional() {
201
+ if (this.unidirectional) {
202
+ super.removeAt(0);
203
+ this.setAttribute('unidirectional', false).setAttribute('bidirectional', true);
204
+ }
205
+ }
206
+ }
207
+
208
+ Parser.classes.ConverterRuleToken = __filename;
209
+ module.exports = ConverterRuleToken;
package/src/extLink.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..'),
3
+ const {noWrap, normalizeSpace} = require('../util/string'),
4
+ /** @type {Parser} */ Parser = require('..'),
4
5
  Token = require('.'),
5
6
  MagicLinkToken = require('./magicLink');
6
7
 
@@ -32,9 +33,9 @@ class ExtLinkToken extends Token {
32
33
  this.appendChild(new MagicLinkToken(url, true, config, accum));
33
34
  this.#space = space;
34
35
  if (text) {
35
- const inner = new Token(text, config, true, accum);
36
+ const inner = new Token(text, config, true, accum, {'Stage-7': ':', ConverterToken: ':'});
36
37
  inner.type = 'ext-link-text';
37
- this.appendChild(inner.setAttribute('stage', 8));
38
+ this.appendChild(inner.setAttribute('stage', Parser.MAX_STAGE - 1));
38
39
  }
39
40
  this.protectChildren(0);
40
41
  }
@@ -48,20 +49,32 @@ class ExtLinkToken extends Token {
48
49
  }
49
50
  }
50
51
 
52
+ #correct() {
53
+ if (!this.#space && this.childNodes.length > 1
54
+ // 都替换成`<`肯定不对,但无妨
55
+ && /^[^[\]<>"{\x00-\x20\x7f\p{Zs}\ufffd]/u.test(this.lastElementChild.text().replace(/&[lg]t;/, '<'))
56
+ ) {
57
+ this.#space = ' ';
58
+ }
59
+ }
60
+
51
61
  toString() {
52
- return `[${this.firstElementChild.toString()}${this.#space}${this.children[1]?.toString() ?? ''}]`;
62
+ this.#correct();
63
+ return `[${this.firstElementChild.toString()}${this.#space}${normalizeSpace(this.children[1])}]`;
53
64
  }
54
65
 
55
66
  getPadding() {
67
+ this.#correct();
56
68
  return 1;
57
69
  }
58
70
 
59
71
  getGaps() {
72
+ this.#correct();
60
73
  return this.#space.length;
61
74
  }
62
75
 
63
76
  text() {
64
- return `[${super.text(' ')}]`;
77
+ return `[${super.text(' ').replaceAll('\n', ' ')}]`;
65
78
  }
66
79
 
67
80
  /**
@@ -69,7 +82,7 @@ class ExtLinkToken extends Token {
69
82
  * @complexity `n`
70
83
  */
71
84
  plain() {
72
- return this.childElementCount === 1 ? [] : this.lastElementChild.plain();
85
+ return this.childNodes.length === 1 ? [] : this.lastElementChild.plain();
73
86
  }
74
87
 
75
88
  /** @this {ExtLinkToken & {firstElementChild: MagicLinkToken}} */
@@ -82,7 +95,7 @@ class ExtLinkToken extends Token {
82
95
  url = String(url);
83
96
  const root = Parser.parse(`[${url}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
84
97
  {childNodes: {length}, firstElementChild} = root;
85
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 1) {
98
+ if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 1) {
86
99
  throw new SyntaxError(`非法的外链目标:${url}`);
87
100
  }
88
101
  const {firstChild} = firstElementChild;
@@ -96,11 +109,11 @@ class ExtLinkToken extends Token {
96
109
  text = String(text);
97
110
  const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
98
111
  {childNodes: {length}, firstElementChild} = root;
99
- if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 2) {
100
- throw new SyntaxError(`非法的外链文字:${text.replaceAll('\n', '\\n')}`);
112
+ if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childNodes.length !== 2) {
113
+ throw new SyntaxError(`非法的外链文字:${noWrap(text)}`);
101
114
  }
102
115
  const {lastChild} = firstElementChild;
103
- if (this.childElementCount === 1) {
116
+ if (this.childNodes.length === 1) {
104
117
  this.appendChild(lastChild);
105
118
  } else {
106
119
  this.lastElementChild.replaceWith(lastChild);
package/src/heading.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../util/debug'),
4
- fixedToken = require('../mixin/fixedToken'),
3
+ const fixedToken = require('../mixin/fixedToken'),
4
+ sol = require('../mixin/sol'),
5
5
  /** @type {Parser} */ Parser = require('..'),
6
6
  Token = require('.');
7
7
 
@@ -9,7 +9,7 @@ const {typeError} = require('../util/debug'),
9
9
  * 章节标题
10
10
  * @classdesc `{childNodes: [Token, HiddenToken]}`
11
11
  */
12
- class HeadingToken extends fixedToken(Token) {
12
+ class HeadingToken extends fixedToken(sol(Token)) {
13
13
  type = 'heading';
14
14
 
15
15
  /**
@@ -38,34 +38,29 @@ class HeadingToken extends fixedToken(Token) {
38
38
  return token;
39
39
  }
40
40
 
41
+ /** @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}} */
41
42
  toString() {
42
- const equals = '='.repeat(Number(this.name)),
43
- {previousVisibleSibling, nextVisibleSibling} = this;
44
- return `${
45
- typeof previousVisibleSibling === 'string' && !previousVisibleSibling.endsWith('\n')
46
- || previousVisibleSibling instanceof Token
47
- ? '\n'
48
- : ''
49
- }${equals}${super.toString(equals)}${
50
- typeof nextVisibleSibling === 'string' && !nextVisibleSibling.startsWith('\n')
51
- || nextVisibleSibling instanceof Token
52
- ? '\n'
53
- : ''
54
- }`;
43
+ const equals = '='.repeat(Number(this.name));
44
+ return `${this.prependNewLine()}${equals}${
45
+ this.firstElementChild.toString()
46
+ }${equals}${this.lastElementChild.toString()}${this.appendNewLine()}`;
55
47
  }
56
48
 
57
49
  getPadding() {
58
- return Number(this.name);
50
+ return super.getPadding() + Number(this.name);
59
51
  }
60
52
 
61
53
  getGaps() {
62
54
  return Number(this.name);
63
55
  }
64
56
 
65
- /** @returns {string} */
57
+ /**
58
+ * @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}}
59
+ * @returns {string}
60
+ */
66
61
  text() {
67
62
  const equals = '='.repeat(Number(this.name));
68
- return `${equals}${this.firstElementChild.text()}${equals}`;
63
+ return `${this.prependNewLine()}${equals}${this.firstElementChild.text()}${equals}${this.appendNewLine()}`;
69
64
  }
70
65
 
71
66
  /** @returns {[number, string][]} */
@@ -76,7 +71,7 @@ class HeadingToken extends fixedToken(Token) {
76
71
  /** @param {number} n */
77
72
  setLevel(n) {
78
73
  if (typeof n !== 'number') {
79
- typeError(this, 'setLevel', 'Number');
74
+ this.typeError('setLevel', 'Number');
80
75
  }
81
76
  n = Math.min(Math.max(n, 1), 6);
82
77
  this.setAttribute('name', String(n)).firstElementChild.setAttribute('name', this.name);
package/src/html.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const fixedToken = require('../mixin/fixedToken'),
3
+ const {noWrap} = require('../util/string'),
4
+ fixedToken = require('../mixin/fixedToken'),
4
5
  attributeParent = require('../mixin/attributeParent'),
5
6
  /** @type {Parser} */ Parser = require('..'),
6
7
  Token = require('.');
@@ -79,7 +80,7 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
79
80
  findMatchingTag() {
80
81
  const {html} = this.getAttribute('config'),
81
82
  {name, parentElement, closing, selfClosing} = this,
82
- string = this.toString().replaceAll('\n', '\\n');
83
+ string = noWrap(this.toString());
83
84
  if (closing && selfClosing) {
84
85
  throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
85
86
  } else if (html[2].includes(name) || selfClosing && html[1].includes(name)) { // 自封闭标签
@@ -134,7 +135,7 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
134
135
  this.selfClosing = false;
135
136
  this.closing = true;
136
137
  } else {
137
- Parser.warn('无法修复无效自封闭标签', this.toString().replaceAll('\n', '\\n'));
138
+ Parser.warn('无法修复无效自封闭标签', noWrap(this.toString()));
138
139
  throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
139
140
  }
140
141
  }
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {typeError} = require('../util/debug'),
4
- {text, extUrlChar} = require('../util/string'),
3
+ const {text, noWrap, extUrlChar} = require('../util/string'),
5
4
  Title = require('../lib/title'),
6
5
  /** @type {Parser} */ Parser = require('..'),
7
6
  Token = require('.');
@@ -32,7 +31,7 @@ class ImageParameterToken extends Token {
32
31
  if (!value) {
33
32
  return this.#noLink;
34
33
  }
35
- const regex = new RegExp(`(?:${config.protocol}|//)${extUrlChar}`, 'ui');
34
+ const regex = new RegExp(`(?:${config.protocol}|//)${extUrlChar}(?=\x00\\d+t\x7f|$)`, 'ui');
36
35
  if (regex.test(value)) {
37
36
  return value;
38
37
  }
@@ -200,13 +199,13 @@ class ImageParameterToken extends Token {
200
199
  setValue(value) {
201
200
  if (this.#isVoid()) {
202
201
  if (typeof value !== 'boolean') {
203
- typeError(this, 'setValue', 'Boolean');
202
+ this.typeError('setValue', 'Boolean');
204
203
  } else if (value === false) {
205
204
  this.remove();
206
205
  }
207
206
  return;
208
207
  } else if (typeof value !== 'string') {
209
- typeError(this, 'setValue', 'String');
208
+ this.typeError('setValue', 'String');
210
209
  }
211
210
  const root = Parser.parse(`[[File:F|${
212
211
  this.#syntax ? this.#syntax.replace('$1', value) : value
@@ -214,9 +213,9 @@ class ImageParameterToken extends Token {
214
213
  {childNodes: {length}, firstElementChild} = root,
215
214
  param = firstElementChild?.lastElementChild;
216
215
  if (length !== 1 || !firstElementChild?.matches('file#File:F')
217
- || firstElementChild.childElementCount !== 2 || param.name !== this.name
216
+ || firstElementChild.childNodes.length !== 2 || param.name !== this.name
218
217
  ) {
219
- throw new SyntaxError(`非法的 ${this.name} 参数:${value.replaceAll('\n', '\\n')}`);
218
+ throw new SyntaxError(`非法的 ${this.name} 参数:${noWrap(value)}`);
220
219
  }
221
220
  this.replaceChildren(...param.childNodes);
222
221
  }