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.
- package/config/default.json +129 -66
- package/config/zhwiki.json +4 -4
- package/index.js +97 -65
- package/lib/element.js +159 -302
- package/lib/node.js +384 -198
- package/lib/ranges.js +3 -4
- package/lib/text.js +65 -36
- package/lib/title.js +9 -8
- package/mixin/fixedToken.js +4 -4
- package/mixin/hidden.js +2 -0
- package/mixin/sol.js +16 -7
- package/package.json +14 -3
- package/parser/brackets.js +8 -2
- package/parser/commentAndExt.js +1 -1
- package/parser/converter.js +1 -1
- package/parser/externalLinks.js +2 -2
- package/parser/hrAndDoubleUnderscore.js +8 -7
- package/parser/links.js +8 -9
- package/parser/magicLinks.js +1 -1
- package/parser/selector.js +5 -5
- package/parser/table.js +18 -16
- package/src/arg.js +71 -42
- package/src/atom/index.js +7 -5
- package/src/attribute.js +102 -64
- package/src/charinsert.js +91 -0
- package/src/converter.js +34 -15
- package/src/converterFlags.js +87 -40
- package/src/converterRule.js +59 -53
- package/src/extLink.js +45 -37
- package/src/gallery.js +71 -16
- package/src/hasNowiki/index.js +42 -0
- package/src/hasNowiki/pre.js +40 -0
- package/src/heading.js +41 -18
- package/src/html.js +76 -48
- package/src/imageParameter.js +73 -51
- package/src/imagemap.js +205 -0
- package/src/imagemapLink.js +43 -0
- package/src/index.js +243 -138
- package/src/link/category.js +10 -14
- package/src/link/file.js +112 -56
- package/src/link/galleryImage.js +74 -10
- package/src/link/index.js +86 -61
- package/src/magicLink.js +48 -21
- package/src/nested/choose.js +24 -0
- package/src/nested/combobox.js +23 -0
- package/src/nested/index.js +88 -0
- package/src/nested/references.js +23 -0
- package/src/nowiki/comment.js +18 -4
- package/src/nowiki/dd.js +2 -2
- package/src/nowiki/doubleUnderscore.js +16 -11
- package/src/nowiki/index.js +12 -0
- package/src/nowiki/quote.js +28 -1
- package/src/onlyinclude.js +15 -8
- package/src/paramTag/index.js +83 -0
- package/src/paramTag/inputbox.js +42 -0
- package/src/parameter.js +73 -46
- package/src/syntax.js +9 -1
- package/src/table/index.js +58 -44
- package/src/table/td.js +63 -63
- package/src/table/tr.js +52 -35
- package/src/tagPair/ext.js +60 -43
- package/src/tagPair/include.js +11 -1
- package/src/tagPair/index.js +29 -20
- package/src/transclude.js +214 -166
- package/tool/index.js +720 -439
- package/util/base.js +17 -0
- package/util/debug.js +1 -1
- package/{test/util.js → util/diff.js} +15 -19
- package/util/lint.js +40 -0
- package/util/string.js +37 -20
- package/.eslintrc.json +0 -714
- package/errors/README +0 -1
- package/jsconfig.json +0 -7
- package/printed/README +0 -1
- package/printed/example.json +0 -120
- package/test/api.js +0 -83
- package/test/real.js +0 -133
- package/test/test.js +0 -28
- package/typings/api.d.ts +0 -13
- package/typings/array.d.ts +0 -28
- package/typings/event.d.ts +0 -24
- package/typings/index.d.ts +0 -94
- package/typings/node.d.ts +0 -29
- package/typings/parser.d.ts +0 -16
- package/typings/table.d.ts +0 -14
- package/typings/token.d.ts +0 -22
- package/typings/tool.d.ts +0 -11
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/*
|
|
4
4
|
* PHP解析器的步骤:
|
|
5
5
|
* -1. 替换签名和`{{subst:}}`,参见Parser::preSaveTransform;这在revision中不可能保留,可以跳过
|
|
6
|
-
* 0. 移除特定字符`\0`和`\
|
|
6
|
+
* 0. 移除特定字符`\0`和`\x7F`,参见Parser::parse
|
|
7
7
|
* 1. 注释/扩展标签('<'相关),参见Preprocessor_Hash::buildDomTreeArrayFromText和Sanitizer::decodeTagAttributes
|
|
8
8
|
* 2. 模板/模板变量/标题,注意rightmost法则,以及`-{`和`[[`可以破坏`{{`或`{{{`语法,
|
|
9
9
|
* 参见Preprocessor_Hash::buildDomTreeArrayFromText
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
22
|
-
* \0\d+.\
|
|
22
|
+
* \0\d+.\x7F标记Token:
|
|
23
23
|
* e: ExtToken
|
|
24
24
|
* c: CommentToken、NoIncludeToken和IncludeToken
|
|
25
25
|
* !: `{{!}}`专用
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* -: `{{!-}}`专用
|
|
29
29
|
* +: `{{!!}}`专用
|
|
30
30
|
* ~: `{{=}}`专用
|
|
31
|
+
* m: `{{fullurl:}}`、`{{canonicalurl:}}`或`{{filepath:}}`
|
|
31
32
|
* t: ArgToken或TranscludeToken
|
|
32
33
|
* h: HeadingToken
|
|
33
34
|
* x: HtmlToken
|
|
@@ -58,20 +59,12 @@ class Token extends AstElement {
|
|
|
58
59
|
type = 'root';
|
|
59
60
|
#stage = 0; // 解析阶段,参见顶部注释。只对plain Token有意义。
|
|
60
61
|
#config;
|
|
61
|
-
// 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\
|
|
62
|
+
// 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7F/`标记;2. 数组中的Token会依次执行parseOnce和build方法。
|
|
62
63
|
#accum;
|
|
63
64
|
/** @type {Record<string, Ranges>} */ #acceptable;
|
|
64
65
|
#protectedChildren = new Ranges();
|
|
65
66
|
/** @type {boolean} */ #include;
|
|
66
67
|
|
|
67
|
-
/**
|
|
68
|
-
* 保护部分子节点不被移除
|
|
69
|
-
* @param {...string|number|Range} args 子节点范围
|
|
70
|
-
*/
|
|
71
|
-
#protectChildren = (...args) => {
|
|
72
|
-
this.#protectedChildren.push(...new Ranges(args));
|
|
73
|
-
};
|
|
74
|
-
|
|
75
68
|
/**
|
|
76
69
|
* 将维基语法替换为占位符
|
|
77
70
|
* @param {number} n 解析阶段
|
|
@@ -144,9 +137,36 @@ class Token extends AstElement {
|
|
|
144
137
|
throw new Error(`解析错误!未正确标记的 Token:${s}`);
|
|
145
138
|
});
|
|
146
139
|
|
|
140
|
+
/**
|
|
141
|
+
* 将占位符替换为子Token
|
|
142
|
+
* @complexity `n`
|
|
143
|
+
*/
|
|
144
|
+
#build = () => {
|
|
145
|
+
this.#stage = MAX_STAGE;
|
|
146
|
+
const {length, firstChild} = this,
|
|
147
|
+
str = String(firstChild);
|
|
148
|
+
if (length === 1 && firstChild.type === 'text' && str.includes('\0')) {
|
|
149
|
+
this.replaceChildren(...this.#buildFromStr(str));
|
|
150
|
+
this.normalize();
|
|
151
|
+
if (this.type === 'root') {
|
|
152
|
+
for (const token of this.#accum) {
|
|
153
|
+
token.getAttribute('build')();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 保护部分子节点不被移除
|
|
161
|
+
* @param {...string|number|Range} args 子节点范围
|
|
162
|
+
*/
|
|
163
|
+
#protectChildren = (...args) => {
|
|
164
|
+
this.#protectedChildren.push(...new Ranges(args));
|
|
165
|
+
};
|
|
166
|
+
|
|
147
167
|
/** 所有图片,包括图库 */
|
|
148
168
|
get images() {
|
|
149
|
-
return this.querySelectorAll('file, gallery-image');
|
|
169
|
+
return this.querySelectorAll('file, gallery-image, imagemap-image');
|
|
150
170
|
}
|
|
151
171
|
|
|
152
172
|
/** 所有内链、外链和自由外链 */
|
|
@@ -167,7 +187,7 @@ class Token extends AstElement {
|
|
|
167
187
|
constructor(wikitext, config = Parser.getConfig(), halfParsed = false, accum = [], acceptable = null) {
|
|
168
188
|
super();
|
|
169
189
|
if (typeof wikitext === 'string') {
|
|
170
|
-
this.
|
|
190
|
+
this.insertAt(halfParsed ? wikitext : wikitext.replaceAll(/[\0\x7F]/gu, ''));
|
|
171
191
|
}
|
|
172
192
|
this.#config = config;
|
|
173
193
|
this.#accum = accum;
|
|
@@ -175,34 +195,6 @@ class Token extends AstElement {
|
|
|
175
195
|
accum.push(this);
|
|
176
196
|
}
|
|
177
197
|
|
|
178
|
-
/**
|
|
179
|
-
* 深拷贝所有子节点
|
|
180
|
-
* @complexity `n`
|
|
181
|
-
* @returns {(AstText|Token)[]}
|
|
182
|
-
*/
|
|
183
|
-
cloneChildNodes() {
|
|
184
|
-
return this.childNodes.map(child => child.cloneNode());
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* 深拷贝节点
|
|
189
|
-
* @complexity `n`
|
|
190
|
-
* @throws `Error` 未定义复制方法
|
|
191
|
-
*/
|
|
192
|
-
cloneNode() {
|
|
193
|
-
if (!this.isPlain()) {
|
|
194
|
-
throw new Error(`未定义 ${this.constructor.name} 的复制方法!`);
|
|
195
|
-
}
|
|
196
|
-
const cloned = this.cloneChildNodes();
|
|
197
|
-
return Parser.run(() => {
|
|
198
|
-
const token = new Token(undefined, this.#config, false, [], this.#acceptable);
|
|
199
|
-
token.type = this.type;
|
|
200
|
-
token.append(...cloned);
|
|
201
|
-
token.getAttribute('protectChildren')(...this.#protectedChildren);
|
|
202
|
-
return token;
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
198
|
/**
|
|
207
199
|
* @override
|
|
208
200
|
* @template {string} T
|
|
@@ -211,22 +203,24 @@ class Token extends AstElement {
|
|
|
211
203
|
*/
|
|
212
204
|
getAttribute(key) {
|
|
213
205
|
switch (key) {
|
|
214
|
-
case 'stage':
|
|
215
|
-
return this.#stage;
|
|
216
206
|
case 'config':
|
|
217
207
|
return structuredClone(this.#config);
|
|
218
208
|
case 'accum':
|
|
219
209
|
return this.#accum;
|
|
210
|
+
case 'parseOnce':
|
|
211
|
+
return this.#parseOnce;
|
|
212
|
+
case 'buildFromStr':
|
|
213
|
+
return this.#buildFromStr;
|
|
214
|
+
case 'build':
|
|
215
|
+
return this.#build;
|
|
216
|
+
case 'stage':
|
|
217
|
+
return this.#stage;
|
|
220
218
|
case 'acceptable':
|
|
221
219
|
return this.#acceptable ? {...this.#acceptable} : null;
|
|
222
220
|
case 'protectChildren':
|
|
223
221
|
return this.#protectChildren;
|
|
224
222
|
case 'protectedChildren':
|
|
225
223
|
return new Ranges(this.#protectedChildren);
|
|
226
|
-
case 'parseOnce':
|
|
227
|
-
return this.#parseOnce;
|
|
228
|
-
case 'buildFromStr':
|
|
229
|
-
return this.#buildFromStr;
|
|
230
224
|
case 'include': {
|
|
231
225
|
if (this.#include !== undefined) {
|
|
232
226
|
return this.#include;
|
|
@@ -252,16 +246,8 @@ class Token extends AstElement {
|
|
|
252
246
|
* @template {string} T
|
|
253
247
|
* @param {T} key 属性键
|
|
254
248
|
* @param {TokenAttribute<T>} value 属性值
|
|
255
|
-
* @throws `RangeError` 禁止手动指定私有属性
|
|
256
249
|
*/
|
|
257
250
|
setAttribute(key, value) {
|
|
258
|
-
if (key === 'include' || !Parser.running && (key === 'config' || key === 'accum')) {
|
|
259
|
-
throw new RangeError(`禁止手动指定私有的 #${key} 属性!`);
|
|
260
|
-
} else if (!Parser.debugging && (key === 'stage' || key === 'acceptable' || key === 'protectedChildren')
|
|
261
|
-
&& externalUse('setAttribute')
|
|
262
|
-
) {
|
|
263
|
-
throw new RangeError(`使用 ${this.constructor.name}.setAttribute 方法设置私有属性 #${key} 仅用于代码调试!`);
|
|
264
|
-
}
|
|
265
251
|
switch (key) {
|
|
266
252
|
case 'stage':
|
|
267
253
|
if (this.#stage === 0 && this.type === 'root') {
|
|
@@ -269,15 +255,6 @@ class Token extends AstElement {
|
|
|
269
255
|
}
|
|
270
256
|
this.#stage = value;
|
|
271
257
|
return this;
|
|
272
|
-
case 'config':
|
|
273
|
-
this.#config = value;
|
|
274
|
-
return this;
|
|
275
|
-
case 'accum':
|
|
276
|
-
this.#accum = value;
|
|
277
|
-
return this;
|
|
278
|
-
case 'protectedChildren':
|
|
279
|
-
this.#protectedChildren = value;
|
|
280
|
-
return this;
|
|
281
258
|
case 'acceptable': {
|
|
282
259
|
const /** @type {acceptable} */ acceptable = {};
|
|
283
260
|
if (value) {
|
|
@@ -308,36 +285,6 @@ class Token extends AstElement {
|
|
|
308
285
|
return this.constructor === Token;
|
|
309
286
|
}
|
|
310
287
|
|
|
311
|
-
/**
|
|
312
|
-
* @override
|
|
313
|
-
* @param {number} i 移除位置
|
|
314
|
-
* @returns {Token}
|
|
315
|
-
* @complexity `n`
|
|
316
|
-
* @throws `Error` 不可移除的子节点
|
|
317
|
-
*/
|
|
318
|
-
removeAt(i) {
|
|
319
|
-
if (typeof i !== 'number') {
|
|
320
|
-
this.typeError('removeAt', 'Number');
|
|
321
|
-
}
|
|
322
|
-
const iPos = i < 0 ? i + this.childNodes.length : i;
|
|
323
|
-
if (!Parser.running) {
|
|
324
|
-
const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
|
|
325
|
-
if (protectedIndices.includes(iPos)) {
|
|
326
|
-
throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
|
|
327
|
-
} else if (this.#acceptable) {
|
|
328
|
-
const acceptableIndices = Object.fromEntries(
|
|
329
|
-
Object.entries(this.#acceptable)
|
|
330
|
-
.map(([str, ranges]) => [str, ranges.applyTo(this.childNodes.length - 1)]),
|
|
331
|
-
),
|
|
332
|
-
nodesAfter = i === -1 ? [] : this.childNodes.slice(i + 1);
|
|
333
|
-
if (nodesAfter.some(({constructor: {name}}, j) => !acceptableIndices[name].includes(i + j))) {
|
|
334
|
-
throw new Error(`移除 ${this.constructor.name} 的第 ${i} 个子节点会破坏规定的顺序!`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return super.removeAt(i);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
288
|
/**
|
|
342
289
|
* @override
|
|
343
290
|
* @template {string|Token} T
|
|
@@ -372,6 +319,45 @@ class Token extends AstElement {
|
|
|
372
319
|
return token;
|
|
373
320
|
}
|
|
374
321
|
|
|
322
|
+
/**
|
|
323
|
+
* 规范化页面标题
|
|
324
|
+
* @param {string} title 标题(含或不含命名空间前缀)
|
|
325
|
+
* @param {number} defaultNs 命名空间
|
|
326
|
+
*/
|
|
327
|
+
normalizeTitle(title, defaultNs = 0, halfParsed = false) {
|
|
328
|
+
return Parser.normalizeTitle(title, defaultNs, this.#include, this.#config, halfParsed);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @override
|
|
333
|
+
* @param {number} i 移除位置
|
|
334
|
+
* @returns {Token}
|
|
335
|
+
* @complexity `n`
|
|
336
|
+
* @throws `Error` 不可移除的子节点
|
|
337
|
+
*/
|
|
338
|
+
removeAt(i) {
|
|
339
|
+
if (!Number.isInteger(i)) {
|
|
340
|
+
this.typeError('removeAt', 'Number');
|
|
341
|
+
}
|
|
342
|
+
const iPos = i < 0 ? i + this.childNodes.length : i;
|
|
343
|
+
if (!Parser.running) {
|
|
344
|
+
const protectedIndices = this.#protectedChildren.applyTo(this.childNodes);
|
|
345
|
+
if (protectedIndices.includes(iPos)) {
|
|
346
|
+
throw new Error(`${this.constructor.name} 的第 ${i} 个子节点不可移除!`);
|
|
347
|
+
} else if (this.#acceptable) {
|
|
348
|
+
const acceptableIndices = Object.fromEntries(
|
|
349
|
+
Object.entries(this.#acceptable)
|
|
350
|
+
.map(([str, ranges]) => [str, ranges.applyTo(this.childNodes.length - 1)]),
|
|
351
|
+
),
|
|
352
|
+
nodesAfter = i === -1 ? [] : this.childNodes.slice(i + 1);
|
|
353
|
+
if (nodesAfter.some(({constructor: {name}}, j) => !acceptableIndices[name].includes(i + j))) {
|
|
354
|
+
throw new Error(`移除 ${this.constructor.name} 的第 ${i} 个子节点会破坏规定的顺序!`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return super.removeAt(i);
|
|
359
|
+
}
|
|
360
|
+
|
|
375
361
|
/**
|
|
376
362
|
* 替换为同类节点
|
|
377
363
|
* @param {Token} token 待替换的节点
|
|
@@ -444,6 +430,120 @@ class Token extends AstElement {
|
|
|
444
430
|
throw new RangeError(`非法的标签名!${tagName}`);
|
|
445
431
|
}
|
|
446
432
|
|
|
433
|
+
/**
|
|
434
|
+
* 创建纯文本节点
|
|
435
|
+
* @param {string} data 文本内容
|
|
436
|
+
*/
|
|
437
|
+
createTextNode(data = '') {
|
|
438
|
+
return typeof data === 'string' ? new AstText(data) : this.typeError('createComment', 'String');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* 找到给定位置所在的节点
|
|
443
|
+
* @param {number} index 位置
|
|
444
|
+
*/
|
|
445
|
+
caretPositionFromIndex(index) {
|
|
446
|
+
if (index === undefined) {
|
|
447
|
+
return undefined;
|
|
448
|
+
} else if (!Number.isInteger(index)) {
|
|
449
|
+
this.typeError('caretPositionFromIndex', 'Number');
|
|
450
|
+
}
|
|
451
|
+
const {length} = String(this);
|
|
452
|
+
if (index > length || index < -length) {
|
|
453
|
+
return undefined;
|
|
454
|
+
} else if (index < 0) {
|
|
455
|
+
index += length;
|
|
456
|
+
}
|
|
457
|
+
let child = this, // eslint-disable-line unicorn/no-this-assignment
|
|
458
|
+
acc = 0,
|
|
459
|
+
start = 0;
|
|
460
|
+
while (child.type !== 'text') {
|
|
461
|
+
const {childNodes} = child;
|
|
462
|
+
acc += child.getPadding();
|
|
463
|
+
for (let i = 0; acc <= index && i < childNodes.length; i++) {
|
|
464
|
+
const cur = childNodes[i],
|
|
465
|
+
{length: l} = String(cur);
|
|
466
|
+
acc += l;
|
|
467
|
+
if (acc >= index) {
|
|
468
|
+
child = cur;
|
|
469
|
+
acc -= l;
|
|
470
|
+
start = acc;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
acc += child.getGaps(i);
|
|
474
|
+
}
|
|
475
|
+
if (child.childNodes === childNodes) {
|
|
476
|
+
return {offsetNode: child, offset: index - start};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return {offsetNode: child, offset: index - start};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 找到给定位置所在的节点
|
|
484
|
+
* @param {number} x 列数
|
|
485
|
+
* @param {number} y 行数
|
|
486
|
+
*/
|
|
487
|
+
caretPositionFromPoint(x, y) {
|
|
488
|
+
return this.caretPositionFromIndex(this.indexFromPos(y, x));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 找到给定位置所在的最外层节点
|
|
493
|
+
* @param {number} index 位置
|
|
494
|
+
* @throws `Error` 不是根节点
|
|
495
|
+
*/
|
|
496
|
+
elementFromIndex(index) {
|
|
497
|
+
if (index === undefined) {
|
|
498
|
+
return undefined;
|
|
499
|
+
} else if (!Number.isInteger(index)) {
|
|
500
|
+
this.typeError('elementFromIndex', 'Number');
|
|
501
|
+
} else if (this.type !== 'root') {
|
|
502
|
+
throw new Error('elementFromIndex方法只可用于根节点!');
|
|
503
|
+
}
|
|
504
|
+
const {length} = String(this);
|
|
505
|
+
if (index > length || index < -length) {
|
|
506
|
+
return undefined;
|
|
507
|
+
} else if (index < 0) {
|
|
508
|
+
index += length;
|
|
509
|
+
}
|
|
510
|
+
const {childNodes} = this;
|
|
511
|
+
let acc = 0,
|
|
512
|
+
i = 0;
|
|
513
|
+
for (; acc < index && i < childNodes.length; i++) {
|
|
514
|
+
const {length: l} = String(childNodes[i]);
|
|
515
|
+
acc += l;
|
|
516
|
+
}
|
|
517
|
+
return childNodes[i && i - 1];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 找到给定位置所在的最外层节点
|
|
522
|
+
* @param {number} x 列数
|
|
523
|
+
* @param {number} y 行数
|
|
524
|
+
*/
|
|
525
|
+
elementFromPoint(x, y) {
|
|
526
|
+
return this.elementFromIndex(this.indexFromPos(y, x));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* 找到给定位置所在的所有节点
|
|
531
|
+
* @param {number} index 位置
|
|
532
|
+
*/
|
|
533
|
+
elementsFromIndex(index) {
|
|
534
|
+
const offsetNode = this.caretPositionFromIndex(index)?.offsetNode;
|
|
535
|
+
return offsetNode && [...offsetNode.getAncestors().reverse(), offsetNode];
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* 找到给定位置所在的所有节点
|
|
540
|
+
* @param {number} x 列数
|
|
541
|
+
* @param {number} y 行数
|
|
542
|
+
*/
|
|
543
|
+
elementsFromPoint(x, y) {
|
|
544
|
+
return this.elementsFromIndex(this.indexFromPos(y, x));
|
|
545
|
+
}
|
|
546
|
+
|
|
447
547
|
/**
|
|
448
548
|
* 判断标题是否是跨维基链接
|
|
449
549
|
* @param {string} title 标题
|
|
@@ -453,12 +553,31 @@ class Token extends AstElement {
|
|
|
453
553
|
}
|
|
454
554
|
|
|
455
555
|
/**
|
|
456
|
-
*
|
|
457
|
-
* @
|
|
458
|
-
* @
|
|
556
|
+
* 深拷贝所有子节点
|
|
557
|
+
* @complexity `n`
|
|
558
|
+
* @returns {(AstText|Token)[]}
|
|
459
559
|
*/
|
|
460
|
-
|
|
461
|
-
return
|
|
560
|
+
cloneChildNodes() {
|
|
561
|
+
return this.childNodes.map(child => child.cloneNode());
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* 深拷贝节点
|
|
566
|
+
* @complexity `n`
|
|
567
|
+
* @throws `Error` 未定义复制方法
|
|
568
|
+
*/
|
|
569
|
+
cloneNode() {
|
|
570
|
+
if (this.constructor !== Token) {
|
|
571
|
+
throw new Error(`未定义 ${this.constructor.name} 的复制方法!`);
|
|
572
|
+
}
|
|
573
|
+
const cloned = this.cloneChildNodes();
|
|
574
|
+
return Parser.run(() => {
|
|
575
|
+
const token = new Token(undefined, this.#config, false, [], this.#acceptable);
|
|
576
|
+
token.type = this.type;
|
|
577
|
+
token.append(...cloned);
|
|
578
|
+
token.getAttribute('protectChildren')(...this.#protectedChildren);
|
|
579
|
+
return token;
|
|
580
|
+
});
|
|
462
581
|
}
|
|
463
582
|
|
|
464
583
|
/**
|
|
@@ -499,7 +618,7 @@ class Token extends AstElement {
|
|
|
499
618
|
* @complexity `n`
|
|
500
619
|
*/
|
|
501
620
|
section(n) {
|
|
502
|
-
return
|
|
621
|
+
return Number.isInteger(n) ? this.sections()?.[n] : this.typeError('section', 'Number');
|
|
503
622
|
}
|
|
504
623
|
|
|
505
624
|
/**
|
|
@@ -521,27 +640,28 @@ class Token extends AstElement {
|
|
|
521
640
|
if (!parentNode) {
|
|
522
641
|
return undefined;
|
|
523
642
|
}
|
|
524
|
-
const {
|
|
525
|
-
index =
|
|
643
|
+
const {childNodes} = parentNode,
|
|
644
|
+
index = childNodes.indexOf(this);
|
|
526
645
|
let i;
|
|
527
646
|
for (i = index - 1; i >= 0; i--) {
|
|
528
|
-
|
|
647
|
+
const {type, name, selfClosing, closing} = childNodes[i];
|
|
648
|
+
if (type === 'html' && (!tag || name === tag) && selfClosing === false && closing === false) {
|
|
529
649
|
break;
|
|
530
650
|
}
|
|
531
651
|
}
|
|
532
652
|
if (i === -1) {
|
|
533
653
|
return parentNode.findEnclosingHtml(tag);
|
|
534
654
|
}
|
|
535
|
-
const opening =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (
|
|
655
|
+
const opening = childNodes[i];
|
|
656
|
+
for (i = index + 1; i < childNodes.length; i++) {
|
|
657
|
+
const {type, name, selfClosing, closing} = childNodes[i];
|
|
658
|
+
if (type === 'html' && name === opening.name && selfClosing === false && closing === true) {
|
|
539
659
|
break;
|
|
540
660
|
}
|
|
541
661
|
}
|
|
542
|
-
return i ===
|
|
662
|
+
return i === childNodes.length
|
|
543
663
|
? parentNode.findEnclosingHtml(tag)
|
|
544
|
-
: [opening,
|
|
664
|
+
: [opening, childNodes[i]];
|
|
545
665
|
}
|
|
546
666
|
|
|
547
667
|
/**
|
|
@@ -589,29 +709,6 @@ class Token extends AstElement {
|
|
|
589
709
|
this.normalize();
|
|
590
710
|
}
|
|
591
711
|
|
|
592
|
-
/**
|
|
593
|
-
* 将占位符替换为子Token
|
|
594
|
-
* @complexity `n`
|
|
595
|
-
*/
|
|
596
|
-
build() {
|
|
597
|
-
if (!Parser.debugging && externalUse('build')) {
|
|
598
|
-
this.debugOnly('build');
|
|
599
|
-
}
|
|
600
|
-
this.#stage = MAX_STAGE;
|
|
601
|
-
const {childNodes: {length}, firstChild} = this,
|
|
602
|
-
str = String(firstChild);
|
|
603
|
-
if (length === 1 && firstChild.type === 'text' && str.includes('\0')) {
|
|
604
|
-
this.replaceChildren(...this.#buildFromStr(str));
|
|
605
|
-
this.normalize();
|
|
606
|
-
if (this.type === 'root') {
|
|
607
|
-
for (const token of this.#accum) {
|
|
608
|
-
token.build();
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
return this;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
712
|
/** 生成部分Token的`name`属性 */
|
|
616
713
|
afterBuild() {
|
|
617
714
|
if (!Parser.debugging && externalUse('afterBuild')) {
|
|
@@ -630,16 +727,18 @@ class Token extends AstElement {
|
|
|
630
727
|
* @param {boolean} include 是否嵌入
|
|
631
728
|
*/
|
|
632
729
|
parse(n = MAX_STAGE, include = false) {
|
|
633
|
-
if (
|
|
730
|
+
if (!Number.isInteger(n)) {
|
|
634
731
|
this.typeError('parse', 'Number');
|
|
635
|
-
} else if (n < MAX_STAGE && !Parser.debugging && Parser.warning && externalUse('parse')) {
|
|
636
|
-
Parser.warn('指定解析层级的方法仅供熟练用户使用!');
|
|
637
732
|
}
|
|
638
733
|
this.#include = Boolean(include);
|
|
639
734
|
while (this.#stage < n) {
|
|
640
735
|
this.#parseOnce(this.#stage, include);
|
|
641
736
|
}
|
|
642
|
-
|
|
737
|
+
if (n) {
|
|
738
|
+
this.#build();
|
|
739
|
+
this.afterBuild();
|
|
740
|
+
}
|
|
741
|
+
return this;
|
|
643
742
|
}
|
|
644
743
|
|
|
645
744
|
/**
|
|
@@ -654,7 +753,9 @@ class Token extends AstElement {
|
|
|
654
753
|
/** 解析花括号 */
|
|
655
754
|
#parseBrackets() {
|
|
656
755
|
const parseBrackets = require('../parser/brackets');
|
|
657
|
-
this.
|
|
756
|
+
const str = this.type === 'root' ? String(this) : `\0${String(this)}`,
|
|
757
|
+
parsed = parseBrackets(str, this.#config, this.#accum);
|
|
758
|
+
this.setText(this.type === 'root' ? parsed : parsed.slice(1));
|
|
658
759
|
}
|
|
659
760
|
|
|
660
761
|
/** 解析HTML标签 */
|
|
@@ -718,9 +819,13 @@ class Token extends AstElement {
|
|
|
718
819
|
|
|
719
820
|
/** 解析列表 */
|
|
720
821
|
#parseList() {
|
|
822
|
+
if (this.type === 'image-parameter') {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
721
825
|
const parseList = require('../parser/list');
|
|
722
826
|
const lines = String(this).split('\n');
|
|
723
|
-
|
|
827
|
+
let i = this.type === 'root' || this.type === 'ext-inner' && this.type === 'poem' ? 0 : 1;
|
|
828
|
+
for (; i < lines.length; i++) {
|
|
724
829
|
lines[i] = parseList(lines[i], this.#config, this.#accum);
|
|
725
830
|
}
|
|
726
831
|
this.setText(lines.join('\n'));
|
package/src/link/category.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Title = require('../../lib/title'),
|
|
4
4
|
Parser = require('../..'),
|
|
5
|
-
LinkToken = require('.')
|
|
6
|
-
Token = require('..');
|
|
5
|
+
LinkToken = require('.');
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* 分类
|
|
@@ -12,17 +11,13 @@ const Title = require('../../lib/title'),
|
|
|
12
11
|
class CategoryToken extends LinkToken {
|
|
13
12
|
type = 'category';
|
|
14
13
|
|
|
15
|
-
setLangLink = undefined;
|
|
16
|
-
setFragment = undefined;
|
|
17
|
-
asSelfLink = undefined;
|
|
18
|
-
pipeTrick = undefined;
|
|
19
|
-
|
|
20
14
|
/** 分类排序关键字 */
|
|
21
15
|
get sortkey() {
|
|
22
|
-
return this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
return this.childNodes[1]?.text()?.replaceAll(
|
|
17
|
+
/&#(\d+|x[\da-f]+);|\n/gu,
|
|
18
|
+
/** @param {string} p */
|
|
19
|
+
(_, p) => p ? String.fromCodePoint(p[0] === 'x' ? parseInt(p.slice(1), 16) : Number(p)) : '',
|
|
20
|
+
);
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
set sortkey(text) {
|
|
@@ -34,10 +29,11 @@ class CategoryToken extends LinkToken {
|
|
|
34
29
|
* @param {string|undefined} text 排序关键字
|
|
35
30
|
* @param {Title} title 分类页面标题对象
|
|
36
31
|
* @param {accum} accum
|
|
32
|
+
* @param {string} delimiter `|`
|
|
37
33
|
*/
|
|
38
|
-
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
39
|
-
super(link, text, title, config, accum);
|
|
40
|
-
this.seal(['setFragment', 'asSelfLink', 'pipeTrick'], true);
|
|
34
|
+
constructor(link, text, title, config = Parser.getConfig(), accum = [], delimiter = '|') {
|
|
35
|
+
super(link, text, title, config, accum, delimiter);
|
|
36
|
+
this.seal(['selfLink', 'interwiki', 'setLangLink', 'setFragment', 'asSelfLink', 'pipeTrick'], true);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
/**
|