wikiparser-node 0.4.0 → 0.5.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/index.js +25 -2
- package/lib/element.js +69 -185
- package/lib/node.js +159 -1
- package/lib/ranges.js +1 -2
- package/lib/text.js +35 -6
- package/lib/title.js +1 -1
- package/mixin/fixedToken.js +4 -4
- package/mixin/sol.js +17 -7
- package/package.json +11 -1
- package/parser/commentAndExt.js +1 -1
- package/parser/converter.js +1 -1
- package/parser/externalLinks.js +1 -1
- package/parser/hrAndDoubleUnderscore.js +6 -5
- package/parser/links.js +1 -2
- package/parser/magicLinks.js +1 -1
- package/parser/selector.js +5 -5
- package/parser/table.js +12 -12
- package/src/arg.js +44 -20
- package/src/attribute.js +34 -7
- package/src/converter.js +13 -5
- package/src/converterFlags.js +42 -5
- package/src/converterRule.js +25 -19
- package/src/extLink.js +20 -14
- package/src/gallery.js +35 -4
- package/src/heading.js +28 -9
- package/src/html.js +46 -18
- package/src/imageParameter.js +13 -7
- package/src/index.js +22 -15
- package/src/link/category.js +6 -6
- package/src/link/file.js +25 -5
- package/src/link/index.js +36 -33
- package/src/magicLink.js +32 -4
- package/src/nowiki/comment.js +14 -0
- package/src/nowiki/doubleUnderscore.js +5 -0
- package/src/nowiki/quote.js +28 -1
- package/src/onlyinclude.js +5 -0
- package/src/parameter.js +48 -35
- package/src/table/index.js +37 -24
- package/src/table/td.js +23 -17
- package/src/table/tr.js +47 -30
- package/src/tagPair/ext.js +4 -5
- package/src/tagPair/include.js +10 -0
- package/src/tagPair/index.js +8 -0
- package/src/transclude.js +79 -46
- package/tool/index.js +1 -1
- package/{test/util.js → util/diff.js} +14 -18
- package/util/lint.js +40 -0
- package/util/string.js +20 -3
- 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/index.js
CHANGED
|
@@ -75,6 +75,8 @@ const /** @type {Parser} */ Parser = {
|
|
|
75
75
|
'converter-rule-from': ['convert-rule-from', 'conversion-rule-from'],
|
|
76
76
|
},
|
|
77
77
|
|
|
78
|
+
promises: [Promise.resolve()],
|
|
79
|
+
|
|
78
80
|
warn(msg, ...args) {
|
|
79
81
|
if (this.warning) {
|
|
80
82
|
console.warn('\x1B[33m%s\x1B[0m', msg, ...args);
|
|
@@ -172,7 +174,7 @@ const /** @type {Parser} */ Parser = {
|
|
|
172
174
|
|
|
173
175
|
parse(wikitext, include, maxStage = Parser.MAX_STAGE, config = Parser.getConfig()) {
|
|
174
176
|
const Token = require('./src');
|
|
175
|
-
let token;
|
|
177
|
+
let /** @type {Token} */ token;
|
|
176
178
|
this.run(() => {
|
|
177
179
|
if (typeof wikitext === 'string') {
|
|
178
180
|
token = new Token(wikitext, config);
|
|
@@ -197,6 +199,27 @@ const /** @type {Parser} */ Parser = {
|
|
|
197
199
|
throw e;
|
|
198
200
|
}
|
|
199
201
|
});
|
|
202
|
+
if (this.debugging) {
|
|
203
|
+
let restored = String(token),
|
|
204
|
+
process = '解析';
|
|
205
|
+
if (restored === wikitext) {
|
|
206
|
+
const entities = {lt: '<', gt: '>', amp: '&'};
|
|
207
|
+
restored = token.print().replaceAll(
|
|
208
|
+
/<[^<]+?>|&([lg]t|amp);/gu,
|
|
209
|
+
/** @param {string} s */ (_, s) => s ? entities[s] : '',
|
|
210
|
+
);
|
|
211
|
+
process = '渲染HTML';
|
|
212
|
+
}
|
|
213
|
+
if (restored !== wikitext) {
|
|
214
|
+
const diff = require('./util/diff');
|
|
215
|
+
const {promises: {0: cur, length}} = this;
|
|
216
|
+
this.promises.unshift((async () => {
|
|
217
|
+
await cur;
|
|
218
|
+
this.error(`${process}过程中不可逆地修改了原始文本!`);
|
|
219
|
+
return diff(wikitext, restored, length);
|
|
220
|
+
})());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
200
223
|
return token;
|
|
201
224
|
},
|
|
202
225
|
|
|
@@ -234,7 +257,7 @@ const /** @type {Parser} */ Parser = {
|
|
|
234
257
|
|
|
235
258
|
const /** @type {PropertyDescriptorMap} */ def = {};
|
|
236
259
|
for (const key in Parser) {
|
|
237
|
-
if (['aliases', 'MAX_STAGE', 'typeAliases'].includes(key)) {
|
|
260
|
+
if (['aliases', 'MAX_STAGE', 'typeAliases', 'promises'].includes(key)) {
|
|
238
261
|
def[key] = {enumerable: false, writable: false};
|
|
239
262
|
} else if (!['config', 'isInterwiki', 'normalizeTitle', 'parse', 'getTool'].includes(key)) {
|
|
240
263
|
def[key] = {enumerable: false};
|
package/lib/element.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs'),
|
|
4
|
+
path = require('path'),
|
|
4
5
|
{externalUse} = require('../util/debug'),
|
|
5
|
-
{toCase, noWrap} = require('../util/string'),
|
|
6
|
+
{toCase, noWrap, print} = require('../util/string'),
|
|
6
7
|
{nth} = require('./ranges'),
|
|
7
8
|
parseSelector = require('../parser/selector'),
|
|
8
9
|
Parser = require('..'),
|
|
@@ -138,6 +139,18 @@ class AstElement extends AstNode {
|
|
|
138
139
|
return previousSibling;
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
/** 内部高度 */
|
|
143
|
+
get clientHeight() {
|
|
144
|
+
const {innerText} = this;
|
|
145
|
+
return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 内部宽度 */
|
|
149
|
+
get clientWidth() {
|
|
150
|
+
const {innerText} = this;
|
|
151
|
+
return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
141
154
|
constructor() {
|
|
142
155
|
super();
|
|
143
156
|
this.seal('name');
|
|
@@ -228,11 +241,11 @@ class AstElement extends AstNode {
|
|
|
228
241
|
this.getAttribute('verifyChild')(i);
|
|
229
242
|
const /** @type {AstText} */ oldText = this.childNodes.at(i),
|
|
230
243
|
{type, data, constructor: {name}} = oldText;
|
|
231
|
-
if (type
|
|
232
|
-
|
|
244
|
+
if (type === 'text') {
|
|
245
|
+
oldText.replaceData(str);
|
|
246
|
+
return data;
|
|
233
247
|
}
|
|
234
|
-
|
|
235
|
-
return data;
|
|
248
|
+
throw new RangeError(`第 ${i} 个子节点是 ${name}!`);
|
|
236
249
|
}
|
|
237
250
|
|
|
238
251
|
/** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
|
|
@@ -298,7 +311,7 @@ class AstElement extends AstNode {
|
|
|
298
311
|
|| (type === 'file' || type === 'gallery-image' && link);
|
|
299
312
|
case ':local-link':
|
|
300
313
|
return (type === 'link' || type === 'file' || type === 'gallery-image')
|
|
301
|
-
&& link?.
|
|
314
|
+
&& link?.[0] === '#';
|
|
302
315
|
case ':read-only':
|
|
303
316
|
return fixed;
|
|
304
317
|
case ':read-write':
|
|
@@ -358,7 +371,7 @@ class AstElement extends AstNode {
|
|
|
358
371
|
return Parser.run(() => {
|
|
359
372
|
const stack = parseSelector(selector),
|
|
360
373
|
/** @type {Set<string>} */
|
|
361
|
-
pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step
|
|
374
|
+
pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step[0] === ':'));
|
|
362
375
|
if (pseudos.size > 0) {
|
|
363
376
|
Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
|
|
364
377
|
}
|
|
@@ -521,204 +534,74 @@ class AstElement extends AstNode {
|
|
|
521
534
|
return String(this).split('\n', n + 1).at(-1);
|
|
522
535
|
}
|
|
523
536
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
* @complexity `n`
|
|
528
|
-
*/
|
|
529
|
-
posFromIndex(index) {
|
|
530
|
-
if (typeof index !== 'number') {
|
|
531
|
-
this.typeError('posFromIndex', 'Number');
|
|
532
|
-
}
|
|
533
|
-
const text = String(this);
|
|
534
|
-
if (index >= -text.length && index < text.length && Number.isInteger(index)) {
|
|
535
|
-
const lines = text.slice(0, index).split('\n');
|
|
536
|
-
return {top: lines.length - 1, left: lines.at(-1).length};
|
|
537
|
-
}
|
|
538
|
-
return undefined;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* 将行列号转换为字符位置
|
|
543
|
-
* @param {number} top 行号
|
|
544
|
-
* @param {number} left 列号
|
|
545
|
-
* @complexity `n`
|
|
546
|
-
*/
|
|
547
|
-
indexFromPos(top, left) {
|
|
548
|
-
if (typeof top !== 'number' || typeof left !== 'number') {
|
|
549
|
-
this.typeError('indexFromPos', 'Number');
|
|
550
|
-
}
|
|
551
|
-
const lines = String(this).split('\n');
|
|
552
|
-
return top >= 0 && left >= 0 && Number.isInteger(top) && Number.isInteger(left)
|
|
553
|
-
&& lines.length >= top + 1 && lines[top].length >= left
|
|
554
|
-
? lines.slice(0, top).reduce((acc, curLine) => acc + curLine.length + 1, 0) + left
|
|
555
|
-
: undefined;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* 获取行数和最后一行的列数
|
|
560
|
-
* @complexity `n`
|
|
561
|
-
*/
|
|
562
|
-
#getDimension() {
|
|
563
|
-
const lines = String(this).split('\n');
|
|
564
|
-
return {height: lines.length, width: lines.at(-1).length};
|
|
565
|
-
}
|
|
537
|
+
static lintIgnoredHidden = new Set(['noinclude', 'double-underscore', 'hidden']);
|
|
538
|
+
static lintIgnoredSyntax = new Set(['magic-word-name', 'heading-trail', 'table-syntax']);
|
|
539
|
+
static lintIgnoredExt = new Set(['nowiki', 'pre', 'syntaxhighlight', 'source', 'math', 'timeline']);
|
|
566
540
|
|
|
567
541
|
/**
|
|
568
|
-
*
|
|
569
|
-
* @param {number
|
|
570
|
-
* @complexity `n`
|
|
542
|
+
* Linter
|
|
543
|
+
* @param {number} start 起始位置
|
|
571
544
|
*/
|
|
572
|
-
|
|
573
|
-
if (
|
|
574
|
-
this.
|
|
545
|
+
lint(start = 0) {
|
|
546
|
+
if (AstElement.lintIgnoredHidden.has(this.type) || AstElement.lintIgnoredSyntax.has(this.type)
|
|
547
|
+
|| this.type === 'ext-inner' && AstElement.lintIgnoredExt.has(this.name)
|
|
548
|
+
) {
|
|
549
|
+
return [];
|
|
575
550
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
* @param {this} parent 父节点
|
|
582
|
-
* @returns {number}
|
|
583
|
-
*/
|
|
584
|
-
const getIndex = (end, parent) => childNodes.slice(0, end).reduce(
|
|
585
|
-
(acc, cur, i) => acc + String(cur).length + parent.getGaps(i),
|
|
586
|
-
0,
|
|
587
|
-
) + parent.getPadding();
|
|
588
|
-
if (j === undefined) {
|
|
589
|
-
const {parentNode} = this;
|
|
590
|
-
if (!parentNode) {
|
|
591
|
-
return 0;
|
|
592
|
-
}
|
|
593
|
-
({childNodes} = parentNode);
|
|
594
|
-
return getIndex(childNodes.indexOf(this), parentNode);
|
|
551
|
+
const /** @type {LintError[]} */ errors = [];
|
|
552
|
+
for (let i = 0, cur = start + this.getPadding(); i < this.childNodes.length; i++) {
|
|
553
|
+
const child = this.childNodes[i];
|
|
554
|
+
errors.push(...child.lint(cur));
|
|
555
|
+
cur += String(child).length + this.getGaps(i);
|
|
595
556
|
}
|
|
596
|
-
|
|
597
|
-
({childNodes} = this);
|
|
598
|
-
return getIndex(j, this);
|
|
557
|
+
return errors;
|
|
599
558
|
}
|
|
600
559
|
|
|
601
560
|
/**
|
|
602
|
-
*
|
|
603
|
-
* @
|
|
604
|
-
* @
|
|
605
|
-
*/
|
|
606
|
-
getAbsoluteIndex() {
|
|
607
|
-
const {parentNode} = this;
|
|
608
|
-
return parentNode ? parentNode.getAbsoluteIndex() + this.getRelativeIndex() : 0;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* 获取当前节点的相对位置,或其第`j`个子节点的相对位置
|
|
613
|
-
* @param {number|undefined} j 子节点序号
|
|
614
|
-
* @complexity `n`
|
|
561
|
+
* 以HTML格式打印
|
|
562
|
+
* @param {printOpt} opt 选项
|
|
563
|
+
* @returns {string}
|
|
615
564
|
*/
|
|
616
|
-
|
|
617
|
-
return
|
|
618
|
-
?
|
|
619
|
-
: this.
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* 获取当前节点的行列位置和大小
|
|
624
|
-
* @complexity `n`
|
|
625
|
-
*/
|
|
626
|
-
getBoundingClientRect() {
|
|
627
|
-
const root = this.getRootNode();
|
|
628
|
-
return {...this.#getDimension(), ...root.posFromIndex(this.getAbsoluteIndex())};
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/** 第一个子节点前的间距 */
|
|
632
|
-
getPadding() {
|
|
633
|
-
return 0;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/** 子节点间距 */
|
|
637
|
-
getGaps() {
|
|
638
|
-
return 0;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* 行数
|
|
643
|
-
* @complexity `n`
|
|
644
|
-
*/
|
|
645
|
-
get offsetHeight() {
|
|
646
|
-
return this.#getDimension().height;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* 最后一行的列数
|
|
651
|
-
* @complexity `n`
|
|
652
|
-
*/
|
|
653
|
-
get offsetWidth() {
|
|
654
|
-
return this.#getDimension().width;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* 行号
|
|
659
|
-
* @complexity `n`
|
|
660
|
-
*/
|
|
661
|
-
get offsetTop() {
|
|
662
|
-
return this.#getPosition().top;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* 列号
|
|
667
|
-
* @complexity `n`
|
|
668
|
-
*/
|
|
669
|
-
get offsetLeft() {
|
|
670
|
-
return this.#getPosition().left;
|
|
565
|
+
print(opt = {}) {
|
|
566
|
+
return this.childNodes.length === 0
|
|
567
|
+
? ''
|
|
568
|
+
: `<span class="wpb-${opt.class ?? this.type}">${print(this.childNodes, opt)}</span>`;
|
|
671
569
|
}
|
|
672
570
|
|
|
673
571
|
/**
|
|
674
|
-
*
|
|
675
|
-
* @
|
|
572
|
+
* 保存为JSON
|
|
573
|
+
* @param {string} file 文件名
|
|
574
|
+
* @returns {Record<string, any>}
|
|
676
575
|
*/
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
|
|
576
|
+
json(file) {
|
|
577
|
+
const {childNodes, ...prop} = this,
|
|
578
|
+
json = {
|
|
579
|
+
...prop,
|
|
580
|
+
childNodes: childNodes.map(child => child.type === 'text' ? String(child) : child.json()),
|
|
581
|
+
};
|
|
582
|
+
if (typeof file === 'string') {
|
|
583
|
+
fs.writeFileSync(
|
|
584
|
+
path.join(__dirname.slice(0, -4), 'printed', `${file}${file.endsWith('.json') ? '' : '.json'}`),
|
|
585
|
+
JSON.stringify(json, null, 2),
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return json;
|
|
691
589
|
}
|
|
692
590
|
|
|
693
591
|
/**
|
|
694
592
|
* 输出AST
|
|
695
|
-
* @
|
|
696
|
-
* @
|
|
697
|
-
* @param {T extends 'markup' ? number : string} depth 输出深度
|
|
698
|
-
* @returns {T extends 'markup' ? void : Record<string, any>}
|
|
593
|
+
* @param {number} depth 当前深度
|
|
594
|
+
* @returns {void}
|
|
699
595
|
*/
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
const {childNodes, ...prop} = this,
|
|
703
|
-
json = {
|
|
704
|
-
...prop,
|
|
705
|
-
childNodes: childNodes.map(child => child.type === 'text' ? String(child) : child.print('json')),
|
|
706
|
-
};
|
|
707
|
-
if (typeof depth === 'string') {
|
|
708
|
-
fs.writeFileSync(
|
|
709
|
-
`${__dirname.slice(0, -3)}printed/${depth}${depth.endsWith('.json') ? '' : '.json'}`,
|
|
710
|
-
JSON.stringify(json, null, 2),
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
return json;
|
|
714
|
-
} else if (typeof depth !== 'number') {
|
|
596
|
+
echo(depth = 0) {
|
|
597
|
+
if (typeof depth !== 'number') {
|
|
715
598
|
this.typeError('print', 'Number');
|
|
716
599
|
}
|
|
717
600
|
const indent = ' '.repeat(depth),
|
|
718
601
|
str = String(this),
|
|
719
|
-
{childNodes, type
|
|
602
|
+
{childNodes, type} = this,
|
|
720
603
|
{length} = childNodes;
|
|
721
|
-
if (
|
|
604
|
+
if (childNodes.every(child => child.type === 'text' || !String(child))) {
|
|
722
605
|
console.log(`${indent}\x1B[32m<%s>\x1B[0m${noWrap(str)}\x1B[32m</%s>\x1B[0m`, type, type);
|
|
723
606
|
return undefined;
|
|
724
607
|
}
|
|
@@ -736,11 +619,12 @@ class AstElement extends AstNode {
|
|
|
736
619
|
} else if (child.type === 'text') {
|
|
737
620
|
console.log(`${indent} ${noWrap(String(child))}`);
|
|
738
621
|
} else {
|
|
739
|
-
child.
|
|
622
|
+
child.echo(depth + 1);
|
|
740
623
|
}
|
|
741
|
-
i += childStr.length
|
|
624
|
+
i += childStr.length;
|
|
742
625
|
if (gap) {
|
|
743
|
-
console.log(`${indent} ${noWrap(str.slice(i
|
|
626
|
+
console.log(`${indent} ${noWrap(str.slice(i, i + gap))}`);
|
|
627
|
+
i += gap;
|
|
744
628
|
}
|
|
745
629
|
}
|
|
746
630
|
if (i < str.length) {
|
package/lib/node.js
CHANGED
|
@@ -561,7 +561,9 @@ class AstNode {
|
|
|
561
561
|
const /** @type {AstText[]} */ childNodes = [...this.childNodes];
|
|
562
562
|
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
563
563
|
const {type, data} = childNodes[i];
|
|
564
|
-
if (
|
|
564
|
+
if (this.getGaps(i - 1)) {
|
|
565
|
+
//
|
|
566
|
+
} else if (data === '') {
|
|
565
567
|
childNodes.splice(i, 1);
|
|
566
568
|
} else if (type === 'text' && childNodes[i - 1]?.type === 'text') {
|
|
567
569
|
childNodes[i - 1].setAttribute('data', childNodes[i - 1].data + data);
|
|
@@ -579,6 +581,162 @@ class AstNode {
|
|
|
579
581
|
}
|
|
580
582
|
return parentNode ?? this;
|
|
581
583
|
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* 将字符位置转换为行列号
|
|
587
|
+
* @param {number} index 字符位置
|
|
588
|
+
* @complexity `n`
|
|
589
|
+
*/
|
|
590
|
+
posFromIndex(index) {
|
|
591
|
+
if (typeof index !== 'number') {
|
|
592
|
+
this.typeError('posFromIndex', 'Number');
|
|
593
|
+
}
|
|
594
|
+
const str = String(this);
|
|
595
|
+
if (index >= -str.length && index <= str.length && Number.isInteger(index)) {
|
|
596
|
+
const lines = str.slice(0, index).split('\n');
|
|
597
|
+
return {top: lines.length - 1, left: lines.at(-1).length};
|
|
598
|
+
}
|
|
599
|
+
return undefined;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* 将行列号转换为字符位置
|
|
604
|
+
* @param {number} top 行号
|
|
605
|
+
* @param {number} left 列号
|
|
606
|
+
* @complexity `n`
|
|
607
|
+
*/
|
|
608
|
+
indexFromPos(top, left) {
|
|
609
|
+
if (typeof top !== 'number' || typeof left !== 'number') {
|
|
610
|
+
this.typeError('indexFromPos', 'Number');
|
|
611
|
+
}
|
|
612
|
+
const lines = String(this).split('\n');
|
|
613
|
+
return top >= 0 && left >= 0 && Number.isInteger(top) && Number.isInteger(left)
|
|
614
|
+
&& lines.length >= top + 1 && lines[top].length >= left
|
|
615
|
+
? lines.slice(0, top).reduce((acc, curLine) => acc + curLine.length + 1, 0) + left
|
|
616
|
+
: undefined;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* 获取行数和最后一行的列数
|
|
621
|
+
* @complexity `n`
|
|
622
|
+
*/
|
|
623
|
+
#getDimension() {
|
|
624
|
+
const lines = String(this).split('\n');
|
|
625
|
+
return {height: lines.length, width: lines.at(-1).length};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** 第一个子节点前的间距 */
|
|
629
|
+
getPadding() {
|
|
630
|
+
return 0;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** 子节点间距 */
|
|
634
|
+
getGaps() {
|
|
635
|
+
return 0;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* 获取当前节点的相对字符位置,或其第`j`个子节点的相对字符位置
|
|
640
|
+
* @param {number|undefined} j 子节点序号
|
|
641
|
+
* @complexity `n`
|
|
642
|
+
*/
|
|
643
|
+
getRelativeIndex(j) {
|
|
644
|
+
if (j !== undefined && typeof j !== 'number') {
|
|
645
|
+
this.typeError('getRelativeIndex', 'Number');
|
|
646
|
+
}
|
|
647
|
+
let /** @type {this[]} */ childNodes;
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* 获取子节点相对于父节点的字符位置,使用前需要先给`childNodes`赋值
|
|
651
|
+
* @param {number} end 子节点序号
|
|
652
|
+
* @param {this} parent 父节点
|
|
653
|
+
* @returns {number}
|
|
654
|
+
*/
|
|
655
|
+
const getIndex = (end, parent) => childNodes.slice(0, end).reduce(
|
|
656
|
+
(acc, cur, i) => acc + String(cur).length + parent.getGaps(i),
|
|
657
|
+
0,
|
|
658
|
+
) + parent.getPadding();
|
|
659
|
+
if (j === undefined) {
|
|
660
|
+
const {parentNode} = this;
|
|
661
|
+
if (parentNode) {
|
|
662
|
+
({childNodes} = parentNode);
|
|
663
|
+
return getIndex(childNodes.indexOf(this), parentNode);
|
|
664
|
+
}
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
this.getAttribute('verifyChild')(j, 1);
|
|
668
|
+
({childNodes} = this);
|
|
669
|
+
return getIndex(j, this);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* 获取当前节点的绝对位置
|
|
674
|
+
* @returns {number}
|
|
675
|
+
* @complexity `n`
|
|
676
|
+
*/
|
|
677
|
+
getAbsoluteIndex() {
|
|
678
|
+
const {parentNode} = this;
|
|
679
|
+
return parentNode ? parentNode.getAbsoluteIndex() + this.getRelativeIndex() : 0;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* 获取当前节点的相对位置,或其第`j`个子节点的相对位置
|
|
684
|
+
* @param {number|undefined} j 子节点序号
|
|
685
|
+
* @complexity `n`
|
|
686
|
+
*/
|
|
687
|
+
#getPosition(j) {
|
|
688
|
+
return j === undefined
|
|
689
|
+
? this.parentNode?.posFromIndex(this.getRelativeIndex()) ?? {top: 0, left: 0}
|
|
690
|
+
: this.posFromIndex(this.getRelativeIndex(j));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* 获取当前节点的行列位置和大小
|
|
695
|
+
* @complexity `n`
|
|
696
|
+
*/
|
|
697
|
+
getBoundingClientRect() {
|
|
698
|
+
return {...this.#getDimension(), ...this.getRootNode().posFromIndex(this.getAbsoluteIndex())};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* 行数
|
|
703
|
+
* @complexity `n`
|
|
704
|
+
*/
|
|
705
|
+
get offsetHeight() {
|
|
706
|
+
return this.#getDimension().height;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* 最后一行的列数
|
|
711
|
+
* @complexity `n`
|
|
712
|
+
*/
|
|
713
|
+
get offsetWidth() {
|
|
714
|
+
return this.#getDimension().width;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* 相对于父容器的行号
|
|
719
|
+
* @complexity `n`
|
|
720
|
+
*/
|
|
721
|
+
get offsetTop() {
|
|
722
|
+
return this.#getPosition().top;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* 相对于父容器的列号
|
|
727
|
+
* @complexity `n`
|
|
728
|
+
*/
|
|
729
|
+
get offsetLeft() {
|
|
730
|
+
return this.#getPosition().left;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* 位置、大小和padding
|
|
735
|
+
* @complexity `n`
|
|
736
|
+
*/
|
|
737
|
+
get style() {
|
|
738
|
+
return {...this.#getPosition(), ...this.#getDimension(), padding: this.getPadding()};
|
|
739
|
+
}
|
|
582
740
|
}
|
|
583
741
|
|
|
584
742
|
Parser.classes.AstNode = __filename;
|
package/lib/ranges.js
CHANGED
|
@@ -66,8 +66,7 @@ class Range {
|
|
|
66
66
|
* @complexity `n`
|
|
67
67
|
*/
|
|
68
68
|
applyTo(arr) {
|
|
69
|
-
return new Array(typeof arr === 'number' ? arr : arr.length).fill().map((_, i) => i)
|
|
70
|
-
.slice(this.start, this.end)
|
|
69
|
+
return new Array(typeof arr === 'number' ? arr : arr.length).fill().map((_, i) => i).slice(this.start, this.end)
|
|
71
70
|
.filter((_, j) => j % this.step === 0);
|
|
72
71
|
}
|
|
73
72
|
}
|
package/lib/text.js
CHANGED
|
@@ -33,6 +33,7 @@ class AstText extends AstNode {
|
|
|
33
33
|
* @template {string} T
|
|
34
34
|
* @param {T} key 属性键
|
|
35
35
|
* @returns {TokenAttribute<T>}
|
|
36
|
+
* @throws `Error` 文本节点没有子节点
|
|
36
37
|
*/
|
|
37
38
|
getAttribute(key) {
|
|
38
39
|
return key === 'verifyChild'
|
|
@@ -70,7 +71,6 @@ class AstText extends AstNode {
|
|
|
70
71
|
/**
|
|
71
72
|
* 在后方添加字符串
|
|
72
73
|
* @param {string} text 添加的字符串
|
|
73
|
-
* @throws `Error` 禁止外部调用
|
|
74
74
|
*/
|
|
75
75
|
appendData(text) {
|
|
76
76
|
this.#setData(this.data + text);
|
|
@@ -80,8 +80,6 @@ class AstText extends AstNode {
|
|
|
80
80
|
* 删减字符串
|
|
81
81
|
* @param {number} offset 起始位置
|
|
82
82
|
* @param {number} count 删减字符数
|
|
83
|
-
* @throws `RangeError` 错误的删减位置
|
|
84
|
-
* @throws `Error` 禁止外部调用
|
|
85
83
|
*/
|
|
86
84
|
deleteData(offset, count) {
|
|
87
85
|
this.#setData(this.data.slice(0, offset) + this.data.slice(offset + count));
|
|
@@ -91,8 +89,6 @@ class AstText extends AstNode {
|
|
|
91
89
|
* 插入字符串
|
|
92
90
|
* @param {number} offset 插入位置
|
|
93
91
|
* @param {string} text 待插入的字符串
|
|
94
|
-
* @throws `RangeError` 错误的插入位置
|
|
95
|
-
* @throws `Error` 禁止外部调用
|
|
96
92
|
*/
|
|
97
93
|
insertData(offset, text) {
|
|
98
94
|
this.#setData(this.data.slice(0, offset) + text + this.data.slice(offset));
|
|
@@ -101,7 +97,6 @@ class AstText extends AstNode {
|
|
|
101
97
|
/**
|
|
102
98
|
* 替换字符串
|
|
103
99
|
* @param {string} text 替换的字符串
|
|
104
|
-
* @throws `Error` 禁止外部调用
|
|
105
100
|
*/
|
|
106
101
|
replaceData(text = '') {
|
|
107
102
|
this.#setData(text);
|
|
@@ -140,6 +135,40 @@ class AstText extends AstNode {
|
|
|
140
135
|
parentNode.setAttribute('childNodes', childNodes);
|
|
141
136
|
return newText;
|
|
142
137
|
}
|
|
138
|
+
|
|
139
|
+
static errorSyntax = /[{}]+|\[{2,}|\[(?!(?:(?!https?\b)[^[])*\])|(?<=^|\])([^[]*?)\]+|<(?=\s*\/?\w+[\s/>])/giu;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Linter
|
|
143
|
+
* @param {number} start 起始位置
|
|
144
|
+
* @returns {LintError[]}
|
|
145
|
+
*/
|
|
146
|
+
lint(start = 0) {
|
|
147
|
+
const {data} = this,
|
|
148
|
+
errors = [...data.matchAll(AstText.errorSyntax)];
|
|
149
|
+
if (errors.length > 0) {
|
|
150
|
+
const {top, left} = this.getRootNode().posFromIndex(start);
|
|
151
|
+
return errors.map(({0: error, 1: prefix, index}) => {
|
|
152
|
+
if (prefix) {
|
|
153
|
+
index += prefix.length;
|
|
154
|
+
error = error.slice(prefix.length);
|
|
155
|
+
}
|
|
156
|
+
const lines = data.slice(0, index).split('\n'),
|
|
157
|
+
startLine = lines.length + top - 1,
|
|
158
|
+
{length} = lines.at(-1),
|
|
159
|
+
startCol = lines.length > 1 ? length : left + length;
|
|
160
|
+
return {
|
|
161
|
+
message: `孤立的"${error[0]}"`,
|
|
162
|
+
severity: error[0] === '{' || error[0] === '}' ? 'error' : 'warning',
|
|
163
|
+
startLine,
|
|
164
|
+
endLine: startLine,
|
|
165
|
+
startCol,
|
|
166
|
+
endCol: startCol + error.length,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
143
172
|
}
|
|
144
173
|
|
|
145
174
|
Parser.classes.AstText = __filename;
|
package/lib/title.js
CHANGED
|
@@ -55,7 +55,7 @@ class Title {
|
|
|
55
55
|
}
|
|
56
56
|
this.main = title && `${title[0].toUpperCase()}${title.slice(1)}`;
|
|
57
57
|
this.prefix = `${namespace}${namespace && ':'}`;
|
|
58
|
-
this.title = `${iw ? `${this.interwiki}:` : ''}${this.prefix}${this.main}`;
|
|
58
|
+
this.title = `${iw ? `${this.interwiki}:` : ''}${this.prefix}${this.main.replaceAll(' ', '_')}`;
|
|
59
59
|
this.valid = Boolean(this.main || this.fragment) && !/\0\d+[eh!+-]\x7F|[<>[\]{}|]/u.test(this.title);
|
|
60
60
|
}
|
|
61
61
|
|
package/mixin/fixedToken.js
CHANGED
|
@@ -28,11 +28,11 @@ const fixedToken = Constructor => class extends Constructor {
|
|
|
28
28
|
* @throws `Error`
|
|
29
29
|
*/
|
|
30
30
|
insertAt(token, i = this.childNodes.length) {
|
|
31
|
-
if (
|
|
32
|
-
|
|
31
|
+
if (Parser.running) {
|
|
32
|
+
super.insertAt(token, i);
|
|
33
|
+
return token;
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
return token;
|
|
35
|
+
throw new Error(`${this.constructor.name} 不可插入元素!`);
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|