wikiparser-node 0.4.0 → 0.6.1

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 (87) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +97 -65
  4. package/lib/element.js +159 -302
  5. package/lib/node.js +384 -198
  6. package/lib/ranges.js +3 -4
  7. package/lib/text.js +65 -36
  8. package/lib/title.js +9 -8
  9. package/mixin/fixedToken.js +4 -4
  10. package/mixin/hidden.js +2 -0
  11. package/mixin/sol.js +16 -7
  12. package/package.json +14 -3
  13. package/parser/brackets.js +8 -2
  14. package/parser/commentAndExt.js +1 -1
  15. package/parser/converter.js +1 -1
  16. package/parser/externalLinks.js +2 -2
  17. package/parser/hrAndDoubleUnderscore.js +8 -7
  18. package/parser/links.js +8 -9
  19. package/parser/magicLinks.js +1 -1
  20. package/parser/selector.js +5 -5
  21. package/parser/table.js +18 -16
  22. package/src/arg.js +71 -42
  23. package/src/atom/index.js +7 -5
  24. package/src/attribute.js +102 -64
  25. package/src/charinsert.js +91 -0
  26. package/src/converter.js +34 -15
  27. package/src/converterFlags.js +87 -40
  28. package/src/converterRule.js +59 -53
  29. package/src/extLink.js +45 -37
  30. package/src/gallery.js +71 -16
  31. package/src/hasNowiki/index.js +42 -0
  32. package/src/hasNowiki/pre.js +40 -0
  33. package/src/heading.js +41 -18
  34. package/src/html.js +76 -48
  35. package/src/imageParameter.js +73 -51
  36. package/src/imagemap.js +205 -0
  37. package/src/imagemapLink.js +43 -0
  38. package/src/index.js +243 -138
  39. package/src/link/category.js +10 -14
  40. package/src/link/file.js +112 -56
  41. package/src/link/galleryImage.js +74 -10
  42. package/src/link/index.js +86 -61
  43. package/src/magicLink.js +48 -21
  44. package/src/nested/choose.js +24 -0
  45. package/src/nested/combobox.js +23 -0
  46. package/src/nested/index.js +88 -0
  47. package/src/nested/references.js +23 -0
  48. package/src/nowiki/comment.js +18 -4
  49. package/src/nowiki/dd.js +2 -2
  50. package/src/nowiki/doubleUnderscore.js +16 -11
  51. package/src/nowiki/index.js +12 -0
  52. package/src/nowiki/quote.js +28 -1
  53. package/src/onlyinclude.js +15 -8
  54. package/src/paramTag/index.js +83 -0
  55. package/src/paramTag/inputbox.js +42 -0
  56. package/src/parameter.js +73 -46
  57. package/src/syntax.js +9 -1
  58. package/src/table/index.js +58 -44
  59. package/src/table/td.js +63 -63
  60. package/src/table/tr.js +52 -35
  61. package/src/tagPair/ext.js +60 -43
  62. package/src/tagPair/include.js +11 -1
  63. package/src/tagPair/index.js +29 -20
  64. package/src/transclude.js +214 -166
  65. package/tool/index.js +720 -439
  66. package/util/base.js +17 -0
  67. package/util/debug.js +1 -1
  68. package/{test/util.js → util/diff.js} +15 -19
  69. package/util/lint.js +40 -0
  70. package/util/string.js +37 -20
  71. package/.eslintrc.json +0 -714
  72. package/errors/README +0 -1
  73. package/jsconfig.json +0 -7
  74. package/printed/README +0 -1
  75. package/printed/example.json +0 -120
  76. package/test/api.js +0 -83
  77. package/test/real.js +0 -133
  78. package/test/test.js +0 -28
  79. package/typings/api.d.ts +0 -13
  80. package/typings/array.d.ts +0 -28
  81. package/typings/event.d.ts +0 -24
  82. package/typings/index.d.ts +0 -94
  83. package/typings/node.d.ts +0 -29
  84. package/typings/parser.d.ts +0 -16
  85. package/typings/table.d.ts +0 -14
  86. package/typings/token.d.ts +0 -22
  87. package/typings/tool.d.ts +0 -11
package/src/arg.js CHANGED
@@ -1,16 +1,22 @@
1
1
  'use strict';
2
2
 
