wikiparser-node 0.8.1-m → 0.9.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 (81) hide show
  1. package/README.md +39 -0
  2. package/config/moegirl.json +1 -0
  3. package/i18n/zh-hans.json +44 -0
  4. package/i18n/zh-hant.json +44 -0
  5. package/index.js +264 -10
  6. package/lib/element.js +507 -33
  7. package/lib/node.js +550 -6
  8. package/lib/ranges.js +130 -0
  9. package/lib/text.js +111 -41
  10. package/lib/title.js +28 -5
  11. package/mixin/attributeParent.js +117 -0
  12. package/mixin/fixedToken.js +40 -0
  13. package/mixin/hidden.js +3 -0
  14. package/mixin/singleLine.js +31 -0
  15. package/mixin/sol.js +54 -0
  16. package/package.json +9 -8
  17. package/parser/brackets.js +9 -2
  18. package/parser/commentAndExt.js +3 -5
  19. package/parser/converter.js +1 -0
  20. package/parser/externalLinks.js +1 -0
  21. package/parser/hrAndDoubleUnderscore.js +1 -0
  22. package/parser/html.js +1 -0
  23. package/parser/links.js +6 -5
  24. package/parser/list.js +1 -0
  25. package/parser/magicLinks.js +5 -4
  26. package/parser/quotes.js +1 -0
  27. package/parser/selector.js +177 -0
  28. package/parser/table.js +1 -0
  29. package/src/arg.js +123 -5
  30. package/src/atom/hidden.js +2 -0
  31. package/src/atom/index.js +17 -0
  32. package/src/attribute.js +191 -8
  33. package/src/attributes.js +311 -8
  34. package/src/charinsert.js +97 -0
  35. package/src/converter.js +108 -2
  36. package/src/converterFlags.js +190 -3
  37. package/src/converterRule.js +185 -4
  38. package/src/extLink.js +122 -2
  39. package/src/gallery.js +59 -11
  40. package/src/hasNowiki/index.js +12 -0
  41. package/src/hasNowiki/pre.js +12 -0
  42. package/src/heading.js +57 -6
  43. package/src/html.js +133 -12
  44. package/src/imageParameter.js +232 -38
  45. package/src/imagemap.js +65 -6
  46. package/src/imagemapLink.js +14 -2
  47. package/src/index.js +537 -8
  48. package/src/link/category.js +32 -1
  49. package/src/link/file.js +173 -11
  50. package/src/link/galleryImage.js +63 -5
  51. package/src/link/index.js +268 -9
  52. package/src/magicLink.js +92 -11
  53. package/src/nested/choose.js +1 -0
  54. package/src/nested/combobox.js +1 -0
  55. package/src/nested/index.js +31 -7
  56. package/src/nested/references.js +1 -0
  57. package/src/nowiki/comment.js +27 -3
  58. package/src/nowiki/dd.js +47 -1
  59. package/src/nowiki/doubleUnderscore.js +31 -1
  60. package/src/nowiki/hr.js +20 -1
  61. package/src/nowiki/index.js +25 -3
  62. package/src/nowiki/list.js +5 -2
  63. package/src/nowiki/noinclude.js +14 -0
  64. package/src/nowiki/quote.js +17 -3
  65. package/src/onlyinclude.js +26 -1
  66. package/src/paramTag/index.js +27 -4
  67. package/src/paramTag/inputbox.js +4 -1
  68. package/src/parameter.js +150 -8
  69. package/src/syntax.js +68 -0
  70. package/src/table/index.js +941 -4
  71. package/src/table/td.js +229 -10
  72. package/src/table/tr.js +249 -4
  73. package/src/tagPair/ext.js +36 -9
  74. package/src/tagPair/include.js +24 -0
  75. package/src/tagPair/index.js +51 -2
  76. package/src/transclude.js +547 -29
  77. package/tool/index.js +1202 -0
  78. package/util/debug.js +73 -0
  79. package/util/lint.js +8 -7
  80. package/util/string.js +67 -1
  81. package/config/minimum.json +0 -142
