wikiparser-node 0.0.2 → 0.0.3
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/README.md +15 -5
- package/errors/README +1 -0
- package/index.js +42 -9
- package/lib/element.js +70 -12
- package/lib/node.js +17 -9
- package/package.json +1 -1
- package/parser/externalLinks.js +1 -1
- package/parser/table.js +2 -2
- package/printed/README +1 -0
- package/src/arg.js +3 -3
- package/src/attribute.js +6 -6
- package/src/extLink.js +3 -2
- package/src/heading.js +2 -3
- package/src/html.js +4 -3
- package/src/imageParameter.js +5 -6
- package/src/index.js +5 -5
- package/src/link/file.js +7 -7
- package/src/link/index.js +2 -4
- package/src/magicLink.js +2 -3
- package/src/nowiki/comment.js +1 -1
- package/src/parameter.js +3 -3
- package/src/table/index.js +26 -21
- package/src/table/td.js +2 -2
- package/src/table/tr.js +2 -6
- package/src/tagPair/index.js +1 -1
- package/src/transclude.js +14 -11
- package/tool/index.js +50 -40
- package/typings/index.d.ts +1 -0
- package/util/debug.js +3 -3
- package/util/string.js +4 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
10. [findEnclosingHtml](#token.findenclosinghtml)
|
|
30
30
|
11. [getCategories](#token.getcategories)
|
|
31
31
|
12. [redoQuotes](#token.redoquotes)
|
|
32
|
+
13. [print](#token.print)
|
|
32
33
|
2. [实例属性](#token.instance.properties)
|
|
33
34
|
1. [type](#token.type)
|
|
34
35
|
3. [原型属性](#token.prototype.properties)
|
|
@@ -426,6 +427,15 @@ assert.deepStrictEqual(root.childNodes, ["'", root.firstElementChild, "a", root.
|
|
|
426
427
|
var root = Parser.parse(wikitext);
|
|
427
428
|
assert(root.type === 'root');
|
|
428
429
|
```
|
|
430
|
+
|
|
431
|
+
**print**(format?: 'markup'\|'json' = 'markup'): void\|object<a id="token.print"></a>
|
|
432
|
+
- 打印解析生成的 AST。
|
|
433
|
+
|
|
434
|
+
```js
|
|
435
|
+
var root = Parser.parse("<ref>{{T|<br>\n----\n[[File:F|thumb|''[//example.net]'']]}}</ref>");
|
|
436
|
+
root.print();
|
|
437
|
+
root.print('json', 'example'); // JSON格式的输出结果将保存至 /printed/example.json 文件
|
|
438
|
+
```
|
|
429
439
|
</details>
|
|
430
440
|
|
|
431
441
|
## 原型属性<a id="token.prototype.properties"></a>
|
|
@@ -930,7 +940,7 @@ param.setValue(' 2 ');
|
|
|
930
940
|
assert(root.toString() === '{{a|b= 2 }}'); // setValue方法总是保留空白字符,哪怕是无效的
|
|
931
941
|
```
|
|
932
942
|
|
|
933
|
-
**rename**(key: string, force
|
|
943
|
+
**rename**(key: string, force?: boolean = false): void<a id="parametertoken.rename"></a>
|
|
934
944
|
- 重命名参数,可选是否在导致重复参数时抛出错误。
|
|
935
945
|
|
|
936
946
|
```js
|
|
@@ -1164,7 +1174,7 @@ assert(root.toString() === '{|\n!colspan=2|\n|-\n| \n!\n|}');
|
|
|
1164
1174
|
|:-:|:-:|:-:|
|
|
1165
1175
|
|<table><tr><td colspan=2>td</td></tr><tr><td>td</td><td>td</td></tr></table>|<table><tr><td colspan=2>td</td></tr><tr><td>td</td><th>th</th></tr></table>|<table><tr><th colspan=2>th</th></tr><tr><td>td</td><th>th</th></tr></table>|
|
|
1166
1176
|
|
|
1167
|
-
**insertTableRow**(row: number, attr: Record\<string, string\|boolean>, inner?: string, subtype?: 'td'\|'th', innerAttr?: Record\<string, string\|boolean>): TrToken<a id="tabletoken.inserttablerow"></a>
|
|
1177
|
+
**insertTableRow**(row: number, attr: Record\<string, string\|boolean>, inner?: string, subtype?: 'td'\|'th' = 'td', innerAttr?: Record\<string, string\|boolean>): TrToken<a id="tabletoken.inserttablerow"></a>
|
|
1168
1178
|
- 插入空行或一行单元格。
|
|
1169
1179
|
|
|
1170
1180
|
```js
|
|
@@ -1179,7 +1189,7 @@ assert(root.toString() === '{|\n|a|| rowspan="3"|b||c\n|- class="tr"\n|-\n! clas
|
|
|
1179
1189
|
|:-:|:-:|
|
|
1180
1190
|
|<table><tr><td>a</td><td rowspan=2>b</td><td>c</td></tr><tr><td>d</td><td>e</td></tr></table>|<table><tr><td>a</td><td rowspan=3>b</td><td>c</td></tr><tr><th>f</th><th>f</th></tr><tr><td>d</td><td>e</td></tr></table>|
|
|
1181
1191
|
|
|
1182
|
-
**insertTableCol**(x: number, inner: string, subtype
|
|
1192
|
+
**insertTableCol**(x: number, inner: string, subtype?: 'td'\|'th' = 'td', attr?: Record\<string, string\|boolean>): void<a id="tabletoken.inserttablecol"></a>
|
|
1183
1193
|
- 插入一列单元格。
|
|
1184
1194
|
|
|
1185
1195
|
```js
|
|
@@ -1193,7 +1203,7 @@ assert(root.toString() === '{|\n| colspan="3"|a\n|-\n|b\n! class="th"|d\n|c\n|}'
|
|
|
1193
1203
|
|:-:|:-:|
|
|
1194
1204
|
|<table><tr><td colspan=2 align="center">a</td></tr><tr><td>b</td><td>c</td></tr></table>|<table><tr><td colspan=3 align="center">a</td></tr><tr><td>b</td><th>d</th><td>c</td></tr></table>|
|
|
1195
1205
|
|
|
1196
|
-
**insertTableCell**(inner: string, coords: {row: number, column: number}\|{x: number, y: number}, subtype
|
|
1206
|
+
**insertTableCell**(inner: string, coords: {row: number, column: number}\|{x: number, y: number}, subtype?: 'td'\|'th' = 'td', attr?: Record\<string, string\|boolean>): [TdToken](#tdtoken)<a id="tabletoken.inserttablecell"></a>
|
|
1197
1207
|
- 插入一个单元格。
|
|
1198
1208
|
|
|
1199
1209
|
```js
|
|
@@ -1471,7 +1481,7 @@ link.asSelfLink();
|
|
|
1471
1481
|
assert(root.toString() === '[[#b]]');
|
|
1472
1482
|
```
|
|
1473
1483
|
|
|
1474
|
-
**setLinkText**(linkText
|
|
1484
|
+
**setLinkText**(linkText: string)<a id="linktoken.setlinktext"></a>
|
|
1475
1485
|
- 修改链接文本。
|
|
1476
1486
|
|
|
1477
1487
|
```js
|
package/errors/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
这里记录解析失败时处于半解析状态的维基文本以及错误信息。
|
package/index.js
CHANGED
|
@@ -108,7 +108,7 @@ const /** @type {Parser} */ Parser = {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
};
|
|
111
|
-
|
|
111
|
+
this.run(() => {
|
|
112
112
|
build(['title', 'fragment']);
|
|
113
113
|
});
|
|
114
114
|
}
|
|
@@ -119,25 +119,58 @@ const /** @type {Parser} */ Parser = {
|
|
|
119
119
|
|
|
120
120
|
parse(wikitext, include = false, maxStage = Parser.MAX_STAGE, config = Parser.getConfig()) {
|
|
121
121
|
const Token = require('./src');
|
|
122
|
+
let token;
|
|
122
123
|
this.run(() => {
|
|
123
124
|
if (typeof wikitext === 'string') {
|
|
124
|
-
|
|
125
|
-
} else if (
|
|
125
|
+
token = new Token(wikitext, config);
|
|
126
|
+
} else if (wikitext instanceof Token) {
|
|
127
|
+
token = wikitext;
|
|
128
|
+
wikitext = token.toString();
|
|
129
|
+
} else {
|
|
126
130
|
throw new TypeError('待解析的内容应为 String 或 Token!');
|
|
127
131
|
}
|
|
128
132
|
try {
|
|
129
|
-
|
|
133
|
+
token.parse(maxStage, include);
|
|
130
134
|
} catch (e) {
|
|
131
135
|
if (e instanceof Error) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
+
const file = `${__dirname}/errors/${new Date().toISOString()}`,
|
|
137
|
+
stage = token.getAttribute('stage');
|
|
138
|
+
fs.writeFileSync(file, stage === this.MAX_STAGE ? wikitext : token.toString());
|
|
139
|
+
fs.writeFileSync(`${file}.err`, e.stack);
|
|
140
|
+
fs.writeFileSync(`${file}.json`, JSON.stringify({
|
|
141
|
+
stage, include: token.getAttribute('include'), config: this.config,
|
|
142
|
+
}, null, '\t'));
|
|
136
143
|
}
|
|
137
144
|
throw e;
|
|
138
145
|
}
|
|
139
146
|
});
|
|
140
|
-
return
|
|
147
|
+
return token;
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
reparse(date) {
|
|
151
|
+
const path = `${__dirname}/errors/`,
|
|
152
|
+
main = fs.readdirSync(path).find(name => name.startsWith(date) && name.endsWith('Z'));
|
|
153
|
+
if (!main) {
|
|
154
|
+
throw new RangeError(`找不到对应时间戳的错误记录:${date}`);
|
|
155
|
+
}
|
|
156
|
+
const Token = require('./src'),
|
|
157
|
+
file = `${path}${main}`,
|
|
158
|
+
wikitext = fs.readFileSync(file, 'utf8'),
|
|
159
|
+
{stage, include, config} = require(`${file}.json`);
|
|
160
|
+
this.config = config;
|
|
161
|
+
return this.run(() => {
|
|
162
|
+
const halfParsed = stage < this.MAX_STAGE,
|
|
163
|
+
token = new Token(wikitext, this.getConfig(), halfParsed);
|
|
164
|
+
if (halfParsed) {
|
|
165
|
+
token.setAttribute('stage', stage).parseOnce(stage, include);
|
|
166
|
+
} else {
|
|
167
|
+
token.parse(undefined, include);
|
|
168
|
+
}
|
|
169
|
+
fs.unlinkSync(file);
|
|
170
|
+
fs.unlinkSync(`${file}.err`);
|
|
171
|
+
fs.unlinkSync(`${file}.json`);
|
|
172
|
+
return token;
|
|
173
|
+
});
|
|
141
174
|
},
|
|
142
175
|
|
|
143
176
|
getTool() {
|
package/lib/element.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
{
|
|
3
|
+
const fs = require('fs'),
|
|
4
|
+
{externalUse} = require('../util/debug'),
|
|
5
|
+
{toCase, noWrap} = require('../util/string'),
|
|
5
6
|
{nth} = require('./ranges'),
|
|
6
7
|
EventEmitter = require('events'),
|
|
7
8
|
AstNode = require('./node'),
|
|
@@ -93,7 +94,7 @@ class AstElement extends AstNode {
|
|
|
93
94
|
this.addEventListener(type, listener, options);
|
|
94
95
|
}
|
|
95
96
|
} else if (typeof types !== 'string' || typeof listener !== 'function') {
|
|
96
|
-
typeError(
|
|
97
|
+
this.typeError('addEventListener', 'String', 'Function');
|
|
97
98
|
} else {
|
|
98
99
|
this.#events[options?.once ? 'once' : 'on'](types, listener);
|
|
99
100
|
}
|
|
@@ -109,7 +110,7 @@ class AstElement extends AstNode {
|
|
|
109
110
|
this.removeEventListener(type, listener);
|
|
110
111
|
}
|
|
111
112
|
} else if (typeof types !== 'string' || typeof listener !== 'function') {
|
|
112
|
-
typeError(
|
|
113
|
+
this.typeError('removeEventListener', 'String', 'Function');
|
|
113
114
|
} else {
|
|
114
115
|
this.#events.off(types, listener);
|
|
115
116
|
}
|
|
@@ -122,7 +123,7 @@ class AstElement extends AstNode {
|
|
|
122
123
|
this.removeAllEventListeners(type);
|
|
123
124
|
}
|
|
124
125
|
} else if (types !== undefined && typeof types !== 'string') {
|
|
125
|
-
typeError(
|
|
126
|
+
this.typeError('removeAllEventListeners', 'String');
|
|
126
127
|
} else {
|
|
127
128
|
this.#events.removeAllListeners(types);
|
|
128
129
|
}
|
|
@@ -134,7 +135,7 @@ class AstElement extends AstNode {
|
|
|
134
135
|
*/
|
|
135
136
|
listEventListeners(type) {
|
|
136
137
|
if (typeof type !== 'string') {
|
|
137
|
-
typeError(
|
|
138
|
+
this.typeError('listEventListeners', 'String');
|
|
138
139
|
}
|
|
139
140
|
return this.#events.listeners(type);
|
|
140
141
|
}
|
|
@@ -145,7 +146,7 @@ class AstElement extends AstNode {
|
|
|
145
146
|
*/
|
|
146
147
|
dispatchEvent(e, data) {
|
|
147
148
|
if (!(e instanceof Event)) {
|
|
148
|
-
typeError(
|
|
149
|
+
this.typeError('dispatchEvent', 'Event');
|
|
149
150
|
} else if (!e.target) { // 初始化
|
|
150
151
|
Object.defineProperty(e, 'target', {value: this, enumerable: true});
|
|
151
152
|
e.stopPropagation = function() {
|
|
@@ -357,7 +358,7 @@ class AstElement extends AstNode {
|
|
|
357
358
|
*/
|
|
358
359
|
matches(selector = '', simple = false) {
|
|
359
360
|
if (typeof selector !== 'string') {
|
|
360
|
-
typeError(
|
|
361
|
+
this.typeError('matches', 'String');
|
|
361
362
|
} else if (!selector.trim()) {
|
|
362
363
|
return true;
|
|
363
364
|
}
|
|
@@ -479,7 +480,7 @@ class AstElement extends AstNode {
|
|
|
479
480
|
*/
|
|
480
481
|
comparePosition(other) {
|
|
481
482
|
if (!(other instanceof AstElement)) {
|
|
482
|
-
typeError(
|
|
483
|
+
this.typeError('comparePosition', 'AstElement');
|
|
483
484
|
} else if (this === other) {
|
|
484
485
|
return 0;
|
|
485
486
|
} else if (this.contains(other)) {
|
|
@@ -541,7 +542,7 @@ class AstElement extends AstNode {
|
|
|
541
542
|
*/
|
|
542
543
|
posFromIndex(index) {
|
|
543
544
|
if (typeof index !== 'number') {
|
|
544
|
-
typeError(
|
|
545
|
+
this.typeError('posFromIndex', 'Number');
|
|
545
546
|
}
|
|
546
547
|
const text = this.toString();
|
|
547
548
|
if (index < -text.length || index >= text.length || !Number.isInteger(index)) {
|
|
@@ -558,7 +559,7 @@ class AstElement extends AstNode {
|
|
|
558
559
|
*/
|
|
559
560
|
indexFromPos(top, left) {
|
|
560
561
|
if (typeof top !== 'number' || typeof left !== 'number') {
|
|
561
|
-
typeError(
|
|
562
|
+
this.typeError('indexFromPos', 'Number');
|
|
562
563
|
} else if (top < 0 || left < 0 || !Number.isInteger(top) || !Number.isInteger(left)) {
|
|
563
564
|
return;
|
|
564
565
|
}
|
|
@@ -582,7 +583,7 @@ class AstElement extends AstNode {
|
|
|
582
583
|
*/
|
|
583
584
|
getRelativeIndex(j) {
|
|
584
585
|
if (j !== undefined && typeof j !== 'number') {
|
|
585
|
-
typeError(
|
|
586
|
+
this.typeError('getRelativeIndex', 'Number');
|
|
586
587
|
}
|
|
587
588
|
let /** @type {(string|this)[]} */ childNodes;
|
|
588
589
|
/**
|
|
@@ -684,6 +685,63 @@ class AstElement extends AstNode {
|
|
|
684
685
|
return node ? [[index + this.getRelativeIndex(i), node]] : [];
|
|
685
686
|
});
|
|
686
687
|
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* @template {'markup'|'json'} T
|
|
691
|
+
* @param {T} format
|
|
692
|
+
* @param {T extends 'markup' ? number : string} depth
|
|
693
|
+
* @returns {T extends 'markup' ? void : Record<string, any>}
|
|
694
|
+
*/
|
|
695
|
+
print(format = 'markup', depth = 0) {
|
|
696
|
+
if (format === 'json') {
|
|
697
|
+
const {childNodes, ...prop} = this,
|
|
698
|
+
json = {
|
|
699
|
+
...prop,
|
|
700
|
+
childNodes: childNodes.map(child => typeof child === 'string' ? child : child.print('json')),
|
|
701
|
+
};
|
|
702
|
+
if (typeof depth === 'string') {
|
|
703
|
+
fs.writeFileSync(
|
|
704
|
+
`${__dirname.slice(0, -3)}printed/${depth}${depth.endsWith('.json') ? '' : '.json'}`,
|
|
705
|
+
JSON.stringify(json, null, 2),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
return json;
|
|
709
|
+
} else if (typeof depth !== 'number') {
|
|
710
|
+
this.typeError('print', 'Number');
|
|
711
|
+
}
|
|
712
|
+
const indent = ' '.repeat(depth),
|
|
713
|
+
str = this.toString(),
|
|
714
|
+
{childNodes, type, firstChild} = this,
|
|
715
|
+
{length} = childNodes;
|
|
716
|
+
if (!str || length === 0 || typeof firstChild === 'string' && firstChild === str) {
|
|
717
|
+
console.log(`${indent}\x1b[32m<%s>\x1b[0m${noWrap(str)}\x1b[32m</%s>\x1b[0m`, type, type);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
Parser.info(`${indent}<${type}>`);
|
|
721
|
+
let i = this.getPadding();
|
|
722
|
+
if (i) {
|
|
723
|
+
console.log(`${indent} ${noWrap(str.slice(0, i))}`);
|
|
724
|
+
}
|
|
725
|
+
for (const [j, child] of childNodes.entries()) {
|
|
726
|
+
const childStr = String(child),
|
|
727
|
+
gap = j === length - 1 ? 0 : this.getGaps(j);
|
|
728
|
+
if (!childStr) {
|
|
729
|
+
// pass
|
|
730
|
+
} else if (typeof child === 'string') {
|
|
731
|
+
console.log(`${indent} ${noWrap(child)}`);
|
|
732
|
+
} else {
|
|
733
|
+
child.print('markup', depth + 1);
|
|
734
|
+
}
|
|
735
|
+
i += childStr.length + gap;
|
|
736
|
+
if (gap) {
|
|
737
|
+
console.log(`${indent} ${noWrap(str.slice(i - gap, i))}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (i < str.length) {
|
|
741
|
+
console.log(`${indent} ${noWrap(str.slice(i))}`);
|
|
742
|
+
}
|
|
743
|
+
Parser.info(`${indent}</${type}>`);
|
|
744
|
+
}
|
|
687
745
|
}
|
|
688
746
|
|
|
689
747
|
Parser.classes.AstElement = __filename;
|
package/lib/node.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {typeError, externalUse} = require('../util/debug'),
|
|
4
|
-
{text} = require('../util/string'),
|
|
4
|
+
{text, noWrap} = require('../util/string'),
|
|
5
5
|
assert = require('assert/strict'),
|
|
6
6
|
/** @type {Parser} */ Parser = require('..');
|
|
7
7
|
|
|
@@ -34,6 +34,14 @@ class AstNode {
|
|
|
34
34
|
throw new Error(`${this.constructor.name}.${method} 方法仅用于代码调试!`);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} method
|
|
39
|
+
* @param {...string} types
|
|
40
|
+
*/
|
|
41
|
+
typeError(method, ...types) {
|
|
42
|
+
return typeError(this.constructor, method, ...types);
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
/** @param {string|string[]} keys */
|
|
38
46
|
seal(keys) {
|
|
39
47
|
if (!Parser.running && !Parser.debugging) {
|
|
@@ -68,7 +76,7 @@ class AstNode {
|
|
|
68
76
|
/** @param {PropertyKey} key */
|
|
69
77
|
hasAttribute(key) {
|
|
70
78
|
if (!['string', 'number', 'symbol'].includes(typeof key)) {
|
|
71
|
-
typeError(
|
|
79
|
+
this.typeError('hasAttribute', 'String', 'Number', 'Symbol');
|
|
72
80
|
}
|
|
73
81
|
return key in this;
|
|
74
82
|
}
|
|
@@ -142,7 +150,7 @@ class AstNode {
|
|
|
142
150
|
*/
|
|
143
151
|
toggleAttribute(key, force) {
|
|
144
152
|
if (force !== undefined && typeof force !== 'boolean') {
|
|
145
|
-
typeError(
|
|
153
|
+
this.typeError('toggleAttribute', 'Boolean');
|
|
146
154
|
} else if (this.hasAttribute(key) && typeof this[key] !== 'boolean') {
|
|
147
155
|
throw new RangeError(`${key} 属性的值不为 Boolean!`);
|
|
148
156
|
}
|
|
@@ -175,7 +183,7 @@ class AstNode {
|
|
|
175
183
|
*/
|
|
176
184
|
contains(node) {
|
|
177
185
|
if (!(node instanceof AstNode)) {
|
|
178
|
-
typeError(
|
|
186
|
+
this.typeError('contains', 'Token');
|
|
179
187
|
}
|
|
180
188
|
return node === this || this.childNodes.some(child => child instanceof AstNode && child.contains(node));
|
|
181
189
|
}
|
|
@@ -185,7 +193,7 @@ class AstNode {
|
|
|
185
193
|
if (!Parser.debugging && externalUse('verifyChild')) {
|
|
186
194
|
this.debugOnly('verifyChild');
|
|
187
195
|
} else if (typeof i !== 'number') {
|
|
188
|
-
typeError(
|
|
196
|
+
this.typeError('verifyChild', 'Number');
|
|
189
197
|
}
|
|
190
198
|
const {length} = this.childNodes;
|
|
191
199
|
if (i < -length || i >= length + addition || !Number.isInteger(i)) {
|
|
@@ -216,7 +224,7 @@ class AstNode {
|
|
|
216
224
|
Parser.error('找不到子节点!', node);
|
|
217
225
|
throw new RangeError('找不到子节点!');
|
|
218
226
|
} else if (typeof node === 'string' && childNodes.lastIndexOf(node) > i) {
|
|
219
|
-
throw new RangeError(`重复的纯文本节点 ${node
|
|
227
|
+
throw new RangeError(`重复的纯文本节点 ${noWrap(node)}!`);
|
|
220
228
|
}
|
|
221
229
|
return i;
|
|
222
230
|
}
|
|
@@ -238,7 +246,7 @@ class AstNode {
|
|
|
238
246
|
*/
|
|
239
247
|
insertAt(node, i = this.childNodes.length) {
|
|
240
248
|
if (typeof node !== 'string' && !(node instanceof AstNode)) {
|
|
241
|
-
typeError(
|
|
249
|
+
this.typeError('insertAt', 'String', 'Token');
|
|
242
250
|
} else if (node instanceof AstNode && node.contains(this)) {
|
|
243
251
|
Parser.error('不能插入祖先节点!', node);
|
|
244
252
|
throw new RangeError('不能插入祖先节点!');
|
|
@@ -297,7 +305,7 @@ class AstNode {
|
|
|
297
305
|
/** @param {string} str */
|
|
298
306
|
setText(str, i = 0) {
|
|
299
307
|
if (typeof str !== 'string') {
|
|
300
|
-
typeError(
|
|
308
|
+
this.typeError('setText', 'String');
|
|
301
309
|
}
|
|
302
310
|
this.verifyChild(i);
|
|
303
311
|
const oldText = this.childNodes.at(i);
|
|
@@ -316,7 +324,7 @@ class AstNode {
|
|
|
316
324
|
*/
|
|
317
325
|
splitText(i, offset) {
|
|
318
326
|
if (typeof offset !== 'number') {
|
|
319
|
-
typeError(
|
|
327
|
+
this.typeError('splitText', 'Number');
|
|
320
328
|
}
|
|
321
329
|
this.verifyChild(i);
|
|
322
330
|
const oldText = this.childNodes.at(i);
|
package/package.json
CHANGED
package/parser/externalLinks.js
CHANGED
|
@@ -10,7 +10,7 @@ const {extUrlChar} = require('../util/string'),
|
|
|
10
10
|
const parseExternalLinks = (firstChild, config = Parser.getConfig(), accum = []) => {
|
|
11
11
|
const ExtLinkToken = require('../src/extLink'),
|
|
12
12
|
regex = new RegExp(
|
|
13
|
-
`\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]
|
|
13
|
+
`\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]\x01-\x08\x0a-\x1f\ufffd]*)\\]`,
|
|
14
14
|
'gui',
|
|
15
15
|
);
|
|
16
16
|
return firstChild.replace(regex, /** @type {function(...string): string} */ (_, url, space, text) => {
|
package/parser/table.js
CHANGED
|
@@ -54,10 +54,10 @@ const parseTable = ({firstChild, type}, config = Parser.getConfig(), accum = [])
|
|
|
54
54
|
}
|
|
55
55
|
const [, closing, row, cell, attr] = matches;
|
|
56
56
|
if (closing) {
|
|
57
|
-
while (top
|
|
57
|
+
while (!(top instanceof TableToken)) {
|
|
58
58
|
top = stack.pop();
|
|
59
59
|
}
|
|
60
|
-
top.close(`\n${spaces}${closing}
|
|
60
|
+
top.close(`\n${spaces}${closing}`, true);
|
|
61
61
|
push(attr, stack.at(-1));
|
|
62
62
|
} else if (row) {
|
|
63
63
|
if (top.type === 'td') {
|
package/printed/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
这里存放以 JSON 格式打印的 AST。
|
package/src/arg.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {text} = require('../util/string'),
|
|
3
|
+
const {text, noWrap} = require('../util/string'),
|
|
4
4
|
/** @type {Parser} */ Parser = require('..'),
|
|
5
5
|
Token = require('.');
|
|
6
6
|
|
|
@@ -118,7 +118,7 @@ class ArgToken extends Token {
|
|
|
118
118
|
const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
119
119
|
{childNodes: {length}, firstElementChild} = root;
|
|
120
120
|
if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childElementCount !== 1) {
|
|
121
|
-
throw new SyntaxError(`非法的参数名称:${name
|
|
121
|
+
throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
|
|
122
122
|
}
|
|
123
123
|
const newName = firstElementChild.firstElementChild;
|
|
124
124
|
root.destroy();
|
|
@@ -132,7 +132,7 @@ class ArgToken extends Token {
|
|
|
132
132
|
const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
133
133
|
{childNodes: {length}, firstElementChild} = root;
|
|
134
134
|
if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childElementCount !== 2) {
|
|
135
|
-
throw new SyntaxError(`非法的参数预设值:${value
|
|
135
|
+
throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
|
|
136
136
|
}
|
|
137
137
|
const [, oldDefault] = this.children,
|
|
138
138
|
newDefault = firstElementChild.lastElementChild;
|
package/src/attribute.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {externalUse} = require('../util/debug'),
|
|
4
4
|
{toCase, removeComment} = require('../util/string'),
|
|
5
5
|
/** @type {Parser} */ Parser = require('..'),
|
|
6
6
|
Token = require('.');
|
|
@@ -147,7 +147,7 @@ class AttributeToken extends Token {
|
|
|
147
147
|
/** @param {string} key */
|
|
148
148
|
hasAttr(key) {
|
|
149
149
|
if (typeof key !== 'string') {
|
|
150
|
-
typeError(
|
|
150
|
+
this.typeError('hasAttr', 'String');
|
|
151
151
|
}
|
|
152
152
|
return this.#attr.has(key.toLowerCase().trim());
|
|
153
153
|
}
|
|
@@ -161,7 +161,7 @@ class AttributeToken extends Token {
|
|
|
161
161
|
if (key === undefined) {
|
|
162
162
|
return Object.fromEntries(this.#attr);
|
|
163
163
|
} else if (typeof key !== 'string') {
|
|
164
|
-
typeError(
|
|
164
|
+
this.typeError('getAttr', 'String');
|
|
165
165
|
}
|
|
166
166
|
return this.#attr.get(key.toLowerCase().trim());
|
|
167
167
|
}
|
|
@@ -182,7 +182,7 @@ class AttributeToken extends Token {
|
|
|
182
182
|
setAttr(key, value, init = false) {
|
|
183
183
|
init &&= !externalUse('setAttr');
|
|
184
184
|
if (typeof key !== 'string' || !['string', 'boolean'].includes(typeof value)) {
|
|
185
|
-
typeError(
|
|
185
|
+
this.typeError('setValue', 'String', 'Boolean');
|
|
186
186
|
} else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
|
|
187
187
|
throw new RangeError('扩展标签属性不能包含 ">"!');
|
|
188
188
|
}
|
|
@@ -214,7 +214,7 @@ class AttributeToken extends Token {
|
|
|
214
214
|
*/
|
|
215
215
|
removeAttr(key) {
|
|
216
216
|
if (typeof key !== 'string') {
|
|
217
|
-
typeError(
|
|
217
|
+
this.typeError('removeAttr', 'String');
|
|
218
218
|
}
|
|
219
219
|
key = key.toLowerCase().trim();
|
|
220
220
|
if (this.#attr.delete(key)) {
|
|
@@ -229,7 +229,7 @@ class AttributeToken extends Token {
|
|
|
229
229
|
*/
|
|
230
230
|
toggleAttr(key, force) {
|
|
231
231
|
if (typeof key !== 'string') {
|
|
232
|
-
typeError(
|
|
232
|
+
this.typeError('toggleAttr', 'String');
|
|
233
233
|
} else if (force !== undefined) {
|
|
234
234
|
force = Boolean(force);
|
|
235
235
|
}
|
package/src/extLink.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {noWrap} = require('../util/string'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('..'),
|
|
4
5
|
Token = require('.'),
|
|
5
6
|
MagicLinkToken = require('./magicLink');
|
|
6
7
|
|
|
@@ -97,7 +98,7 @@ class ExtLinkToken extends Token {
|
|
|
97
98
|
const root = Parser.parse(`[//url ${text}]`, this.getAttribute('include'), 8, this.getAttribute('config')),
|
|
98
99
|
{childNodes: {length}, firstElementChild} = root;
|
|
99
100
|
if (length !== 1 || firstElementChild?.type !== 'ext-link' || firstElementChild.childElementCount !== 2) {
|
|
100
|
-
throw new SyntaxError(`非法的外链文字:${text
|
|
101
|
+
throw new SyntaxError(`非法的外链文字:${noWrap(text)}`);
|
|
101
102
|
}
|
|
102
103
|
const {lastChild} = firstElementChild;
|
|
103
104
|
if (this.childElementCount === 1) {
|
package/src/heading.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
fixedToken = require('../mixin/fixedToken'),
|
|
3
|
+
const fixedToken = require('../mixin/fixedToken'),
|
|
5
4
|
/** @type {Parser} */ Parser = require('..'),
|
|
6
5
|
Token = require('.');
|
|
7
6
|
|
|
@@ -76,7 +75,7 @@ class HeadingToken extends fixedToken(Token) {
|
|
|
76
75
|
/** @param {number} n */
|
|
77
76
|
setLevel(n) {
|
|
78
77
|
if (typeof n !== 'number') {
|
|
79
|
-
typeError(
|
|
78
|
+
this.typeError('setLevel', 'Number');
|
|
80
79
|
}
|
|
81
80
|
n = Math.min(Math.max(n, 1), 6);
|
|
82
81
|
this.setAttribute('name', String(n)).firstElementChild.setAttribute('name', this.name);
|
package/src/html.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {noWrap} = require('../util/string'),
|
|
4
|
+
fixedToken = require('../mixin/fixedToken'),
|
|
4
5
|
attributeParent = require('../mixin/attributeParent'),
|
|
5
6
|
/** @type {Parser} */ Parser = require('..'),
|
|
6
7
|
Token = require('.');
|
|
@@ -79,7 +80,7 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
79
80
|
findMatchingTag() {
|
|
80
81
|
const {html} = this.getAttribute('config'),
|
|
81
82
|
{name, parentElement, closing, selfClosing} = this,
|
|
82
|
-
string = this.toString()
|
|
83
|
+
string = noWrap(this.toString());
|
|
83
84
|
if (closing && selfClosing) {
|
|
84
85
|
throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
|
|
85
86
|
} else if (html[2].includes(name) || selfClosing && html[1].includes(name)) { // 自封闭标签
|
|
@@ -134,7 +135,7 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
134
135
|
this.selfClosing = false;
|
|
135
136
|
this.closing = true;
|
|
136
137
|
} else {
|
|
137
|
-
Parser.warn('无法修复无效自封闭标签', this.toString()
|
|
138
|
+
Parser.warn('无法修复无效自封闭标签', noWrap(this.toString()));
|
|
138
139
|
throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
|
|
139
140
|
}
|
|
140
141
|
}
|
package/src/imageParameter.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
{text, extUrlChar} = require('../util/string'),
|
|
3
|
+
const {text, noWrap, extUrlChar} = require('../util/string'),
|
|
5
4
|
Title = require('../lib/title'),
|
|
6
5
|
/** @type {Parser} */ Parser = require('..'),
|
|
7
6
|
Token = require('.');
|
|
@@ -32,7 +31,7 @@ class ImageParameterToken extends Token {
|
|
|
32
31
|
if (!value) {
|
|
33
32
|
return this.#noLink;
|
|
34
33
|
}
|
|
35
|
-
const regex = new RegExp(`(?:${config.protocol}|//)${extUrlChar}`, 'ui');
|
|
34
|
+
const regex = new RegExp(`(?:${config.protocol}|//)${extUrlChar}(?=\x00\\d+t\x7f|$)`, 'ui');
|
|
36
35
|
if (regex.test(value)) {
|
|
37
36
|
return value;
|
|
38
37
|
}
|
|
@@ -200,13 +199,13 @@ class ImageParameterToken extends Token {
|
|
|
200
199
|
setValue(value) {
|
|
201
200
|
if (this.#isVoid()) {
|
|
202
201
|
if (typeof value !== 'boolean') {
|
|
203
|
-
typeError(
|
|
202
|
+
this.typeError('setValue', 'Boolean');
|
|
204
203
|
} else if (value === false) {
|
|
205
204
|
this.remove();
|
|
206
205
|
}
|
|
207
206
|
return;
|
|
208
207
|
} else if (typeof value !== 'string') {
|
|
209
|
-
typeError(
|
|
208
|
+
this.typeError('setValue', 'String');
|
|
210
209
|
}
|
|
211
210
|
const root = Parser.parse(`[[File:F|${
|
|
212
211
|
this.#syntax ? this.#syntax.replace('$1', value) : value
|
|
@@ -216,7 +215,7 @@ class ImageParameterToken extends Token {
|
|
|
216
215
|
if (length !== 1 || !firstElementChild?.matches('file#File:F')
|
|
217
216
|
|| firstElementChild.childElementCount !== 2 || param.name !== this.name
|
|
218
217
|
) {
|
|
219
|
-
throw new SyntaxError(`非法的 ${this.name} 参数:${value
|
|
218
|
+
throw new SyntaxError(`非法的 ${this.name} 参数:${noWrap(value)}`);
|
|
220
219
|
}
|
|
221
220
|
this.replaceChildren(...param.childNodes);
|
|
222
221
|
}
|