3
3
  const {text, noWrap} = require('../util/string'),
4
+ {generateForChild} = require('../util/lint'),
4
5
  Parser = require('..'),
5
6
  Token = require('.');
6
7
 
7
8
  /**
8
9
  * `{{{}}}`包裹的参数
9
- * @classdesc `{childNodes: [AtomToken, Token, ...HiddenToken]}`
10
+ * @classdesc `{childNodes: [AtomToken, ?Token, ...HiddenToken]}`
10
11
  */
11
12
  class ArgToken extends Token {
12
13
  type = 'arg';
13
14
 
15
+ /** default */
16
+ get default() {
17
+ return this.childNodes[1]?.text() ?? false;
18
+ }
19
+
14
20
  /**
15
21
  * @param {string[]} parts 以'|'分隔的各部分
16
22
  * @param {accum} accum
@@ -20,26 +26,67 @@ class ArgToken extends Token {
20
26
  super(undefined, config, true, accum, {AtomToken: 0, Token: 1, HiddenToken: '2:'});
21
27
  for (let i = 0; i < parts.length; i++) {
22
28
  if (i === 0 || i > 1) {
23
- const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden'),
24
- token = new AtomToken(parts[i], i === 0 ? 'arg-name' : undefined, config, accum, {
25
- 'Stage-2': ':', '!HeadingToken': '',
26
- });
27
- this.appendChild(token);
29
+ const AtomToken = i === 0 ? require('./atom') : require('./atom/hidden');
30
+ const token = new AtomToken(parts[i], i === 0 ? 'arg-name' : undefined, config, accum, {
31
+ 'Stage-2': ':', '!HeadingToken': '',
32
+ });
33
+ super.insertAt(token);
28
34
  } else {
29
35
  const token = new Token(parts[i], config, true, accum);
30
36
  token.type = 'arg-default';
31
- this.appendChild(token.setAttribute('stage', 2));
37
+ super.insertAt(token.setAttribute('stage', 2));
32
38
  }
33
39
  }
34
40
  this.getAttribute('protectChildren')(0);
35
41
  }
36
42
 
43
+ /**
44
+ * @override
45
+ * @param {string} selector
46
+ */
47
+ toString(selector) {
48
+ return selector && this.matches(selector) ? '' : `{{{${super.toString(selector, '|')}}}}`;
49
+ }
50
+
51
+ /** @override */
52
+ getPadding() {
53
+ return 3;
54
+ }
55
+
56
+ /** @override */
57
+ getGaps() {
58
+ return 1;
59
+ }
60
+
61
+ /** @override */
62
+ print() {
63
+ return super.print({pre: '{{{', post: '}}}', sep: '|'});
64
+ }
65
+
66
+ /**
67
+ * @override
68
+ * @param {number} start 起始位置
69
+ * @returns {LintError[]}
70
+ */
71
+ lint(start = 0) {
72
+ const {childNodes: [argName, argDefault, ...rest]} = this,
73
+ errors = argName.lint(start + 3);
74
+ if (argDefault) {
75
+ errors.push(...argDefault.lint(start + 4 + String(argName).length));
76
+ }
77
+ if (rest.length > 0) {
78
+ const rect = this.getRootNode().posFromIndex(start);
79
+ errors.push(...rest.map(child => generateForChild(child, rect, '三重括号内的不可见部分')));
80
+ }
81
+ return errors;
82
+ }
83
+
37
84
  /** @override */
38
85
  cloneNode() {
39
86
  const [name, ...cloned] = this.cloneChildNodes();
40
87
  return Parser.run(() => {
41
88
  const token = new ArgToken([''], this.getAttribute('config'));
42
- token.firstElementChild.safeReplaceWith(name);
89
+ token.firstChild.safeReplaceWith(name);
43
90
  token.append(...cloned);
44
91
  return token.afterBuild();
45
92
  });
@@ -47,9 +94,9 @@ class ArgToken extends Token {
47
94
 
48
95
  /** @override */
49
96
  afterBuild() {
50
- this.setAttribute('name', this.firstElementChild.text().trim());
97
+ this.setAttribute('name', this.firstChild.text().trim());
51
98
  const /** @type {AstListener} */ argListener = ({prevTarget}) => {
52
- if (prevTarget === this.firstElementChild) {
99
+ if (prevTarget === this.firstChild) {
53
100
  this.setAttribute('name', prevTarget.text().trim());
54
101
  }
55
102
  };
@@ -57,30 +104,12 @@ class ArgToken extends Token {
57
104
  return this;
58
105
  }
59
106
 
60
- /**
61
- * @override
62
- * @param {string} selector
63
- */
64
- toString(selector) {
65
- return selector && this.matches(selector) ? '' : `{{{${super.toString(selector, '|')}}}}`;
66
- }
67
-
68
- /** @override */
69
- getPadding() {
70
- return 3;
71
- }
72
-
73
- /** @override */
74
- getGaps() {
75
- return 1;
76
- }
77
-
78
107
  /**
79
108
  * @override
80
109
  * @returns {string}
81
110
  */
82
111
  text() {
83
- return `{{{${text(this.children.slice(0, 2), '|')}}}}`;
112
+ return `{{{${text(this.childNodes.slice(0, 2), '|')}}}}`;
84
113
  }
85
114
 
86
115
  /**
@@ -115,7 +144,7 @@ class ArgToken extends Token {
115
144
  */
116
145
  insertAt(token, i = this.childNodes.length) {
117
146
  const j = i < 0 ? i + this.childNodes.length : i;
118
- if (j > 1 && !Parser.running) {
147
+ if (j > 1) {
119
148
  throw new RangeError(`${this.constructor.name} 不可插入多余的子节点!`);
120
149
  }
121
150
  super.insertAt(token, i);
@@ -133,13 +162,13 @@ class ArgToken extends Token {
133
162
  setName(name) {
134
163
  name = String(name);
135
164
  const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
136
- {childNodes: {length}, firstElementChild} = root;
137
- if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childNodes.length !== 1) {
165
+ {length, firstChild: arg} = root;
166
+ if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 1) {
138
167
  throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
139
168
  }
140
- const {firstElementChild: newName} = firstElementChild;
141
- firstElementChild.destroy(true);
142
- this.firstElementChild.safeReplaceWith(newName);
169
+ const {firstChild} = arg;
170
+ arg.destroy(true);
171
+ this.firstChild.safeReplaceWith(firstChild);
143
172
  }
144
173
 
145
174
  /**
@@ -150,17 +179,17 @@ class ArgToken extends Token {
150
179
  setDefault(value) {
151
180
  value = String(value);
152
181
  const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
153
- {childNodes: {length}, firstElementChild} = root;
154
- if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childNodes.length !== 2) {
182
+ {length, firstChild: arg} = root;
183
+ if (length !== 1 || arg.type !== 'arg' || arg.childNodes.length !== 2) {
155
184
  throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
156
185
  }
157
- const {children: [, oldDefault]} = this,
158
- {lastElementChild} = firstElementChild;
159
- firstElementChild.destroy(true);
186
+ const {childNodes: [, oldDefault]} = this,
187
+ {lastChild} = arg;
188
+ arg.destroy(true);
160
189
  if (oldDefault) {
161
- oldDefault.safeReplaceWith(lastElementChild);
190
+ oldDefault.safeReplaceWith(lastChild);
162
191
  } else {
163
- this.appendChild(lastElementChild);
192
+ this.insertAt(lastChild);
164
193
  }
165
194
  }
166
195
  }
package/src/atom/index.js CHANGED
@@ -5,7 +5,7 @@ const Parser = require('../..'),
5
5
 
6
6
  /**
7
7
  * 不会被继续解析的plain Token
8
- * @classdesc `{childNodes: (AstText|Token)[]}`
8
+ * @classdesc `{childNodes: ...AstText|Token}`
9
9
  */
10
10
  class AtomToken extends Token {
11
11
  type = 'plain';
@@ -30,10 +30,12 @@ class AtomToken extends Token {
30
30
  cloneNode() {
31
31
  const cloned = this.cloneChildNodes(),
32
32
  config = this.getAttribute('config'),
33
- acceptable = this.getAttribute('acceptable'),
34
- token = Parser.run(() => new this.constructor(undefined, this.type, config, [], acceptable));
35
- token.append(...cloned);
36
- return token;
33
+ acceptable = this.getAttribute('acceptable');
34
+ return Parser.run(() => {
35
+ const token = new this.constructor(undefined, this.type, config, [], acceptable);
36
+ token.append(...cloned);
37
+ return token;
38
+ });
37
39
  }
38
40
  }
39
41
 
package/src/attribute.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const {externalUse} = require('../util/debug'),
4
+ {generateForSelf} = require('../util/lint'),
4
5
  {toCase, removeComment, normalizeSpace} = require('../util/string'),
5
6
  Parser = require('..'),
6
7
  Token = require('.');
@@ -14,6 +15,7 @@ const stages = {'ext-attr': 0, 'html-attr': 2, 'table-attr': 3};
14
15
  class AttributeToken extends Token {
15
16
  /** @type {Map<string, string|true>} */ #attr = new Map();
16
17
  #sanitized = true;
18
+ #quoteBalance = true;
17
19
 
18
20
  /**
19
21
  * @override
@@ -82,17 +84,26 @@ class AttributeToken extends Token {
82
84
  this.setAttr('id', id);
83
85
  }
84
86
 
87
+ /** #sanitized */
88
+ get sanitized() {
89
+ return this.#sanitized;
90
+ }
91
+
92
+ /** #quoteBalance */
93
+ get quoteBalance() {
94
+ return this.#quoteBalance;
95
+ }
96
+
85
97
  /**
86
98
  * 从`this.#attr`更新`childNodes`
87
99
  * @complexity `n`
88
100
  */
89
101
  #updateFromAttr() {
90
102
  let equal = '=';
91
- const ParameterToken = require('./parameter');
92
- const parent = this.closest('ext, parameter');
93
- if (parent instanceof ParameterToken && parent.anon
94
- && parent.parentNode?.matches('template, magic-word#invoke')
95
- ) {
103
+ const ParameterToken = require('./parameter'),
104
+ TranscludeToken = require('./transclude');
105
+ const /** @type {ParameterToken & {parentNode: TranscludeToken}} */ parent = this.closest('ext, parameter');
106
+ if (parent instanceof ParameterToken && parent.anon && parent.parentNode?.isTemplate()) {
96
107
  equal = '{{=}}';
97
108
  }
98
109
  return [...this.#attr].map(([k, v]) => {
@@ -117,6 +128,7 @@ class AttributeToken extends Token {
117
128
  this.replaceChildren(...token.childNodes, true);
118
129
  });
119
130
  this.#sanitized = true;
131
+ this.#quoteBalance = true;
120
132
  }
121
133
 
122
134
  /**
@@ -146,11 +158,13 @@ class AttributeToken extends Token {
146
158
  */
147
159
  const build = str =>
148
160
  typeof str === 'boolean' || !token ? str : token.getAttribute('buildFromStr')(str).map(String).join('');
149
- for (const [, key,, quoted, unquoted] of string
150
- .matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(?:\2|$)|(\S*)))?/gsu)
161
+ for (const [, key, quoteStart, quoted, quoteEnd, unquoted] of string
162
+ .matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(\2|$)|(\S*)))?/gsu)
151
163
  ) {
152
164
  if (!this.setAttr(build(key), build(quoted ?? unquoted ?? true), true)) {
153
165
  this.#sanitized = false;
166
+ } else if (quoteStart !== quoteEnd) {
167
+ this.#quoteBalance = false;
154
168
  }
155
169
  }
156
170
  }
@@ -164,7 +178,87 @@ class AttributeToken extends Token {
164
178
  constructor(attr, type, name, config = Parser.getConfig(), accum = []) {
165
179
  super(attr, config, true, accum, {[`Stage-${stages[type]}`]: ':'});
166
180
  this.type = type;
167
- this.setAttribute('name', name).#parseAttr();
181
+ this.#parseAttr();
182
+ this.setAttribute('name', name);
183
+ }
184
+
185
+ /**
186
+ * 获取标签属性
187
+ * @template {string|undefined} T
188
+ * @param {T} key 属性键
189
+ * @returns {T extends string ? string|true : Record<string, string|true>}
190
+ */
191
+ getAttr(key) {
192
+ if (key === undefined) {
193
+ return Object.fromEntries(this.#attr);
194
+ }
195
+ return typeof key === 'string' ? this.#attr.get(key.toLowerCase().trim()) : this.typeError('getAttr', 'String');
196
+ }
197
+
198
+ /**
199
+ * 设置标签属性
200
+ * @param {string} key 属性键
201
+ * @param {string|boolean} value 属性值
202
+ * @param {boolean} init 是否是初次解析
203
+ * @complexity `n`
204
+ * @throws `RangeError` 扩展标签属性不能包含">"
205
+ * @throws `RangeError` 无效的属性名
206
+ */
207
+ setAttr(key, value, init) {
208
+ init &&= !externalUse('setAttr');
209
+ if (typeof key !== 'string' || typeof value !== 'string' && typeof value !== 'boolean') {
210
+ this.typeError('setAttr', 'String', 'Boolean');
211
+ } else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
212
+ throw new RangeError('扩展标签属性不能包含 ">"!');
213
+ }
214
+ key = key.toLowerCase().trim();
215
+ const config = this.getAttribute('config'),
216
+ include = this.getAttribute('include'),
217
+ parsedKey = this.type === 'ext-attr' || init
218
+ ? key
219
+ : Parser.run(() => {
220
+ const token = new Token(key, config),
221
+ parseOnce = token.getAttribute('parseOnce');
222
+ parseOnce(0, include);
223
+ return String(parseOnce());
224
+ });
225
+ if (!/^(?:[\w:]|\0\d+[t!~{}+-]\x7F)(?:[\w:.-]|\0\d+[t!~{}+-]\x7F)*$/u.test(parsedKey)) {
226
+ if (init) {
227
+ return false;
228
+ }
229
+ throw new RangeError(`无效的属性名:${key}!`);
230
+ } else if (value === false) {
231
+ this.#attr.delete(key);
232
+ } else {
233
+ this.#attr.set(key, value === true ? true : value.replaceAll(/\s/gu, ' ').trim());
234
+ }
235
+ if (!init) {
236
+ this.sanitize();
237
+ }
238
+ return true;
239
+ }
240
+
241
+ /**
242
+ * @override
243
+ * @this {AttributeToken & {parentNode: HtmlToken}}
244
+ * @param {number} start 起始位置
245
+ */
246
+ lint(start = 0) {
247
+ const HtmlToken = require('./html');
248
+ const errors = super.lint(start);
249
+ let /** @type {{top: number, left: number}} */ rect;
250
+ if (this.type === 'html-attr' && this.parentNode.closing && this.text().trim()) {
251
+ rect = this.getRootNode().posFromIndex(start);
252
+ errors.push(generateForSelf(this, rect, '位于闭合标签的属性'));
253
+ }
254
+ if (!this.#sanitized) {
255
+ rect ||= this.getRootNode().posFromIndex(start);
256
+ errors.push(generateForSelf(this, rect, '包含无效属性'));
257
+ } else if (!this.#quoteBalance) {
258
+ rect ||= this.getRootNode().posFromIndex(start);
259
+ errors.push(generateForSelf(this, rect, '未闭合的引号', 'warning'));
260
+ }
261
+ return errors;
168
262
  }
169
263
 
170
264
  /** @override */
@@ -227,19 +321,6 @@ class AttributeToken extends Token {
227
321
  return typeof key === 'string' ? this.#attr.has(key.toLowerCase().trim()) : this.typeError('hasAttr', 'String');
228
322
  }
229
323
 
230
- /**
231
- * 获取标签属性
232
- * @template {string|undefined} T
233
- * @param {T} key 属性键
234
- * @returns {T extends string ? string|true : Record<string, string|true>}
235
- */
236
- getAttr(key) {
237
- if (key === undefined) {
238
- return Object.fromEntries(this.#attr);
239
- }
240
- return typeof key === 'string' ? this.#attr.get(key.toLowerCase().trim()) : this.typeError('getAttr', 'String');
241
- }
242
-
243
324
  /** 获取全部的标签属性名 */
244
325
  getAttrNames() {
245
326
  return [...this.#attr.keys()];
@@ -250,49 +331,6 @@ class AttributeToken extends Token {
250
331
  return this.getAttrNames().length > 0;
251
332
  }
252
333
 
253
- /**
254
- * 设置标签属性
255
- * @param {string} key 属性键
256
- * @param {string|boolean} value 属性值
257
- * @param {boolean} init 是否是初次解析
258
- * @complexity `n`
259
- * @throws `RangeError` 扩展标签属性不能包含">"
260
- * @throws `RangeError` 无效的属性名
261
- */
262
- setAttr(key, value, init) {
263
- init &&= !externalUse('setAttr');
264
- if (typeof key !== 'string' || typeof value !== 'string' && typeof value !== 'boolean') {
265
- this.typeError('setAttr', 'String', 'Boolean');
266
- } else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
267
- throw new RangeError('扩展标签属性不能包含 ">"!');
268
- }
269
- key = key.toLowerCase().trim();
270
- const config = this.getAttribute('config'),
271
- include = this.getAttribute('include'),
272
- parsedKey = this.type === 'ext-attr' || init
273
- ? key
274
- : Parser.run(() => {
275
- const token = new Token(key, config),
276
- parseOnce = token.getAttribute('parseOnce');
277
- parseOnce(0, include);
278
- return String(parseOnce());
279
- });
280
- if (!/^(?:[\w:]|\0\d+[t!~{}+-]\x7F)(?:[\w:.-]|\0\d+[t!~{}+-]\x7F)*$/u.test(parsedKey)) {
281
- if (init) {
282
- return false;
283
- }
284
- throw new RangeError(`无效的属性名:${key}!`);
285
- } else if (value === false) {
286
- this.#attr.delete(key);
287
- } else {
288
- this.#attr.set(key, value === true ? true : value.replaceAll(/\s/gu, ' ').trim());
289
- }
290
- if (!init) {
291
- this.sanitize();
292
- }
293
- return true;
294
- }
295
-
296
334
  /**
297
335
  * 移除标签属性
298
336
  * @param {string} key 属性键
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ Token = require('.'),
5
+ HasNowikiToken = require('./hasNowiki');
6
+
7
+ /**
8
+ * `<charinsert>`
9
+ * @classdesc `{childNodes: [...HasNowikiToken]}`
10
+ */
11
+ class CharinsertToken extends Token {
12
+ type = 'ext-inner';
13
+ name = 'charinsert';
14
+
15
+ /**
16
+ * @param {string} wikitext wikitext
17
+ * @param {accum} accum
18
+ */
19
+ constructor(wikitext, config = Parser.getConfig(), accum = []) {
20
+ super(undefined, config, true, accum, {HasNowikiToken: ':'});
21
+ this.append(...wikitext.split('\n').map(str => new HasNowikiToken(str, 'charinsert-line', config, accum)));
22
+ }
23
+
24
+ /**
25
+ * @override
26
+ * @param {string} selector
27
+ */
28
+ toString(selector) {
29
+ return super.toString(selector, '\n');
30
+ }
31
+
32
+ /** @override */
33
+ getGaps() {
34
+ return 1;
35
+ }
36
+
37
+ /** @override */
38
+ print() {
39
+ return super.print({sep: '\n'});
40
+ }
41
+
42
+ /** @override */
43
+ cloneNode() {
44
+ const cloned = this.cloneChildNodes();
45
+ return Parser.run(() => {
46
+ const token = new CharinsertToken(undefined, this.getAttribute('config'));
47
+ token.append(...cloned);
48
+ return token;
49
+ });
50
+ }
51
+
52
+ /**
53
+ * 获取某一行的插入选项
54
+ * @param {number|HasNowikiToken} child 行号或子节点
55
+ */
56
+ getLineItems(child) {
57
+ if (Number.isInteger(child)) {
58
+ child = this.childNodes.at(child);
59
+ }
60
+ if (!(child instanceof HasNowikiToken)) {
61
+ this.typeError('getLineItems', 'Number', 'HasNowikiToken');
62
+ }
63
+ const entities = {'\t': '&#9;', '\r': '&#12;', ' ': '&#32;'};
64
+ return String(child).replaceAll(
65
+ /<nowiki>(.+?)<\/nowiki>/giu,
66
+ /** @type {function(...string): string} */ (_, inner) => inner.replaceAll(/[\t\r ]/gu, s => entities[s]),
67
+ ).split(/\s/u).filter(Boolean)
68
+ .map(item => {
69
+ const parts = item.split('+');
70
+ if (parts.length === 1) {
71
+ return parts[0];
72
+ }
73
+ return parts[0] ? parts.slice(0, 2) : '+';
74
+ });
75
+ }
76
+
77
+ /** 获取所有插入选项 */
78
+ getAllItems() {
79
+ return this.childNodes.flatMap(child => this.getLineItems(child));
80
+ }
81
+
82
+ /** @override */
83
+ text() {
84
+ return this.childNodes.map(
85
+ child => this.getLineItems(child).map(str => Array.isArray(str) ? str.join('+') : str).join(' '),
86
+ ).join('\n');
87
+ }
88
+ }
89
+
90
+ Parser.classes.CharinsertToken = __filename;
91
+ module.exports = CharinsertToken;
package/src/converter.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const {text} = require('../util/string'),
3
+ const {text, print} = require('../util/string'),
4
4
  Parser = require('..'),
5
5
  Token = require('.'),
6
6
  ConverterFlagsToken = require('./converterFlags'),
@@ -21,6 +21,11 @@ class ConverterToken extends Token {
21
21
  return this.hasFlag('R') || this.childNodes.length === 2 && !this.lastChild.variant;
22
22
  }
23
23
 
24
+ /** flags */
25
+ get flags() {
26
+ return this.getAllFlags();
27
+ }
28
+
24
29
  /**
25
30
  * @param {string[]} flags 转换类型标记
26
31
  * @param {string[]} rules 转换规则
@@ -33,7 +38,7 @@ class ConverterToken extends Token {
33
38
  hasColon = firstRule.includes(':'),
34
39
  firstRuleToken = new ConverterRuleToken(firstRule, hasColon, config, accum);
35
40
  if (hasColon && firstRuleToken.childNodes.length === 1) {
36
- this.appendChild(new ConverterRuleToken(rules.join(';'), false, config, accum));
41
+ this.insertAt(new ConverterRuleToken(rules.join(';'), false, config, accum));
37
42
  } else {
38
43
  this.append(
39
44
  firstRuleToken,
@@ -43,21 +48,13 @@ class ConverterToken extends Token {
43
48
  this.getAttribute('protectChildren')(0);
44
49
  }
45
50
 
46
- /** @override */
47
- cloneNode() {
48
- const [flags, ...rules] = this.cloneChildNodes(),
49
- token = Parser.run(() => new ConverterToken([], [], this.getAttribute('config')));
50
- token.firstElementChild.safeReplaceWith(flags);
51
- token.append(...rules);
52
- return token;
53
- }
54
-
55
51
  /**
56
52
  * @override
57
53
  * @param {string} selector
54
+ * @returns {string}
58
55
  */
59
56
  toString(selector) {
60
- const {children: [flags, ...rules]} = this;
57
+ const {childNodes: [flags, ...rules]} = this;
61
58
  return selector && this.matches(selector)
62
59
  ? ''
63
60
  : `-{${flags.toString(selector)}${flags.childNodes.length > 0 ? '|' : ''}${rules.map(String).join(';')}}-`;
@@ -69,17 +66,39 @@ class ConverterToken extends Token {
69
66
  }
70
67
 
71
68
  /**
72
- * /** @override
69
+ * @override
73
70
  * @param {number} i 子节点位置
74
71
  */
75
72
  getGaps(i = 0) {
76
73
  i = i < 0 ? i + this.childNodes.length : i;
77
- return i || this.firstElementChild.childNodes.length > 0 ? 1 : 0;
74
+ return i || this.firstChild.childNodes.length > 0 ? 1 : 0;
78
75
  }
79
76
 
80
77
  /** @override */
81
- text() {
78
+ print() {
82
79
  const {children: [flags, ...rules]} = this;
80
+ return `<span class="wpb-converter">-{${flags.print()}${
81
+ flags.childNodes.length > 0 ? '|' : ''
82
+ }${print(rules, {sep: ';'})}}-</span>`;
83
+ }
84
+
85
+ /** @override */
86
+ cloneNode() {
87
+ const [flags, ...rules] = this.cloneChildNodes();
88
+ return Parser.run(() => {
89
+ const token = new ConverterToken([], [], this.getAttribute('config'));
90
+ token.firstChild.safeReplaceWith(flags);
91
+ token.append(...rules);
92
+ return token;
93
+ });
94
+ }
95
+
96
+ /**
97
+ * @override
98
+ * @returns {string}
99
+ */
100
+ text() {
101
+ const {childNodes: [flags, ...rules]} = this;
83
102
  return `-{${flags.text()}|${text(rules, ';')}}-`;
84
103
  }
85
104