package/lib/ranges.js ADDED
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..');
4
+
5
+ /** 模拟Python的Range对象。除`step`至少为`1`外,允许负数、小数或`end < start`的情形。 */
6
+ class Range {
7
+ start;
8
+ end;
9
+ step;
10
+
11
+ /**
12
+ * @param {string|Range} str 表达式
13
+ * @throws `RangeError` 起点、终点和步长均应为整数
14
+ * @throws `RangeError` n的系数不能为0
15
+ * @throws `RangeError` 应使用CSS选择器或Python切片的格式
16
+ */
17
+ constructor(str) {
18
+ if (str instanceof Range) {
19
+ Object.assign(this, str);
20
+ return;
21
+ }
22
+ str = str.trim();
23
+ if (str === 'odd') {
24
+ Object.assign(this, {start: 1, end: Infinity, step: 2});
25
+ } else if (str === 'even') {
26
+ Object.assign(this, {start: 0, end: Infinity, step: 2});
27
+ } else if (str.includes(':')) {
28
+ const [start, end, step = '1'] = str.split(':', 3);
29
+ this.start = Number(start);
30
+ this.end = Number(end || Infinity);
31
+ this.step = Math.max(Number(step), 1);
32
+ if (!Number.isInteger(this.start)) {
33
+ throw new RangeError(`起点 ${this.start} 应为整数!`);
34
+ } else if (this.end !== Infinity && !Number.isInteger(this.end)) {
35
+ throw new RangeError(`终点 ${this.end} 应为整数!`);
36
+ } else if (!Number.isInteger(this.step)) {
37
+ throw new RangeError(`步长 ${this.step} 应为整数!`);
38
+ }
39
+ } else {
40
+ const mt = /^([+-])?(\d+)?n(?:([+-])(\d+))?$/u.exec(str);
41
+ if (mt) {
42
+ const [, sgnA = '+', a = 1, sgnB = '+'] = mt,
43
+ b = Number(mt[4] ?? 0);
44
+ this.step = Number(a);
45
+ if (this.step === 0) {
46
+ throw new RangeError(`参数 ${str} 中 "n" 的系数不允许为 0!`);
47
+ } else if (sgnA === '+') {
48
+ this.start = sgnB === '+' || b === 0 ? b : this.step - 1 - (b - 1) % this.step;
49
+ this.end = Infinity;
50
+ } else if (sgnB === '-') {
51
+ this.start = 0;
52
+ this.end = b > 0 ? 0 : this.step;
53
+ } else {
54
+ this.start = b % this.step;
55
+ this.end = this.step + b;
56
+ }
57
+ } else {
58
+ throw new RangeError(`参数 ${str} 应写作CSS选择器的 "an+b" 形式或Python切片!`);
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 将Range转换为针对特定数组的下标集
65
+ * @param {number|*[]} arr 参考数组
66
+ * @complexity `n`
67
+ */
68
+ applyTo(arr) {
69
+ return new Array(typeof arr === 'number' ? arr : arr.length).fill().map((_, i) => i).slice(this.start, this.end)
70
+ .filter((_, j) => j % this.step === 0);
71
+ }
72
+ }
73
+
74
+ /** @extends {Array<number|Range>} */
75
+ class Ranges extends Array {
76
+ /** @param {number|string|Range|(number|string|Range)[]} arr 表达式数组 */
77
+ constructor(arr) {
78
+ super();
79
+ if (arr === undefined) {
80
+ return;
81
+ }
82
+ arr = Array.isArray(arr) ? arr : [arr];
83
+ for (const ele of arr) {
84
+ if (ele instanceof Range) {
85
+ this.push(new Range(ele));
86
+ continue;
87
+ }
88
+ const number = Number(ele);
89
+ if (Number.isInteger(number)) {
90
+ this.push(number);
91
+ } else if (typeof ele === 'string' && Number.isNaN(number)) {
92
+ try {
93
+ const range = new Range(ele);
94
+ this.push(range);
95
+ } catch {}
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 将Ranges转换为针对特定Array的下标集
102
+ * @param {number|*[]} arr 参考数组
103
+ * @complexity `n`
104
+ */
105
+ applyTo(arr) {
106
+ const length = typeof arr === 'number' ? arr : arr.length;
107
+ return [
108
+ ...new Set(
109
+ [...this].flatMap(ele => {
110
+ if (typeof ele === 'number') {
111
+ return ele < 0 ? ele + length : ele;
112
+ }
113
+ return ele.applyTo(length);
114
+ }),
115
+ ),
116
+ ].filter(i => i >= 0 && i < length).sort();
117
+ }
118
+
119
+ /**
120
+ * 检查某个下标是否符合表达式
121
+ * @param {string} str 表达式
122
+ * @param {number} i 待检查的下标
123
+ */
124
+ static nth(str, i) {
125
+ return new Ranges(str.split(',')).applyTo(i + 1).includes(i);
126
+ }
127
+ }
128
+
129
+ Parser.classes.Ranges = __filename;
130
+ module.exports = Ranges;
package/lib/text.js CHANGED
@@ -4,8 +4,8 @@ const Parser = require('..'),
4
4
  AstNode = require('./node'),
5
5
  AstElement = require('./element');
6
6
 
7
- const errorSyntax = /https?:|\{+|\}+|\[{2,}|\[(?![^[]*\])|((?:^|\])[^[]*?)\]+|<\s*\/?([a-z]\w*)(?=[\s/>])/giu,
8
- errorSyntaxUrl = /\{+|\}+|\[{2,}|\[(?![^[]*\])|((?:^|\])[^[]*?)\]+|<\s*\/?([a-z]\w*)(?=[\s/>])/giu,
7
+ const errorSyntax = /https?:\/\/|\{+|\}+|\[{2,}|\[(?![^[]*\])|(?<=^|\])([^[]*?)\]+|<\s*\/?([a-z]\w*)(?=[\s/>])/giu,
8
+ errorSyntaxUrl = /\{+|\}+|\[{2,}|\[(?![^[]*\])|(?<=^|\])([^[]*?)\]+|<\s*\/?([a-z]\w*)(?=[\s/>])/giu,
9
9
  disallowedTags = [
10
10
  'html',
11
11
  'base',
@@ -61,15 +61,22 @@ const errorSyntax = /https?:|\{+|\}+|\[{2,}|\[(?![^[]*\])|((?:^|\])[^[]*?)\]+|<\
61
61
 
62
62
  /** 文本节点 */
63
63
  class AstText extends AstNode {
64
- #config;
65
64
  type = 'text';
66
65
  /** @type {string} */ data;
67
66
 
67
+ /** 文本长度 */
68
+ get length() {
69
+ return this.data.length;
70
+ }
71
+
68
72
  /** @param {string} text 包含文本 */
69
- constructor(text = '', config = Parser.getConfig()) {
73
+ constructor(text = '') {
70
74
  super();
71
- this.#config = config;
72
- this.data = text;
75
+ Object.defineProperties(this, {
76
+ data: {value: text, writable: false},
77
+ childNodes: {enumerable: false, configurable: false},
78
+ type: {enumerable: false, writable: false, configurable: false},
79
+ });
73
80
  }
74
81
 
75
82
  /** 输出字符串 */
@@ -88,24 +95,21 @@ class AstText extends AstNode {
88
95
  * @param {number} start 起始位置
89
96
  * @returns {LintError[]}
90
97
  */
91
- lint(start = 0) {
98
+ lint(start = this.getAbsoluteIndex()) {
92
99
  const {data, parentNode} = this,
93
100
  type = parentNode?.type,
94
101
  name = parentNode?.name,
95
- urlAttr = ['itemtype', 'src', 'srcset'].includes(parentNode?.parentNode?.name),
96
102
  errorRegex
97
103
  = type === 'free-ext-link' || type === 'ext-link-url' || type === 'image-parameter' && name === 'link'
98
104
  ? errorSyntaxUrl
99
105
  : errorSyntax,
100
- errors = [];
101
- if (data.search(errorRegex) !== -1) {
102
- errorRegex.lastIndex = 0;
106
+ errors = [...data.matchAll(errorRegex)],
107
+ {ext, html} = this.getRootNode().getAttribute('config');
108
+ if (errors.length > 0) {
103
109
  const root = this.getRootNode(),
104
110
  {top, left} = root.posFromIndex(start),
105
- tags = new Set([this.#config.ext, this.#config.html, disallowedTags].flat(2));
106
- for (let mt = errorRegex.exec(data); mt; mt = errorRegex.exec(data)) {
107
- const [, prefix, tag] = mt;
108
- let {0: error, index} = mt;
111
+ tags = new Set([ext, html, disallowedTags].flat(2));
112
+ return errors.map(/** @returns {LintError} */ ({0: error, 1: prefix, 2: tag, index}) => {
109
113
  if (prefix) {
110
114
  index += prefix.length;
111
115
  error = error.slice(prefix.length);
@@ -118,29 +122,9 @@ class AstText extends AstNode {
118
122
  {0: char, length} = error,
119
123
  endIndex = startIndex + length,
120
124
  end = char === '}' || char === ']' ? endIndex : startIndex + (char === 'h' ? 49 : 50);
121
- let severity = length > 1 ? 'error' : 'warning';
122
- if (char === 'h') {
123
- switch (type) {
124
- case 'ext-link-text':
125
- severity = 'warning';
126
- break;
127
- case 'attr-value':
128
- if (urlAttr) {
129
- continue;
130
- }
131
- break;
132
- case 'ext-inner':
133
- if (name === 'sm2' || name === 'flashmp3') {
134
- continue;
135
- }
136
- // no default
137
- }
138
- } else if (char === '<' && !tags.has(tag.toLowerCase())) {
139
- continue;
140
- }
141
- errors.push({
142
- message: `孤立的"${char === 'h' ? error : char}"`,
143
- severity,
125
+ return (char !== 'h' || index > 0) && (char !== '<' || tags.has(tag.toLowerCase())) && {
126
+ message: Parser.msg('lonely "$1"', char === 'h' ? error : char),
127
+ severity: length > 1 ? 'error' : 'warning',
144
128
  startIndex,
145
129
  endIndex,
146
130
  startLine,
@@ -148,10 +132,10 @@ class AstText extends AstNode {
148
132
  startCol,
149
133
  endCol: startCol + length,
150
134
  excerpt: String(root).slice(Math.max(0, end - 50), end),
151
- });
152
- }
135
+ };
136
+ }).filter(Boolean);
153
137
  }
154
- return errors;
138
+ return [];
155
139
  }
156
140
 
157
141
  /**
@@ -160,7 +144,12 @@ class AstText extends AstNode {
160
144
  */
161
145
  #setData(text) {
162
146
  text = String(text);
147
+ const {data} = this,
148
+ e = new Event('text', {bubbles: true});
163
149
  this.setAttribute('data', text);
150
+ if (data !== text) {
151
+ this.dispatchEvent(e, {oldText: data, newText: text});
152
+ }
164
153
  }
165
154
 
166
155
  /**
@@ -170,6 +159,87 @@ class AstText extends AstNode {
170
159
  replaceData(text = '') {
171
160
  this.#setData(text);
172
161
  }
162
+
163
+ /** 复制 */
164
+ cloneNode() {
165
+ return new AstText(this.data);
166
+ }
167
+
168
+ /**
169
+ * @override
170
+ * @template {string} T
171
+ * @param {T} key 属性键
172
+ * @returns {TokenAttribute<T>}
173
+ * @throws `Error` 文本节点没有子节点
174
+ */
175
+ getAttribute(key) {
176
+ return key === 'verifyChild'
177
+ ? () => {
178
+ throw new Error('文本节点没有子节点!');
179
+ }
180
+ : super.getAttribute(key);
181
+ }
182
+
183
+ /**
184
+ * 在后方添加字符串
185
+ * @param {string} text 添加的字符串
186
+ */
187
+ appendData(text) {
188
+ this.#setData(this.data + text);
189
+ }
190
+
191
+ /**
192
+ * 删减字符串
193
+ * @param {number} offset 起始位置
194
+ * @param {number} count 删减字符数
195
+ */
196
+ deleteData(offset, count) {
197
+ this.#setData(this.data.slice(0, offset) + this.data.slice(offset + count));
198
+ }
199
+
200
+ /**
201
+ * 插入字符串
202
+ * @param {number} offset 插入位置
203
+ * @param {string} text 待插入的字符串
204
+ */
205
+ insertData(offset, text) {
206
+ this.#setData(this.data.slice(0, offset) + text + this.data.slice(offset));
207
+ }
208
+
209
+ /**
210
+ * 提取子串
211
+ * @param {number} offset 起始位置
212
+ * @param {number} count 字符数
213
+ */
214
+ substringData(offset, count) {
215
+ return this.data.slice(offset, offset + count);
216
+ }
217
+
218
+ /**
219
+ * 将文本子节点分裂为两部分
220
+ * @param {number} offset 分裂位置
221
+ * @throws `RangeError` 错误的断开位置
222
+ * @throws `Error` 没有父节点
223
+ */
224
+ splitText(offset) {
225
+ if (!Number.isInteger(offset)) {
226
+ this.typeError('splitText', 'Number');
227
+ } else if (offset > this.length || offset < -this.length) {
228
+ throw new RangeError(`错误的断开位置!${offset}`);
229
+ }
230
+ const {parentNode, data} = this;
231
+ if (!parentNode) {
232
+ throw new Error('待分裂的文本节点没有父节点!');
233
+ }
234
+ const newText = new AstText(data.slice(offset)),
235
+ childNodes = [...parentNode.childNodes];
236
+ this.setAttribute('data', data.slice(0, offset));
237
+ childNodes.splice(childNodes.indexOf(this) + 1, 0, newText);
238
+ newText.setAttribute('parentNode', parentNode);
239
+ parentNode.setAttribute('childNodes', childNodes);
240
+ return newText;
241
+ }
173
242
  }
174
243
 
244
+ Parser.classes.AstText = __filename;
175
245
  module.exports = AstText;
package/lib/title.js CHANGED
@@ -1,13 +1,17 @@
1
1
  'use strict';
2
2
 
3
- const Parser = require('..');
3
+ const {decodeHtml} = require('../util/string'),
4
+ Parser = require('..');
4
5
 
5
6
  /** MediaWiki页面标题对象 */
6
7
  class Title {
7
8
  valid = true;
8
9
  ns = 0;
9
- fragment = '';
10
+ fragment;
10
11
  encoded = false;
12
+ title = '';
13
+ main = '';
14
+ prefix = '';
11
15
  interwiki = '';
12
16
 
13
17
  /**
@@ -19,9 +23,10 @@ class Title {
19
23
  constructor(title, defaultNs = 0, config = Parser.getConfig(), decode = false, selfLink = false) {
20
24
  const {namespaces, nsid} = config;
21
25
  let namespace = namespaces[defaultNs];
26
+ title = decodeHtml(title);
22
27
  if (decode && title.includes('%')) {
23
28
  try {
24
- const encoded = /%(?!5[bd]|7[b-d])[\da-f]{2}/iu.test(title);
29
+ const encoded = /%(?!21|3[ce]|5[bd]|7[b-d])[\da-f]{2}/iu.test(title);
25
30
  title = decodeURIComponent(title);
26
31
  this.encoded = encoded;
27
32
  } catch {}
@@ -46,15 +51,33 @@ class Title {
46
51
  }
47
52
  this.ns = nsid[namespace.toLowerCase()];
48
53
  const i = title.indexOf('#');
49
- let fragment = '';
54
+ let fragment;
50
55
  if (i !== -1) {
51
56
  fragment = title.slice(i + 1).trimEnd();
57
+ if (fragment.includes('%')) {
58
+ try {
59
+ fragment = decodeURIComponent(fragment);
60
+ } catch {}
61
+ } else if (fragment.includes('.')) {
62
+ try {
63
+ fragment = decodeURIComponent(fragment.replaceAll('.', '%'));
64
+ } catch {}
65
+ }
52
66
  title = title.slice(0, i).trim();
53
67
  }
54
- this.valid = Boolean(title || selfLink && fragment || this.interwiki)
68
+ this.valid = Boolean(title || selfLink && fragment !== undefined || this.interwiki)
55
69
  && !/\0\d+[eh!+-]\x7F|[<>[\]{}|]|%[\da-f]{2}/iu.test(title);
56
70
  this.fragment = fragment;
71
+ this.main = title && `${title[0].toUpperCase()}${title.slice(1)}`;
72
+ this.prefix = `${namespace}${namespace && ':'}`;
73
+ this.title = `${iw ? `${this.interwiki}:` : ''}${this.prefix}${this.main.replaceAll(' ', '_')}`;
74
+ }
75
+
76
+ /** @override */
77
+ toString() {
78
+ return `${this.title}${this.fragment === undefined ? '' : `#${this.fragment}`}`;
57
79
  }
58
80
  }
59
81
 
82
+ Parser.classes.Title = __filename;
60
83
  module.exports = Title;
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ AttributesToken = require('../src/attributes');
5
+
6
+ /**
7
+ * 子节点含有AttributesToken的类
8
+ * @template T
9
+ * @param {T} Constructor 基类
10
+ * @param {number} i AttributesToken子节点的位置
11
+ * @returns {T}
12
+ */
13
+ const attributeParent = (Constructor, i = 0) => class extends Constructor {
14
+ /** getAttrs()方法的getter写法 */
15
+ get attributes() {
16
+ return this.getAttrs();
17
+ }
18
+
19
+ /** 以字符串表示的class属性 */
20
+ get className() {
21
+ const attr = this.getAttr('class');
22
+ return typeof attr === 'string' ? attr : '';
23
+ }
24
+
25
+ set className(className) {
26
+ this.setAttr('class', className);
27
+ }
28
+
29
+ /** 以Set表示的class属性 */
30
+ get classList() {
31
+ return new Set(this.className.split(/\s/u));
32
+ }
33
+
34
+ /** id属性 */
35
+ get id() {
36
+ const attr = this.getAttr('id');
37
+ return typeof attr === 'string' ? attr : '';
38
+ }
39
+
40
+ set id(id) {
41
+ this.setAttr('id', id);
42
+ }
43
+
44
+ /**
45
+ * AttributesToken子节点是否具有某属性
46
+ * @this {{childNodes: AttributesToken[]}}
47
+ * @param {string} key 属性键
48
+ */
49
+ hasAttr(key) {
50
+ return this.childNodes.at(i).hasAttr(key);
51
+ }
52
+
53
+ /**
54
+ * 获取AttributesToken子节点的属性
55
+ * @this {{childNodes: AttributesToken[]}}
56
+ * @param {string} key 属性键
57
+ */
58
+ getAttr(key) {
59
+ return this.childNodes.at(i).getAttr(key);
60
+ }
61
+
62
+ /**
63
+ * 列举AttributesToken子节点的属性键
64
+ * @this {{childNodes: AttributesToken[]}}
65
+ */
66
+ getAttrNames() {
67
+ return this.childNodes.at(i).getAttrNames();
68
+ }
69
+
70
+ /**
71
+ * AttributesToken子节点是否具有任意属性
72
+ * @this {{childNodes: AttributesToken[]}}
73
+ */
74
+ hasAttrs() {
75
+ return this.childNodes.at(i).hasAttrs();
76
+ }
77
+
78
+ /**
79
+ * 获取AttributesToken子节点的全部标签属性
80
+ * @this {{childNodes: AttributesToken[]}}
81
+ */
82
+ getAttrs() {
83
+ return this.childNodes.at(i).getAttrs();
84
+ }
85
+
86
+ /**
87
+ * 对AttributesToken子节点设置属性
88
+ * @this {{childNodes: AttributesToken[]}}
89
+ * @param {string} key 属性键
90
+ * @param {string|boolean} value 属性值
91
+ */
92
+ setAttr(key, value) {
93
+ return this.childNodes.at(i).setAttr(key, value);
94
+ }
95
+
96
+ /**
97
+ * 移除AttributesToken子节点的某属性
98
+ * @this {{childNodes: AttributesToken[]}}
99
+ * @param {string} key 属性键
100
+ */
101
+ removeAttr(key) {
102
+ this.childNodes.at(i).removeAttr(key);
103
+ }
104
+
105
+ /**
106
+ * 开关AttributesToken子节点的某属性
107
+ * @this {{childNodes: AttributesToken[]}}
108
+ * @param {string} key 属性键
109
+ * @param {boolean|undefined} force 强制开启或关闭
110
+ */
111
+ toggleAttr(key, force) {
112
+ this.childNodes.at(i).toggleAttr(key, force);
113
+ }
114
+ };
115
+
116
+ Parser.mixins.attributeParent = __filename;
117
+ module.exports = attributeParent;
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ Token = require('../src');
5
+
6
+ /**
7
+ * 不可增删子节点的类
8
+ * @template T
9
+ * @param {T} Constructor 基类
10
+ * @returns {T}
11
+ */
12
+ const fixedToken = Constructor => class extends Constructor {
13
+ static fixed = true;
14
+
15
+ /**
16
+ * 移除子节点
17
+ * @throws `Error`
18
+ */
19
+ removeAt() {
20
+ throw new Error(`${this.constructor.name} 不可删除元素!`);
21
+ }
22
+
23
+ /**
24
+ * 插入子节点
25
+ * @template {Token} T
26
+ * @param {T} token 待插入的子节点
27
+ * @param {number} i 插入位置
28
+ * @throws `Error`
29
+ */
30
+ insertAt(token, i = this.length) {
31
+ if (Parser.running) {
32
+ super.insertAt(token, i);
33
+ return token;
34
+ }
35
+ throw new Error(`${this.constructor.name} 不可插入元素!`);
36
+ }
37
+ };
38
+
39
+ Parser.mixins.fixedToken = __filename;
40
+ module.exports = fixedToken;
package/mixin/hidden.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const Parser = require('..');
4
+
3
5
  /**
4
6
  * 解析后不可见的类
5
7
  * @template T
@@ -15,4 +17,5 @@ const hidden = Constructor => class extends Constructor {
15
17
  }
16
18
  };
17
19
 
20
+ Parser.mixins.hidden = __filename;
18
21
  module.exports = hidden;
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..');
4
+
5
+ /**
6
+ * 不可包含换行符的类
7
+ * @template {Function} T
8
+ * @param {T} Constructor 基类
9
+ * @returns {T}
10
+ */
11
+ const singleLine = Constructor => {
12
+ const SingleLineConstructor = class extends Constructor {
13
+ /**
14
+ * @override
15
+ * @param {string} selector
16
+ */
17
+ toString(selector) {
18
+ return super.toString(selector).replaceAll('\n', ' ');
19
+ }
20
+
21
+ /** @override */
22
+ text() {
23
+ return super.text().replaceAll('\n', ' ');
24
+ }
25
+ };
26
+ Object.defineProperty(SingleLineConstructor, 'name', {value: `SingleLine${Constructor.name}`});
27
+ return SingleLineConstructor;
28
+ };
29
+
30
+ Parser.mixins.singleLine = __filename;
31
+ module.exports = singleLine;
package/mixin/sol.js ADDED
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const Parser = require('..'),
4
+ Token = require('../src');
5
+
6
+ /**
7
+ * 只能位于行首的类
8
+ * @template T
9
+ * @param {T} Constructor 基类
10
+ * @returns {T}
11
+ */
12
+ const sol = Constructor => class SolToken extends Constructor {
13
+ /**
14
+ * 是否可以视为root节点
15
+ * @this {Token}
16
+ */
17
+ #isRoot() {
18
+ const {parentNode, type} = this;
19
+ return parentNode?.type === 'root'
20
+ || type !== 'heading' && parentNode?.type === 'ext-inner' && parentNode.name === 'poem';
21
+ }
22
+
23
+ /**
24
+ * 在前方插入newline
25
+ * @this {SolToken & Token}
26
+ */
27
+ prependNewLine() {
28
+ return (this.previousVisibleSibling || !this.#isRoot()) && !String(this.previousVisibleSibling).endsWith('\n')
29
+ ? '\n'
30
+ : '';
31
+ }
32
+
33
+ /**
34
+ * 还原为wikitext
35
+ * @this {SolToken & Token}
36
+ * @param {string} selector
37
+ */
38
+ toString(selector) {
39
+ return selector && this.matches(selector) ? '' : `${this.prependNewLine()}${super.toString(selector)}`;
40
+ }
41
+
42
+ /** 获取padding */
43
+ getPadding() {
44
+ return this.prependNewLine().length;
45
+ }
46
+
47
+ /** 可见部分 */
48
+ text() {
49
+ return `${this.prependNewLine()}${super.text()}`;
50
+ }
51
+ };
52
+
53
+ Parser.mixins.sol = __filename;
54
+ module.exports = sol;