wikiparser-node 0.0.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.
- package/.eslintrc.json +229 -0
- package/LICENSE +674 -0
- package/README.md +1896 -0
- package/config/default.json +766 -0
- package/config/llwiki.json +686 -0
- package/config/moegirl.json +721 -0
- package/index.js +159 -0
- package/jsconfig.json +7 -0
- package/lib/element.js +690 -0
- package/lib/node.js +357 -0
- package/lib/ranges.js +122 -0
- package/lib/title.js +57 -0
- package/mixin/attributeParent.js +67 -0
- package/mixin/fixedToken.js +32 -0
- package/mixin/hidden.js +22 -0
- package/package.json +30 -0
- package/parser/brackets.js +107 -0
- package/parser/commentAndExt.js +61 -0
- package/parser/externalLinks.js +30 -0
- package/parser/hrAndDoubleUnderscore.js +26 -0
- package/parser/html.js +41 -0
- package/parser/links.js +92 -0
- package/parser/magicLinks.js +40 -0
- package/parser/quotes.js +63 -0
- package/parser/table.js +97 -0
- package/src/arg.js +150 -0
- package/src/atom/hidden.js +10 -0
- package/src/atom/index.js +33 -0
- package/src/attribute.js +342 -0
- package/src/extLink.js +116 -0
- package/src/heading.js +91 -0
- package/src/html.js +144 -0
- package/src/imageParameter.js +172 -0
- package/src/index.js +602 -0
- package/src/link/category.js +88 -0
- package/src/link/file.js +201 -0
- package/src/link/index.js +214 -0
- package/src/listToken.js +47 -0
- package/src/magicLink.js +66 -0
- package/src/nowiki/comment.js +45 -0
- package/src/nowiki/doubleUnderscore.js +42 -0
- package/src/nowiki/hr.js +41 -0
- package/src/nowiki/index.js +37 -0
- package/src/nowiki/noinclude.js +24 -0
- package/src/nowiki/quote.js +37 -0
- package/src/onlyinclude.js +42 -0
- package/src/parameter.js +165 -0
- package/src/syntax.js +80 -0
- package/src/table/index.js +867 -0
- package/src/table/td.js +259 -0
- package/src/table/tr.js +244 -0
- package/src/tagPair/ext.js +85 -0
- package/src/tagPair/include.js +45 -0
- package/src/tagPair/index.js +91 -0
- package/src/transclude.js +627 -0
- package/tool/index.js +898 -0
- package/typings/element.d.ts +28 -0
- package/typings/index.d.ts +49 -0
- package/typings/node.d.ts +23 -0
- package/typings/parser.d.ts +9 -0
- package/typings/table.d.ts +14 -0
- package/typings/token.d.ts +21 -0
- package/typings/tool.d.ts +10 -0
- package/util/debug.js +70 -0
- package/util/string.js +60 -0
package/lib/node.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {typeError, externalUse} = require('../util/debug'),
|
|
4
|
+
{text} = require('../util/string'),
|
|
5
|
+
assert = require('assert/strict'),
|
|
6
|
+
/** @type {Parser} */ Parser = require('..');
|
|
7
|
+
|
|
8
|
+
class AstNode {
|
|
9
|
+
/** @type {(string|this)[]} */ childNodes = [];
|
|
10
|
+
/** @type {this} */ #parentNode;
|
|
11
|
+
/** @type {string[]} */ #optional = [];
|
|
12
|
+
|
|
13
|
+
get firstChild() {
|
|
14
|
+
return this.childNodes[0];
|
|
15
|
+
}
|
|
16
|
+
get lastChild() {
|
|
17
|
+
return this.childNodes.at(-1);
|
|
18
|
+
}
|
|
19
|
+
get parentNode() {
|
|
20
|
+
return this.#parentNode;
|
|
21
|
+
}
|
|
22
|
+
/** @complexity `n` */
|
|
23
|
+
get nextSibling() {
|
|
24
|
+
const childNodes = this.#parentNode?.childNodes;
|
|
25
|
+
return childNodes?.[childNodes?.indexOf(this) + 1];
|
|
26
|
+
}
|
|
27
|
+
/** @complexity `n` */
|
|
28
|
+
get previousSibling() {
|
|
29
|
+
const childNodes = this.#parentNode?.childNodes;
|
|
30
|
+
return childNodes?.[childNodes?.indexOf(this) - 1];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
debugOnly(method = 'debugOnly') {
|
|
34
|
+
throw new Error(`${this.constructor.name}.${method} 方法仅用于代码调试!`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @param {string|string[]} keys */
|
|
38
|
+
seal(keys) {
|
|
39
|
+
if (!Parser.running && !Parser.debugging) {
|
|
40
|
+
this.debugOnly('seal');
|
|
41
|
+
}
|
|
42
|
+
keys = Array.isArray(keys) ? keys : [keys];
|
|
43
|
+
this.#optional.push(...keys);
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
Object.defineProperty(this, key, {writable: false, enumerable: Boolean(this[key])});
|
|
46
|
+
}
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
Object.defineProperty(this, 'childNodes', {writable: false});
|
|
52
|
+
Object.freeze(this.childNodes);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @param {this} node */
|
|
56
|
+
isEqualNode(node) {
|
|
57
|
+
try {
|
|
58
|
+
assert.deepStrictEqual(this, node);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
if (e instanceof assert.AssertionError) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @param {PropertyKey} key */
|
|
69
|
+
hasAttribute(key) {
|
|
70
|
+
if (!['string', 'number', 'symbol'].includes(typeof key)) {
|
|
71
|
+
typeError(this, 'hasAttribute', 'String', 'Number', 'Symbol');
|
|
72
|
+
}
|
|
73
|
+
return key in this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 除非用于私有属性,否则总是返回字符串
|
|
78
|
+
* @template {string} T
|
|
79
|
+
* @param {T} key
|
|
80
|
+
* @returns {TokenAttribute<T>}
|
|
81
|
+
*/
|
|
82
|
+
getAttribute(key) {
|
|
83
|
+
if (key === 'optional') {
|
|
84
|
+
return [...this.#optional];
|
|
85
|
+
}
|
|
86
|
+
return this.hasAttribute(key) ? String(this[key]) : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getAttributeNames() {
|
|
90
|
+
const names = Object.getOwnPropertyNames(this);
|
|
91
|
+
return names.filter(name => typeof this[name] !== 'function');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
hasAttributes() {
|
|
95
|
+
return this.getAttributeNames().length > 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @template {string} T
|
|
100
|
+
* @param {T} key
|
|
101
|
+
* @param {TokenAttribute<T>} value
|
|
102
|
+
*/
|
|
103
|
+
setAttribute(key, value) {
|
|
104
|
+
if (key === 'parentNode') {
|
|
105
|
+
if (externalUse('setAttribute')) {
|
|
106
|
+
throw new RangeError(`禁止手动指定 ${key} 属性!`);
|
|
107
|
+
}
|
|
108
|
+
this.#parentNode = value;
|
|
109
|
+
} else if (this.hasAttribute(key)) {
|
|
110
|
+
const descriptor = Object.getOwnPropertyDescriptor(this, key);
|
|
111
|
+
if (!descriptor || !descriptor.writable && externalUse('setAttribute')) {
|
|
112
|
+
throw new RangeError(`禁止手动指定 ${key} 属性!`);
|
|
113
|
+
} else if (this.#optional.includes(key)) {
|
|
114
|
+
descriptor.enumerable = Boolean(value);
|
|
115
|
+
}
|
|
116
|
+
const oldValue = this[key],
|
|
117
|
+
frozen = oldValue !== null && typeof oldValue === 'object' && Object.isFrozen(oldValue);
|
|
118
|
+
Object.defineProperty(this, key, {...descriptor, value});
|
|
119
|
+
if (frozen && value !== null && typeof value === 'object') {
|
|
120
|
+
Object.freeze(value);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
this[key] = value;
|
|
124
|
+
}
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @param {PropertyKey} key */
|
|
129
|
+
removeAttribute(key) {
|
|
130
|
+
if (this.hasAttribute(key)) {
|
|
131
|
+
const descriptor = Object.getOwnPropertyDescriptor(this, key);
|
|
132
|
+
if (!descriptor || !descriptor.writable) {
|
|
133
|
+
throw new RangeError(`属性 ${key} 不可删除!`);
|
|
134
|
+
}
|
|
135
|
+
delete this[key];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {PropertyKey} name
|
|
141
|
+
* @param {boolean|undefined} force
|
|
142
|
+
*/
|
|
143
|
+
toggleAttribute(key, force) {
|
|
144
|
+
if (force !== undefined && typeof force !== 'boolean') {
|
|
145
|
+
typeError(this, 'toggleAttribute', 'Boolean');
|
|
146
|
+
} else if (this.hasAttribute(key) && typeof this[key] !== 'boolean') {
|
|
147
|
+
throw new RangeError(`${key} 属性的值不为 Boolean!`);
|
|
148
|
+
}
|
|
149
|
+
this.setAttribute(key, force === true || force === undefined && !this[key]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @complexity `n` */
|
|
153
|
+
toString(separator = '') {
|
|
154
|
+
return this.childNodes.map(String).join(separator);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 可见部分
|
|
159
|
+
* @returns {string}
|
|
160
|
+
* @complexity `n`
|
|
161
|
+
*/
|
|
162
|
+
text(separator = '') {
|
|
163
|
+
return text(this.childNodes, separator);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
hasChildNodes() {
|
|
167
|
+
return this.childNodes.length > 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 是自身或子孙节点
|
|
172
|
+
* @param {this} node
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
* @complexity `n`
|
|
175
|
+
*/
|
|
176
|
+
contains(node) {
|
|
177
|
+
if (!(node instanceof AstNode)) {
|
|
178
|
+
typeError(this, 'contains', 'Token');
|
|
179
|
+
}
|
|
180
|
+
return node === this || this.childNodes.some(child => child instanceof AstNode && child.contains(node));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** @param {number} i */
|
|
184
|
+
verifyChild(i, addition = 0) {
|
|
185
|
+
if (!Parser.debugging && externalUse('verifyChild')) {
|
|
186
|
+
this.debugOnly('verifyChild');
|
|
187
|
+
} else if (typeof i !== 'number') {
|
|
188
|
+
typeError(this, 'verifyChild', 'Number');
|
|
189
|
+
}
|
|
190
|
+
const {length} = this.childNodes;
|
|
191
|
+
if (i < -length || i >= length + addition || !Number.isInteger(i)) {
|
|
192
|
+
throw new RangeError(`不存在第 ${i} 个子节点!`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @param {number} i */
|
|
197
|
+
removeAt(i) {
|
|
198
|
+
this.verifyChild(i);
|
|
199
|
+
const childNodes = [...this.childNodes],
|
|
200
|
+
[node] = childNodes.splice(i, 1);
|
|
201
|
+
if (node instanceof AstNode) {
|
|
202
|
+
node.setAttribute('parentNode');
|
|
203
|
+
}
|
|
204
|
+
this.setAttribute('childNodes', childNodes);
|
|
205
|
+
return node;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {string|this} node
|
|
210
|
+
* @complexity `n`
|
|
211
|
+
*/
|
|
212
|
+
#getChildIndex(node) {
|
|
213
|
+
const {childNodes} = this,
|
|
214
|
+
i = childNodes.indexOf(node);
|
|
215
|
+
if (i === -1) {
|
|
216
|
+
Parser.error('找不到子节点!', node);
|
|
217
|
+
throw new RangeError('找不到子节点!');
|
|
218
|
+
} else if (typeof node === 'string' && childNodes.lastIndexOf(node) > i) {
|
|
219
|
+
throw new RangeError(`重复的纯文本节点 ${node.replaceAll('\n', '\\n')}!`);
|
|
220
|
+
}
|
|
221
|
+
return i;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @template {string|this} T
|
|
226
|
+
* @param {T} node
|
|
227
|
+
* @complexity `n`
|
|
228
|
+
*/
|
|
229
|
+
removeChild(node) {
|
|
230
|
+
this.removeAt(this.#getChildIndex(node));
|
|
231
|
+
return node;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @template {string|this} T
|
|
236
|
+
* @param {T} node
|
|
237
|
+
* @complexity `n`
|
|
238
|
+
*/
|
|
239
|
+
insertAt(node, i = this.childNodes.length) {
|
|
240
|
+
if (typeof node !== 'string' && !(node instanceof AstNode)) {
|
|
241
|
+
typeError(this, 'insertAt', 'String', 'Token');
|
|
242
|
+
} else if (node instanceof AstNode && node.contains(this)) {
|
|
243
|
+
Parser.error('不能插入祖先节点!', node);
|
|
244
|
+
throw new RangeError('不能插入祖先节点!');
|
|
245
|
+
}
|
|
246
|
+
this.verifyChild(i, 1);
|
|
247
|
+
const childNodes = [...this.childNodes];
|
|
248
|
+
if (node instanceof AstNode) {
|
|
249
|
+
const j = Parser.running ? -1 : childNodes.indexOf(node);
|
|
250
|
+
if (j !== -1) {
|
|
251
|
+
childNodes.splice(j, 1);
|
|
252
|
+
} else {
|
|
253
|
+
node.parentNode?.removeChild(node);
|
|
254
|
+
node.setAttribute('parentNode', this);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
childNodes.splice(i, 0, node);
|
|
258
|
+
this.setAttribute('childNodes', childNodes);
|
|
259
|
+
return node;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @template {string|this} T
|
|
264
|
+
* @param {T} node
|
|
265
|
+
* @complexity `n`
|
|
266
|
+
*/
|
|
267
|
+
appendChild(node) {
|
|
268
|
+
return this.insertAt(node);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @template {string|this} T
|
|
273
|
+
* @param {T} child
|
|
274
|
+
* @param {string|this} reference
|
|
275
|
+
* @complexity `n`
|
|
276
|
+
*/
|
|
277
|
+
insertBefore(child, reference) {
|
|
278
|
+
if (reference === undefined) {
|
|
279
|
+
return this.appendChild(child);
|
|
280
|
+
}
|
|
281
|
+
return this.insertAt(child, this.#getChildIndex(reference));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @template {string|this} T
|
|
286
|
+
* @param {string|this} newChild
|
|
287
|
+
* @param {T} oldChild
|
|
288
|
+
* @complexity `n`
|
|
289
|
+
*/
|
|
290
|
+
replaceChild(newChild, oldChild) {
|
|
291
|
+
const i = this.#getChildIndex(oldChild);
|
|
292
|
+
this.removeAt(i);
|
|
293
|
+
this.insertAt(newChild, i);
|
|
294
|
+
return oldChild;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** @param {string} str */
|
|
298
|
+
setText(str, i = 0) {
|
|
299
|
+
if (typeof str !== 'string') {
|
|
300
|
+
typeError(this, 'setText', 'String');
|
|
301
|
+
}
|
|
302
|
+
this.verifyChild(i);
|
|
303
|
+
const oldText = this.childNodes.at(i);
|
|
304
|
+
if (typeof oldText !== 'string') {
|
|
305
|
+
throw new RangeError(`第 ${i} 个子节点是 ${oldText.constructor.name}!`);
|
|
306
|
+
}
|
|
307
|
+
const childNodes = [...this.childNodes];
|
|
308
|
+
childNodes.splice(i, 1, str);
|
|
309
|
+
this.setAttribute('childNodes', childNodes);
|
|
310
|
+
return oldText;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @param {number} i
|
|
315
|
+
* @param {number} offset
|
|
316
|
+
*/
|
|
317
|
+
splitText(i, offset) {
|
|
318
|
+
if (typeof offset !== 'number') {
|
|
319
|
+
typeError(this, 'splitText', 'Number');
|
|
320
|
+
}
|
|
321
|
+
this.verifyChild(i);
|
|
322
|
+
const oldText = this.childNodes.at(i);
|
|
323
|
+
if (typeof oldText !== 'string') {
|
|
324
|
+
throw new RangeError(`第 ${i} 个子节点是 ${oldText.constructor.name}!`);
|
|
325
|
+
}
|
|
326
|
+
const newText = oldText.slice(offset);
|
|
327
|
+
this.insertAt(newText, i + 1);
|
|
328
|
+
this.setText(oldText.slice(0, offset), i);
|
|
329
|
+
return newText;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** @complexity `n` */
|
|
333
|
+
normalize() {
|
|
334
|
+
const childNodes = [...this.childNodes];
|
|
335
|
+
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
336
|
+
const str = childNodes[i];
|
|
337
|
+
if (str === '') {
|
|
338
|
+
childNodes.splice(i, 1);
|
|
339
|
+
} else if (typeof str === 'string' && typeof childNodes[i - 1] === 'string') {
|
|
340
|
+
childNodes[i - 1] += str;
|
|
341
|
+
childNodes.splice(i, 1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
this.setAttribute('childNodes', childNodes);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
getRootNode() {
|
|
348
|
+
let {parentNode} = this;
|
|
349
|
+
while (parentNode?.parentNode) {
|
|
350
|
+
({parentNode} = parentNode);
|
|
351
|
+
}
|
|
352
|
+
return parentNode ?? this;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
Parser.classes.AstNode = __filename;
|
|
357
|
+
module.exports = AstNode;
|
package/lib/ranges.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..');
|
|
4
|
+
|
|
5
|
+
/** 模拟Python的Range对象。除`step`至少为`1`外,允许负数、小数或`end < start`的情形。 */
|
|
6
|
+
class Range {
|
|
7
|
+
start;
|
|
8
|
+
end;
|
|
9
|
+
step;
|
|
10
|
+
|
|
11
|
+
/** @param {string|Range} str */
|
|
12
|
+
constructor(str) {
|
|
13
|
+
if (str instanceof Range) {
|
|
14
|
+
Object.assign(this, str);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
str = str.trim();
|
|
18
|
+
if (str === 'odd') {
|
|
19
|
+
Object.assign(this, {start: 1, end: Infinity, step: 2});
|
|
20
|
+
} else if (str === 'even') {
|
|
21
|
+
Object.assign(this, {start: 0, end: Infinity, step: 2});
|
|
22
|
+
} else if (str.includes(':')) {
|
|
23
|
+
const [start, end, step = '1'] = str.split(':', 3);
|
|
24
|
+
this.start = Number(start);
|
|
25
|
+
this.end = Number(end || Infinity);
|
|
26
|
+
this.step = Math.max(Number(step), 1);
|
|
27
|
+
if (!Number.isInteger(this.start)) {
|
|
28
|
+
throw new RangeError(`起点 ${this.start} 应为整数!`);
|
|
29
|
+
} else if (this.end !== Infinity && !Number.isInteger(this.end)) {
|
|
30
|
+
throw new RangeError(`终点 ${this.end} 应为整数!`);
|
|
31
|
+
} else if (!Number.isInteger(this.step)) {
|
|
32
|
+
throw new RangeError(`步长 ${this.step} 应为整数!`);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
const mt = str.match(/^([+-])?(\d+)?n(?:([+-])(\d+))?$/);
|
|
36
|
+
if (mt) {
|
|
37
|
+
const [, sgnA = '+', a = 1, sgnB = '+'] = mt,
|
|
38
|
+
b = Number(mt[4] ?? 0);
|
|
39
|
+
this.step = Number(a);
|
|
40
|
+
if (this.step === 0) {
|
|
41
|
+
throw new RangeError(`参数 ${str} 中 "n" 的系数不允许为 0!`);
|
|
42
|
+
} else if (sgnA === '+') {
|
|
43
|
+
this.start = sgnB === '+' || b === 0 ? b : this.step - 1 - (b - 1) % this.step;
|
|
44
|
+
this.end = Infinity;
|
|
45
|
+
} else if (sgnB === '-') {
|
|
46
|
+
this.start = 0;
|
|
47
|
+
this.end = b > 0 ? 0 : this.step;
|
|
48
|
+
} else {
|
|
49
|
+
this.start = b % this.step;
|
|
50
|
+
this.end = this.step + b;
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
throw new RangeError(`参数 ${str} 应写作CSS选择器的 "an+b" 形式或Python切片!`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 将Range转换为针对特定Array的下标集
|
|
60
|
+
* @param {number|any[]} arr
|
|
61
|
+
* @complexity `n`
|
|
62
|
+
*/
|
|
63
|
+
applyTo(arr) {
|
|
64
|
+
return new Array(typeof arr === 'number' ? arr : arr.length).fill().map((_, i) => i)
|
|
65
|
+
.slice(this.start, this.end).filter((_, j) => j % this.step === 0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @extends {Array<number|Range>} */
|
|
70
|
+
class Ranges extends Array {
|
|
71
|
+
/** @param {number|string|Range|(number|string|Range)[]} arr */
|
|
72
|
+
constructor(arr) {
|
|
73
|
+
super();
|
|
74
|
+
if (arr === undefined) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
arr = Array.isArray(arr) ? arr : [arr];
|
|
78
|
+
for (const ele of arr) {
|
|
79
|
+
if (ele instanceof Range) {
|
|
80
|
+
this.push(new Range(ele));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const number = Number(ele);
|
|
84
|
+
if (Number.isInteger(number)) {
|
|
85
|
+
this.push(number);
|
|
86
|
+
} else if (typeof ele === 'string' && Number.isNaN(number)) {
|
|
87
|
+
try {
|
|
88
|
+
const range = new Range(ele);
|
|
89
|
+
this.push(range);
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 将Ranges转换为针对特定Array的下标集
|
|
97
|
+
* @param {number|any[]} arr
|
|
98
|
+
* @complexity `n`
|
|
99
|
+
*/
|
|
100
|
+
applyTo(arr) {
|
|
101
|
+
const length = typeof arr === 'number' ? arr : arr.length;
|
|
102
|
+
return [...new Set(
|
|
103
|
+
Array.from(this).flatMap(ele => {
|
|
104
|
+
if (typeof ele === 'number') {
|
|
105
|
+
return ele < 0 ? ele + length : ele;
|
|
106
|
+
}
|
|
107
|
+
return ele.applyTo(length);
|
|
108
|
+
}),
|
|
109
|
+
)].filter(i => i >= 0 && i < length).sort();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} str
|
|
114
|
+
* @param {number} i
|
|
115
|
+
*/
|
|
116
|
+
static nth(str, i) {
|
|
117
|
+
return new Ranges(str.split(',')).applyTo(i + 1).includes(i);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
Parser.classes.Ranges = __filename;
|
|
122
|
+
module.exports = Ranges;
|
package/lib/title.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {ucfirst} = require('../util/string'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('..');
|
|
5
|
+
|
|
6
|
+
class Title {
|
|
7
|
+
title = '';
|
|
8
|
+
ns = 0;
|
|
9
|
+
interwiki = '';
|
|
10
|
+
fragment = '';
|
|
11
|
+
valid = true;
|
|
12
|
+
|
|
13
|
+
/** @param {string} title */
|
|
14
|
+
constructor(title, defaultNs = 0, config = Parser.getConfig()) {
|
|
15
|
+
const {namespaces, nsid} = config;
|
|
16
|
+
let namespace = namespaces[defaultNs];
|
|
17
|
+
title = title.replaceAll('_', ' ').trim();
|
|
18
|
+
if (title[0] === ':') {
|
|
19
|
+
namespace = '';
|
|
20
|
+
title = title.slice(1).trim();
|
|
21
|
+
}
|
|
22
|
+
const iw = Parser.isInterwiki(title, config);
|
|
23
|
+
if (iw) {
|
|
24
|
+
this.interwiki = iw[1].toLowerCase();
|
|
25
|
+
title = title.slice(iw[0].length);
|
|
26
|
+
}
|
|
27
|
+
const m = title.split(':');
|
|
28
|
+
if (m.length > 1) {
|
|
29
|
+
const id = namespaces[nsid[m[0].trim().toLowerCase()]];
|
|
30
|
+
if (id !== undefined) {
|
|
31
|
+
namespace = id;
|
|
32
|
+
title = m.slice(1).join(':').trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
this.ns = nsid[namespace.toLowerCase()];
|
|
36
|
+
const i = title.indexOf('#');
|
|
37
|
+
if (i !== -1) {
|
|
38
|
+
const fragment = title.slice(i + 1).trimEnd();
|
|
39
|
+
if (fragment.includes('%')) {
|
|
40
|
+
try {
|
|
41
|
+
this.fragment = decodeURIComponent(fragment);
|
|
42
|
+
} catch {}
|
|
43
|
+
} else if (fragment.includes('.')) {
|
|
44
|
+
try {
|
|
45
|
+
this.fragment = decodeURIComponent(fragment.replaceAll('.', '%'));
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
this.fragment ||= fragment;
|
|
49
|
+
title = title.slice(0, i).trim();
|
|
50
|
+
}
|
|
51
|
+
this.title = `${iw ? `${this.interwiki}:` : ''}${namespace}${namespace && ':'}${ucfirst(title)}`;
|
|
52
|
+
this.valid = !/\x00\d+[eh!+-]\x7f|[<>[\]{}|]/.test(this.title);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Parser.classes.Title = __filename;
|
|
57
|
+
module.exports = Title;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..'),
|
|
4
|
+
AttributeToken = require('../src/attribute'); // eslint-disable-line no-unused-vars
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @template T
|
|
8
|
+
* @param {T} constructor
|
|
9
|
+
* @returns {T}
|
|
10
|
+
*/
|
|
11
|
+
const attributeParent = (constructor, i = 0) => class extends constructor {
|
|
12
|
+
/**
|
|
13
|
+
* @this {{children: AttributeToken[]}}
|
|
14
|
+
* @param {string} key
|
|
15
|
+
*/
|
|
16
|
+
hasAttr(key) {
|
|
17
|
+
return this.children.at(i).hasAttr(key);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @this {{children: AttributeToken[]}}
|
|
22
|
+
* @template {string|undefined} T
|
|
23
|
+
* @param {T} key
|
|
24
|
+
*/
|
|
25
|
+
getAttr(key) {
|
|
26
|
+
return this.children.at(i).getAttr(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @this {{children: AttributeToken[]}} */
|
|
30
|
+
getAttrNames() {
|
|
31
|
+
return this.children.at(i).getAttrNames();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @this {{children: AttributeToken[]}} */
|
|
35
|
+
hasAttrs() {
|
|
36
|
+
return this.children.at(i).hasAttrs();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @this {{children: AttributeToken[]}}
|
|
41
|
+
* @param {string} key
|
|
42
|
+
* @param {string|boolean} value
|
|
43
|
+
*/
|
|
44
|
+
setAttr(key, value) {
|
|
45
|
+
return this.children.at(i).setAttr(key, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @this {{children: AttributeToken[]}}
|
|
50
|
+
* @param {string} key
|
|
51
|
+
*/
|
|
52
|
+
removeAttr(key) {
|
|
53
|
+
this.children.at(i).removeAttr(key);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @this {{children: AttributeToken[]}}
|
|
58
|
+
* @param {string} key
|
|
59
|
+
* @param {boolean|undefined} force
|
|
60
|
+
*/
|
|
61
|
+
toggleAttr(key, force) {
|
|
62
|
+
this.children.at(i).toggleAttr(key, force);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
Parser.mixins.attributeParent = __filename;
|
|
67
|
+
module.exports = attributeParent;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {T} constructor
|
|
8
|
+
* @returns {T}
|
|
9
|
+
*/
|
|
10
|
+
const fixedToken = constructor => class extends constructor {
|
|
11
|
+
static fixed = true;
|
|
12
|
+
|
|
13
|
+
removeAt() {
|
|
14
|
+
throw new Error(`${this.constructor.name} 不可删除元素!`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @template {string|Token} T
|
|
19
|
+
* @param {T} token
|
|
20
|
+
* @param {number} i
|
|
21
|
+
*/
|
|
22
|
+
insertAt(token, i = this.childNodes.length) {
|
|
23
|
+
if (!Parser.running) {
|
|
24
|
+
throw new Error(`${this.constructor.name} 不可插入元素!`);
|
|
25
|
+
}
|
|
26
|
+
super.insertAt(token, i);
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
Parser.mixins.fixedToken = __filename;
|
|
32
|
+
module.exports = fixedToken;
|
package/mixin/hidden.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {T} constructor
|
|
8
|
+
* @returns {T}
|
|
9
|
+
*/
|
|
10
|
+
const hidden = constructor => class extends constructor {
|
|
11
|
+
text() {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @returns {[number, string][]} */
|
|
16
|
+
plain() {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
Parser.mixins.hidden = __filename;
|
|
22
|
+
module.exports = hidden;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wikiparser-node",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A Node.js parser for MediaWiki markup with AST",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mediawiki",
|
|
7
|
+
"wikitext",
|
|
8
|
+
"parser"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/bhsd-harry/wikiparser-node#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/bhsd-harry/wikiparser-node/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "GPL-3.0",
|
|
15
|
+
"author": "Bhsd",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/bhsd-harry/wikiparser-node.git"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "echo 'Error: no test specified' && exit 1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"eslint": "^8.8.0",
|
|
25
|
+
"@types/node": "^17.0.23"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": "^18.4.0"
|
|
29
|
+
}
|
|
30
|
+
}
|