wikiparser-node 0.11.0-m → 0.11.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/config/.schema.json +7 -0
- package/config/default.json +1 -0
- package/config/llwiki.json +35 -0
- package/config/moegirl.json +44 -0
- package/config/zhwiki.json +466 -0
- package/dist/index.d.ts +4 -0
- package/dist/lib/element.d.ts +116 -2
- package/dist/lib/node.d.ts +193 -10
- package/dist/lib/ranges.d.ts +37 -0
- package/dist/lib/text.d.ts +35 -1
- package/dist/lib/title.d.ts +6 -0
- package/dist/mixin/attributeParent.d.ts +9 -0
- package/dist/mixin/fixedToken.d.ts +8 -0
- package/dist/mixin/singleLine.d.ts +8 -0
- package/dist/mixin/sol.d.ts +8 -0
- package/dist/parser/selector.d.ts +12 -0
- package/dist/src/arg.d.ts +30 -1
- package/dist/src/atom/index.d.ts +2 -1
- package/dist/src/attribute.d.ts +24 -1
- package/dist/src/attributes.d.ts +79 -0
- package/dist/src/charinsert.d.ts +32 -0
- package/dist/src/converter.d.ts +75 -1
- package/dist/src/converterFlags.d.ts +57 -1
- package/dist/src/converterRule.d.ts +47 -1
- package/dist/src/extLink.d.ts +41 -1
- package/dist/src/gallery.d.ts +15 -1
- package/dist/src/heading.d.ts +22 -2
- package/dist/src/html.d.ts +25 -2
- package/dist/src/imageParameter.d.ts +43 -1
- package/dist/src/imagemap.d.ts +12 -1
- package/dist/src/imagemapLink.d.ts +5 -0
- package/dist/src/index.d.ts +136 -3
- package/dist/src/link/category.d.ts +8 -0
- package/dist/src/link/file.d.ts +58 -0
- package/dist/src/link/index.d.ts +61 -2
- package/dist/src/magicLink.d.ts +22 -0
- package/dist/src/nested/index.d.ts +3 -5
- package/dist/src/nowiki/comment.d.ts +13 -1
- package/dist/src/nowiki/dd.d.ts +9 -0
- package/dist/src/nowiki/doubleUnderscore.d.ts +11 -1
- package/dist/src/nowiki/index.d.ts +11 -2
- package/dist/src/nowiki/quote.d.ts +0 -7
- package/dist/src/onlyinclude.d.ts +8 -1
- package/dist/src/paramTag/index.d.ts +8 -3
- package/dist/src/parameter.d.ts +48 -1
- package/dist/src/syntax.d.ts +6 -1
- package/dist/src/table/index.d.ts +257 -0
- package/dist/src/table/td.d.ts +67 -6
- package/dist/src/table/tr.d.ts +74 -0
- package/dist/src/tagPair/ext.d.ts +2 -1
- package/dist/src/tagPair/include.d.ts +9 -0
- package/dist/src/tagPair/index.d.ts +14 -1
- package/dist/src/transclude.d.ts +125 -1
- package/dist/tool/index.d.ts +420 -0
- package/dist/util/base.d.ts +10 -0
- package/dist/util/debug.d.ts +20 -0
- package/dist/util/diff.d.ts +8 -0
- package/dist/util/string.d.ts +24 -0
- package/index.js +256 -4
- package/lib/element.js +488 -6
- package/lib/node.js +495 -6
- package/lib/ranges.js +130 -0
- package/lib/text.js +96 -1
- package/lib/title.js +28 -1
- package/mixin/attributeParent.js +117 -0
- package/mixin/fixedToken.js +40 -0
- package/mixin/hidden.js +3 -0
- package/mixin/singleLine.js +31 -0
- package/mixin/sol.js +54 -0
- package/package.json +5 -2
- package/parser/brackets.js +1 -0
- package/parser/commentAndExt.js +1 -0
- package/parser/converter.js +1 -0
- package/parser/externalLinks.js +1 -0
- package/parser/hrAndDoubleUnderscore.js +1 -0
- package/parser/html.js +1 -0
- package/parser/links.js +5 -4
- package/parser/list.js +1 -0
- package/parser/magicLinks.js +1 -0
- package/parser/quotes.js +1 -0
- package/parser/selector.js +180 -0
- package/parser/table.js +1 -0
- package/src/arg.js +116 -2
- package/src/atom/hidden.js +2 -0
- package/src/atom/index.js +17 -0
- package/src/attribute.js +189 -3
- package/src/attributes.js +307 -4
- package/src/charinsert.js +97 -0
- package/src/converter.js +108 -2
- package/src/converterFlags.js +187 -0
- package/src/converterRule.js +183 -1
- package/src/extLink.js +121 -1
- package/src/gallery.js +54 -0
- package/src/hasNowiki/index.js +12 -0
- package/src/hasNowiki/pre.js +12 -0
- package/src/heading.js +54 -3
- package/src/html.js +118 -3
- package/src/imageParameter.js +164 -2
- package/src/imagemap.js +61 -2
- package/src/imagemapLink.js +13 -1
- package/src/index.js +530 -3
- package/src/link/category.js +32 -1
- package/src/link/file.js +157 -2
- package/src/link/galleryImage.js +60 -2
- package/src/link/index.js +270 -1
- package/src/magicLink.js +83 -0
- package/src/nested/choose.js +1 -0
- package/src/nested/combobox.js +1 -0
- package/src/nested/index.js +27 -0
- package/src/nested/references.js +1 -0
- package/src/nowiki/comment.js +25 -1
- package/src/nowiki/dd.js +47 -1
- package/src/nowiki/doubleUnderscore.js +31 -1
- package/src/nowiki/hr.js +20 -1
- package/src/nowiki/index.js +23 -1
- package/src/nowiki/list.js +5 -2
- package/src/nowiki/noinclude.js +14 -0
- package/src/nowiki/quote.js +14 -0
- package/src/onlyinclude.js +26 -1
- package/src/paramTag/index.js +24 -1
- package/src/paramTag/inputbox.js +4 -1
- package/src/parameter.js +147 -5
- package/src/syntax.js +68 -0
- package/src/table/index.js +941 -2
- package/src/table/td.js +230 -5
- package/src/table/tr.js +247 -2
- package/src/tagPair/ext.js +21 -1
- package/src/tagPair/include.js +24 -0
- package/src/tagPair/index.js +56 -2
- package/src/transclude.js +512 -5
- package/tool/index.js +1209 -0
- package/typings/array.d.ts +29 -0
- package/typings/event.d.ts +22 -0
- package/typings/index.d.ts +67 -0
- package/typings/node.d.ts +19 -0
- package/typings/parser.d.ts +7 -0
- package/typings/table.d.ts +10 -0
- package/typings/token.d.ts +3 -0
- package/typings/tool.d.ts +6 -0
- package/util/debug.js +73 -0
- package/util/string.js +51 -0
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/** @typedef {import('../typings/token').acceptable} acceptable */
|
|
3
4
|
/**
|
|
4
5
|
* @template T
|
|
5
6
|
* @typedef {import('../typings/node').TokenAttribute<T>} TokenAttribute
|
|
@@ -46,10 +47,13 @@
|
|
|
46
47
|
// v: ConverterToken
|
|
47
48
|
|
|
48
49
|
const {text} = require('../util/string'),
|
|
50
|
+
{externalUse} = require('../util/debug'),
|
|
51
|
+
assert = require('assert/strict'),
|
|
52
|
+
Ranges = require('../lib/ranges'),
|
|
49
53
|
Parser = require('..'),
|
|
50
54
|
AstElement = require('../lib/element'),
|
|
51
55
|
AstText = require('../lib/text');
|
|
52
|
-
const {MAX_STAGE} = Parser;
|
|
56
|
+
const {MAX_STAGE, aliases} = Parser;
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* 所有节点的基类
|
|
@@ -62,6 +66,8 @@ class Token extends AstElement {
|
|
|
62
66
|
// 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7F/`标记;2. 数组中的Token会依次执行parseOnce和build方法。
|
|
63
67
|
#accum;
|
|
64
68
|
/** @type {boolean} */ #include;
|
|
69
|
+
/** @type {Record<string, Ranges>} */ #acceptable;
|
|
70
|
+
#protectedChildren = new Ranges();
|
|
65
71
|
|
|
66
72
|
/**
|
|
67
73
|
* 将维基语法替换为占位符
|
|
@@ -165,9 +171,33 @@ class Token extends AstElement {
|
|
|
165
171
|
}
|
|
166
172
|
};
|
|
167
173
|
|
|
174
|
+
/**
|
|
175
|
+
* 保护部分子节点不被移除
|
|
176
|
+
* @param {...string|number|Range} args 子节点范围
|
|
177
|
+
*/
|
|
178
|
+
#protectChildren = (...args) => {
|
|
179
|
+
this.#protectedChildren.push(...new Ranges(args));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** 所有图片,包括图库 */
|
|
183
|
+
get images() {
|
|
184
|
+
return this.querySelectorAll('file, gallery-image, imagemap-image');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** 所有内链、外链和自由外链 */
|
|
188
|
+
get links() {
|
|
189
|
+
return this.querySelectorAll('link, ext-link, free-ext-link, image-parameter#link');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 所有模板和模块 */
|
|
193
|
+
get embeds() {
|
|
194
|
+
return this.querySelectorAll('template, magic-word#invoke');
|
|
195
|
+
}
|
|
196
|
+
|
|
168
197
|
/**
|
|
169
198
|
* @param {string} wikitext wikitext
|
|
170
199
|
* @param {import('../typings/token').accum} accum
|
|
200
|
+
* @param {acceptable} acceptable 可接受的子节点设置
|
|
171
201
|
*/
|
|
172
202
|
constructor(wikitext, config = Parser.getConfig(), halfParsed = false, accum = [], acceptable = undefined) {
|
|
173
203
|
super();
|
|
@@ -176,6 +206,7 @@ class Token extends AstElement {
|
|
|
176
206
|
}
|
|
177
207
|
this.#config = config;
|
|
178
208
|
this.#accum = accum;
|
|
209
|
+
this.setAttribute('acceptable', acceptable);
|
|
179
210
|
accum.push(this);
|
|
180
211
|
}
|
|
181
212
|
|
|
@@ -205,8 +236,21 @@ class Token extends AstElement {
|
|
|
205
236
|
if (root.type === 'root' && root !== this) {
|
|
206
237
|
return root.getAttribute('include');
|
|
207
238
|
}
|
|
208
|
-
|
|
239
|
+
const includeToken = root.querySelector('include');
|
|
240
|
+
if (includeToken) {
|
|
241
|
+
return includeToken.name === 'noinclude';
|
|
242
|
+
}
|
|
243
|
+
const noincludeToken = root.querySelector('noinclude');
|
|
244
|
+
return Boolean(noincludeToken) && !/^<\/?noinclude(?:\s[^>]*)?\/?>$/iu.test(String(noincludeToken));
|
|
209
245
|
}
|
|
246
|
+
case 'stage':
|
|
247
|
+
return this.#stage;
|
|
248
|
+
case 'acceptable':
|
|
249
|
+
return this.#acceptable ? {...this.#acceptable} : undefined;
|
|
250
|
+
case 'protectChildren':
|
|
251
|
+
return this.#protectChildren;
|
|
252
|
+
case 'protectedChildren':
|
|
253
|
+
return new Ranges(this.#protectedChildren);
|
|
210
254
|
default:
|
|
211
255
|
return super.getAttribute(key);
|
|
212
256
|
}
|
|
@@ -226,6 +270,26 @@ class Token extends AstElement {
|
|
|
226
270
|
}
|
|
227
271
|
this.#stage = value;
|
|
228
272
|
return this;
|
|
273
|
+
case 'acceptable': {
|
|
274
|
+
const /** @type {acceptable} */ acceptable = {};
|
|
275
|
+
if (value) {
|
|
276
|
+
for (const [k, v] of Object.entries(value)) {
|
|
277
|
+
if (k.startsWith('Stage-')) {
|
|
278
|
+
for (let i = 0; i <= Number(k.slice(6)); i++) {
|
|
279
|
+
for (const type of aliases[i]) {
|
|
280
|
+
acceptable[type] = new Ranges(v);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} else if (k[0] === '!') { // `!`项必须放在最后
|
|
284
|
+
delete acceptable[k.slice(1)];
|
|
285
|
+
} else {
|
|
286
|
+
acceptable[k] = new Ranges(v);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
this.#acceptable = value && acceptable;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
229
293
|
default:
|
|
230
294
|
return super.setAttribute(key, value);
|
|
231
295
|
}
|
|
@@ -243,11 +307,26 @@ class Token extends AstElement {
|
|
|
243
307
|
* @param {number} i 插入位置
|
|
244
308
|
* @complexity `n`
|
|
245
309
|
* @returns {T extends Token ? Token : AstText}
|
|
310
|
+
* @throws `RangeError` 不可插入的子节点
|
|
246
311
|
*/
|
|
247
312
|
insertAt(token, i = this.length) {
|
|
248
313
|
if (typeof token === 'string') {
|
|
249
314
|
token = new AstText(token);
|
|
250
315
|
}
|
|
316
|
+
if (!Parser.running && this.#acceptable) {
|
|
317
|
+
const acceptableIndices = Object.fromEntries(
|
|
318
|
+
Object.entries(this.#acceptable)
|
|
319
|
+
.map(([str, ranges]) => [str, ranges.applyTo(this.length + 1)]),
|
|
320
|
+
),
|
|
321
|
+
nodesAfter = this.childNodes.slice(i),
|
|
322
|
+
{constructor: {name: insertedName}} = token,
|
|
323
|
+
k = i < 0 ? i + this.length : i;
|
|
324
|
+
if (!acceptableIndices[insertedName].includes(k)) {
|
|
325
|
+
throw new RangeError(`${this.constructor.name} 的第 ${k} 个子节点不能为 ${insertedName}!`);
|
|
326
|
+
} else if (nodesAfter.some(({constructor: {name}}, j) => !acceptableIndices[name].includes(k + j + 1))) {
|
|
327
|
+
throw new Error(`${this.constructor.name} 插入新的第 ${k} 个子节点会破坏规定的顺序!`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
251
330
|
super.insertAt(token, i);
|
|
252
331
|
if (token.type === 'root') {
|
|
253
332
|
token.type = 'plain';
|
|
@@ -266,9 +345,453 @@ class Token extends AstElement {
|
|
|
266
345
|
return Parser.normalizeTitle(title, defaultNs, this.#include, this.#config, halfParsed, decode, selfLink);
|
|
267
346
|
}
|
|
268
347
|
|
|
348
|
+
/**
|
|
349
|
+
* @override
|
|
350
|
+
* @param {number} i 移除位置
|
|
351
|
+
* @returns {Token}
|
|
352
|
+
* @complexity `n`
|
|
353
|
+
* @throws `Error` 不可移除的子节点
|
|
354
|
+
*/
|
|
355
|
+
removeAt(i) {
|
|
356
|
+
if (!Number.isInteger(i)) {
|
|
357
|
+
this.typeError('removeAt', 'Number');
|
|
358
|
+
}
|
|
359
|
+
const iPos = i < 0 ? i + this.length : i;
|
|
360
|
+
if (!Parser.running) {
|
|
361
|
+
const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
|
|
362
|
+
if (protectedIndices.includes(iPos)) {
|
|
363
|
+
throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
|
|
364
|
+
} else if (this.#acceptable) {
|
|
365
|
+
const acceptableIndices = Object.fromEntries(
|
|
366
|
+
Object.entries(this.#acceptable)
|
|
367
|
+
.map(([str, ranges]) => [str, ranges.applyTo(this.length - 1)]),
|
|
368
|
+
),
|
|
369
|
+
nodesAfter = i === -1 ? [] : this.childNodes.slice(i + 1);
|
|
370
|
+
if (nodesAfter.some(({constructor: {name}}, j) => !acceptableIndices[name].includes(i + j))) {
|
|
371
|
+
throw new Error(`移除 ${this.constructor.name} 的第 ${i} 个子节点会破坏规定的顺序!`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return super.removeAt(i);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 替换为同类节点
|
|
380
|
+
* @param {Token} token 待替换的节点
|
|
381
|
+
* @complexity `n`
|
|
382
|
+
* @throws `Error` 不存在父节点
|
|
383
|
+
* @throws `Error` 待替换的节点具有不同属性
|
|
384
|
+
*/
|
|
385
|
+
safeReplaceWith(token) {
|
|
386
|
+
const {parentNode} = this;
|
|
387
|
+
if (!parentNode) {
|
|
388
|
+
throw new Error('不存在父节点!');
|
|
389
|
+
} else if (token.constructor !== this.constructor) {
|
|
390
|
+
this.typeError('safeReplaceWith', this.constructor.name);
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
assert.deepEqual(token.getAttribute('acceptable'), this.#acceptable);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
if (e instanceof assert.AssertionError) {
|
|
396
|
+
throw new Error(`待替换的 ${this.constructor.name} 带有不同的 #acceptable 属性!`);
|
|
397
|
+
}
|
|
398
|
+
throw e;
|
|
399
|
+
}
|
|
400
|
+
const i = parentNode.childNodes.indexOf(this);
|
|
401
|
+
super.removeAt.call(parentNode, i);
|
|
402
|
+
super.insertAt.call(parentNode, token, i);
|
|
403
|
+
if (token.type === 'root') {
|
|
404
|
+
token.type = 'plain';
|
|
405
|
+
}
|
|
406
|
+
const e = new Event('replace', {bubbles: true});
|
|
407
|
+
token.dispatchEvent(e, {position: i, oldToken: this, newToken: token});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 创建HTML注释
|
|
412
|
+
* @param {string} data 注释内容
|
|
413
|
+
*/
|
|
414
|
+
createComment(data = '') {
|
|
415
|
+
if (typeof data === 'string') {
|
|
416
|
+
const CommentToken = require('./nowiki/comment');
|
|
417
|
+
const config = this.getAttribute('config');
|
|
418
|
+
return Parser.run(() => new CommentToken(data.replaceAll('-->', '-->'), true, config));
|
|
419
|
+
}
|
|
420
|
+
return this.typeError('createComment', 'String');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 创建标签
|
|
425
|
+
* @param {string} tagName 标签名
|
|
426
|
+
* @param {{selfClosing: boolean, closing: boolean}} options 选项
|
|
427
|
+
* @throws `RangeError` 非法的标签名
|
|
428
|
+
*/
|
|
429
|
+
createElement(tagName, {selfClosing, closing} = {}) {
|
|
430
|
+
if (typeof tagName !== 'string') {
|
|
431
|
+
this.typeError('createElement', 'String');
|
|
432
|
+
}
|
|
433
|
+
const config = this.getAttribute('config'),
|
|
434
|
+
include = this.getAttribute('include');
|
|
435
|
+
if (tagName === (include ? 'noinclude' : 'includeonly')) {
|
|
436
|
+
const IncludeToken = require('./tagPair/include');
|
|
437
|
+
return Parser.run(
|
|
438
|
+
() => new IncludeToken(tagName, '', undefined, selfClosing ? undefined : tagName, config),
|
|
439
|
+
);
|
|
440
|
+
} else if (config.ext.includes(tagName)) {
|
|
441
|
+
const ExtToken = require('./tagPair/ext');
|
|
442
|
+
return Parser.run(() => new ExtToken(tagName, '', '', selfClosing ? undefined : '', config));
|
|
443
|
+
} else if (config.html.flat().includes(tagName)) {
|
|
444
|
+
const HtmlToken = require('./html');
|
|
445
|
+
return Parser.run(() => new HtmlToken(tagName, '', closing, selfClosing, config));
|
|
446
|
+
}
|
|
447
|
+
throw new RangeError(`非法的标签名!${tagName}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 创建纯文本节点
|
|
452
|
+
* @param {string} data 文本内容
|
|
453
|
+
*/
|
|
454
|
+
createTextNode(data = '') {
|
|
455
|
+
return typeof data === 'string' ? new AstText(data) : this.typeError('createComment', 'String');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* 找到给定位置所在的节点
|
|
460
|
+
* @param {number} index 位置
|
|
461
|
+
*/
|
|
462
|
+
caretPositionFromIndex(index) {
|
|
463
|
+
if (index === undefined) {
|
|
464
|
+
return undefined;
|
|
465
|
+
} else if (!Number.isInteger(index)) {
|
|
466
|
+
this.typeError('caretPositionFromIndex', 'Number');
|
|
467
|
+
}
|
|
468
|
+
const {length} = String(this);
|
|
469
|
+
if (index > length || index < -length) {
|
|
470
|
+
return undefined;
|
|
471
|
+
} else if (index < 0) {
|
|
472
|
+
index += length;
|
|
473
|
+
}
|
|
474
|
+
let child = this, // eslint-disable-line unicorn/no-this-assignment
|
|
475
|
+
acc = 0,
|
|
476
|
+
start = 0;
|
|
477
|
+
while (child.type !== 'text') {
|
|
478
|
+
const {childNodes} = child;
|
|
479
|
+
acc += child.getPadding();
|
|
480
|
+
for (let i = 0; acc <= index && i < childNodes.length; i++) {
|
|
481
|
+
const cur = childNodes[i],
|
|
482
|
+
{length: l} = String(cur);
|
|
483
|
+
acc += l;
|
|
484
|
+
if (acc >= index) {
|
|
485
|
+
child = cur;
|
|
486
|
+
acc -= l;
|
|
487
|
+
start = acc;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
acc += child.getGaps(i);
|
|
491
|
+
}
|
|
492
|
+
if (child.childNodes === childNodes) {
|
|
493
|
+
return {offsetNode: child, offset: index - start};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {offsetNode: child, offset: index - start};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 找到给定位置所在的节点
|
|
501
|
+
* @param {number} x 列数
|
|
502
|
+
* @param {number} y 行数
|
|
503
|
+
*/
|
|
504
|
+
caretPositionFromPoint(x, y) {
|
|
505
|
+
return this.caretPositionFromIndex(this.indexFromPos(y, x));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* 找到给定位置所在的最外层节点
|
|
510
|
+
* @param {number} index 位置
|
|
511
|
+
* @throws `Error` 不是根节点
|
|
512
|
+
*/
|
|
513
|
+
elementFromIndex(index) {
|
|
514
|
+
if (index === undefined) {
|
|
515
|
+
return undefined;
|
|
516
|
+
} else if (!Number.isInteger(index)) {
|
|
517
|
+
this.typeError('elementFromIndex', 'Number');
|
|
518
|
+
} else if (this.type !== 'root') {
|
|
519
|
+
throw new Error('elementFromIndex方法只可用于根节点!');
|
|
520
|
+
}
|
|
521
|
+
const {length} = String(this);
|
|
522
|
+
if (index > length || index < -length) {
|
|
523
|
+
return undefined;
|
|
524
|
+
} else if (index < 0) {
|
|
525
|
+
index += length;
|
|
526
|
+
}
|
|
527
|
+
const {childNodes} = this;
|
|
528
|
+
let acc = 0,
|
|
529
|
+
i = 0;
|
|
530
|
+
for (; acc < index && i < childNodes.length; i++) {
|
|
531
|
+
const {length: l} = String(childNodes[i]);
|
|
532
|
+
acc += l;
|
|
533
|
+
}
|
|
534
|
+
return childNodes[i && i - 1];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 找到给定位置所在的最外层节点
|
|
539
|
+
* @param {number} x 列数
|
|
540
|
+
* @param {number} y 行数
|
|
541
|
+
*/
|
|
542
|
+
elementFromPoint(x, y) {
|
|
543
|
+
return this.elementFromIndex(this.indexFromPos(y, x));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* 找到给定位置所在的所有节点
|
|
548
|
+
* @param {number} index 位置
|
|
549
|
+
*/
|
|
550
|
+
elementsFromIndex(index) {
|
|
551
|
+
const offsetNode = this.caretPositionFromIndex(index)?.offsetNode;
|
|
552
|
+
return offsetNode && [...offsetNode.getAncestors().reverse(), offsetNode];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* 找到给定位置所在的所有节点
|
|
557
|
+
* @param {number} x 列数
|
|
558
|
+
* @param {number} y 行数
|
|
559
|
+
*/
|
|
560
|
+
elementsFromPoint(x, y) {
|
|
561
|
+
return this.elementsFromIndex(this.indexFromPos(y, x));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* 判断标题是否是跨维基链接
|
|
566
|
+
* @param {string} title 标题
|
|
567
|
+
*/
|
|
568
|
+
isInterwiki(title) {
|
|
569
|
+
return Parser.isInterwiki(title, this.#config);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* 深拷贝所有子节点
|
|
574
|
+
* @complexity `n`
|
|
575
|
+
* @returns {(AstText|Token)[]}
|
|
576
|
+
*/
|
|
577
|
+
cloneChildNodes() {
|
|
578
|
+
return this.childNodes.map(child => child.cloneNode());
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* 深拷贝节点
|
|
583
|
+
* @complexity `n`
|
|
584
|
+
* @throws `Error` 未定义复制方法
|
|
585
|
+
*/
|
|
586
|
+
cloneNode() {
|
|
587
|
+
if (this.constructor !== Token) {
|
|
588
|
+
throw new Error(`未定义 ${this.constructor.name} 的复制方法!`);
|
|
589
|
+
}
|
|
590
|
+
const cloned = this.cloneChildNodes();
|
|
591
|
+
return Parser.run(() => {
|
|
592
|
+
const token = new Token(undefined, this.#config, false, [], this.#acceptable);
|
|
593
|
+
token.type = this.type;
|
|
594
|
+
token.append(...cloned);
|
|
595
|
+
token.getAttribute('protectChildren')(...this.#protectedChildren);
|
|
596
|
+
return token;
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* 获取全部章节
|
|
602
|
+
* @complexity `n`
|
|
603
|
+
*/
|
|
604
|
+
sections() {
|
|
605
|
+
if (this.type !== 'root') {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
const {childNodes} = this,
|
|
609
|
+
headings = [...childNodes.entries()].filter(([, {type}]) => type === 'heading')
|
|
610
|
+
.map(([i, {name}]) => [i, Number(name)]),
|
|
611
|
+
lastHeading = [-1, -1, -1, -1, -1, -1],
|
|
612
|
+
/** @type {(AstText|Token)[][]} */ sections = new Array(headings.length);
|
|
613
|
+
for (let i = 0; i < headings.length; i++) {
|
|
614
|
+
const [index, level] = headings[i];
|
|
615
|
+
for (let j = level; j < 6; j++) {
|
|
616
|
+
const last = lastHeading[j];
|
|
617
|
+
if (last >= 0) {
|
|
618
|
+
sections[last] = childNodes.slice(headings[last][0], index);
|
|
619
|
+
}
|
|
620
|
+
lastHeading[j] = j === level ? i : -1;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
for (const last of lastHeading) {
|
|
624
|
+
if (last >= 0) {
|
|
625
|
+
sections[last] = childNodes.slice(headings[last][0]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
sections.unshift(childNodes.slice(0, headings[0]?.[0]));
|
|
629
|
+
return sections;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* 获取指定章节
|
|
634
|
+
* @param {number} n 章节序号
|
|
635
|
+
* @complexity `n`
|
|
636
|
+
*/
|
|
637
|
+
section(n) {
|
|
638
|
+
return Number.isInteger(n) ? this.sections()?.[n] : this.typeError('section', 'Number');
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* 获取指定的外层HTML标签
|
|
643
|
+
* @param {string|undefined} tag HTML标签名
|
|
644
|
+
* @returns {[Token, Token]}
|
|
645
|
+
* @complexity `n`
|
|
646
|
+
* @throws `RangeError` 非法的标签或空标签
|
|
647
|
+
*/
|
|
648
|
+
findEnclosingHtml(tag) {
|
|
649
|
+
if (tag !== undefined && typeof tag !== 'string') {
|
|
650
|
+
this.typeError('findEnclosingHtml', 'String');
|
|
651
|
+
}
|
|
652
|
+
tag = tag?.toLowerCase();
|
|
653
|
+
if (tag !== undefined && !this.#config.html.slice(0, 2).flat().includes(tag)) {
|
|
654
|
+
throw new RangeError(`非法的标签或空标签:${tag}`);
|
|
655
|
+
}
|
|
656
|
+
const {parentNode} = this;
|
|
657
|
+
if (!parentNode) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
const {childNodes} = parentNode,
|
|
661
|
+
index = childNodes.indexOf(this);
|
|
662
|
+
let i;
|
|
663
|
+
for (i = index - 1; i >= 0; i--) {
|
|
664
|
+
const {type, name, selfClosing, closing} = childNodes[i];
|
|
665
|
+
if (type === 'html' && (!tag || name === tag) && selfClosing === false && closing === false) {
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (i === -1) {
|
|
670
|
+
return parentNode.findEnclosingHtml(tag);
|
|
671
|
+
}
|
|
672
|
+
const opening = childNodes[i];
|
|
673
|
+
for (i = index + 1; i < childNodes.length; i++) {
|
|
674
|
+
const {type, name, selfClosing, closing} = childNodes[i];
|
|
675
|
+
if (type === 'html' && name === opening.name && selfClosing === false && closing === true) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return i === childNodes.length
|
|
680
|
+
? parentNode.findEnclosingHtml(tag)
|
|
681
|
+
: [opening, childNodes[i]];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* 获取全部分类
|
|
686
|
+
* @complexity `n`
|
|
687
|
+
*/
|
|
688
|
+
getCategories() {
|
|
689
|
+
return this.querySelectorAll('category').map(({name, sortkey}) => [name, sortkey]);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* 重新解析单引号
|
|
694
|
+
* @throws `Error` 不接受QuoteToken作为子节点
|
|
695
|
+
*/
|
|
696
|
+
redoQuotes() {
|
|
697
|
+
const acceptable = this.getAttribute('acceptable');
|
|
698
|
+
if (acceptable && !acceptable.QuoteToken?.some(
|
|
699
|
+
range => typeof range !== 'number' && range.start === 0 && range.end === Infinity && range.step === 1,
|
|
700
|
+
)) {
|
|
701
|
+
throw new Error(`${this.constructor.name} 不接受 QuoteToken 作为子节点!`);
|
|
702
|
+
}
|
|
703
|
+
for (const quote of this.childNodes) {
|
|
704
|
+
if (quote.type === 'quote') {
|
|
705
|
+
quote.replaceWith(String(quote));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
this.normalize();
|
|
709
|
+
/** @type {[number, AstText][]} */
|
|
710
|
+
const textNodes = [...this.childNodes.entries()].filter(([, {type}]) => type === 'text'),
|
|
711
|
+
indices = textNodes.map(([i]) => this.getRelativeIndex(i)),
|
|
712
|
+
token = Parser.run(() => {
|
|
713
|
+
const root = new Token(text(textNodes.map(([, str]) => str)), this.getAttribute('config'));
|
|
714
|
+
return root.setAttribute('stage', 6).parse(7);
|
|
715
|
+
});
|
|
716
|
+
for (const quote of [...token.childNodes].reverse()) {
|
|
717
|
+
if (quote.type === 'quote') {
|
|
718
|
+
const index = quote.getRelativeIndex(),
|
|
719
|
+
n = indices.findLastIndex(textIndex => textIndex <= index);
|
|
720
|
+
this.childNodes[n].splitText(index - indices[n]);
|
|
721
|
+
this.childNodes[n + 1].splitText(Number(quote.name));
|
|
722
|
+
this.removeAt(n + 1);
|
|
723
|
+
this.insertAt(quote, n + 1);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
this.normalize();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** 解析部分魔术字 */
|
|
730
|
+
solveConst() {
|
|
731
|
+
const ArgToken = require('./arg'),
|
|
732
|
+
ParameterToken = require('./parameter');
|
|
733
|
+
const targets = this.querySelectorAll('magic-word, arg'),
|
|
734
|
+
magicWords = new Set(['if', 'ifeq', 'switch']);
|
|
735
|
+
for (let i = targets.length - 1; i >= 0; i--) {
|
|
736
|
+
const /** @type {ArgToken} */ target = targets[i],
|
|
737
|
+
{type, name, default: argDefault, childNodes, length} = target;
|
|
738
|
+
if (type === 'arg' || type === 'magic-word' && magicWords.has(name)) {
|
|
739
|
+
let replace = '';
|
|
740
|
+
if (type === 'arg') {
|
|
741
|
+
replace = argDefault === false ? String(target) : argDefault;
|
|
742
|
+
} else if (name === 'if' && !childNodes[1].querySelector('magic-word, template')) {
|
|
743
|
+
replace = String(childNodes[String(childNodes[1] ?? '').trim() ? 2 : 3] ?? '').trim();
|
|
744
|
+
} else if (name === 'ifeq'
|
|
745
|
+
&& !childNodes.slice(1, 3).some(child => child.querySelector('magic-word, template'))
|
|
746
|
+
) {
|
|
747
|
+
replace = String(childNodes[
|
|
748
|
+
String(childNodes[1] ?? '').trim() === String(childNodes[2] ?? '') ? 3 : 4
|
|
749
|
+
] ?? '').trim();
|
|
750
|
+
} else if (name === 'switch' && !childNodes[1].querySelector('magic-word, template')) {
|
|
751
|
+
const key = String(childNodes[1] ?? '').trim();
|
|
752
|
+
let defaultVal = '',
|
|
753
|
+
found = false,
|
|
754
|
+
transclusion = false,
|
|
755
|
+
j = 2;
|
|
756
|
+
for (; j < length; j++) {
|
|
757
|
+
const /** @type {ParameterToken} */ {anon, name: option, value, firstChild} = childNodes[j];
|
|
758
|
+
transclusion = firstChild.querySelector('magic-word, template');
|
|
759
|
+
if (anon) {
|
|
760
|
+
if (j === length - 1) {
|
|
761
|
+
defaultVal = value;
|
|
762
|
+
} else if (transclusion) {
|
|
763
|
+
break;
|
|
764
|
+
} else {
|
|
765
|
+
found ||= key === value;
|
|
766
|
+
}
|
|
767
|
+
} else if (transclusion) {
|
|
768
|
+
break;
|
|
769
|
+
} else if (found || option === key) {
|
|
770
|
+
replace = value;
|
|
771
|
+
break;
|
|
772
|
+
} else if (option.toLowerCase() === '#default') {
|
|
773
|
+
defaultVal = value;
|
|
774
|
+
}
|
|
775
|
+
if (j === length - 1) {
|
|
776
|
+
replace = defaultVal;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (transclusion) {
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
target.replaceWith(replace);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
269
790
|
/** 生成部分Token的`name`属性 */
|
|
270
791
|
afterBuild() {
|
|
271
|
-
if (
|
|
792
|
+
if (!Parser.debugging && externalUse('afterBuild')) {
|
|
793
|
+
this.debugOnly('afterBuild');
|
|
794
|
+
} else if (this.type === 'root') {
|
|
272
795
|
for (const token of this.#accum) {
|
|
273
796
|
token.afterBuild();
|
|
274
797
|
}
|
|
@@ -281,6 +804,9 @@ class Token extends AstElement {
|
|
|
281
804
|
* @param {boolean} include 是否嵌入
|
|
282
805
|
*/
|
|
283
806
|
parse(n = MAX_STAGE, include = false) {
|
|
807
|
+
if (!Number.isInteger(n)) {
|
|
808
|
+
this.typeError('parse', 'Number');
|
|
809
|
+
}
|
|
284
810
|
while (this.#stage < n) {
|
|
285
811
|
this.#parseOnce(this.#stage, include);
|
|
286
812
|
}
|
|
@@ -408,4 +934,5 @@ class Token extends AstElement {
|
|
|
408
934
|
}
|
|
409
935
|
}
|
|
410
936
|
|
|
937
|
+
Parser.classes.Token = __filename;
|
|
411
938
|
module.exports = Token;
|
package/src/link/category.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {decodeHtml} = require('../../util/string'),
|
|
4
|
+
Parser = require('../..'),
|
|
5
|
+
LinkToken = require('.');
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* 分类
|
|
@@ -8,6 +10,35 @@ const LinkToken = require('.');
|
|
|
8
10
|
*/
|
|
9
11
|
class CategoryToken extends LinkToken {
|
|
10
12
|
type = 'category';
|
|
13
|
+
|
|
14
|
+
/** 分类排序关键字 */
|
|
15
|
+
get sortkey() {
|
|
16
|
+
return decodeHtml(this.childNodes[1]?.text());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set sortkey(text) {
|
|
20
|
+
this.setSortkey(text);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} link 分类名
|
|
25
|
+
* @param {string|undefined} text 排序关键字
|
|
26
|
+
* @param {import('../../typings/token').accum} accum
|
|
27
|
+
* @param {string} delimiter `|`
|
|
28
|
+
*/
|
|
29
|
+
constructor(link, text, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
30
|
+
super(link, text, config, accum, delimiter);
|
|
31
|
+
this.seal(['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'pipeTrick'], true);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 设置排序关键字
|
|
36
|
+
* @param {string} text 排序关键字
|
|
37
|
+
*/
|
|
38
|
+
setSortkey(text) {
|
|
39
|
+
this.setLinkText(text);
|
|
40
|
+
}
|
|
11
41
|
}
|
|
12
42
|
|
|
43
|
+
Parser.classes.CategoryToken = __filename;
|
|
13
44
|
module.exports = CategoryToken;
|