wikiparser-node 0.2.3 → 0.3.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/.eslintrc.json +98 -8
- package/config/default.json +45 -13
- package/config/llwiki.json +11 -11
- package/config/moegirl.json +44 -12
- package/index.js +11 -11
- package/lib/element.js +8 -8
- package/lib/node.js +2 -2
- package/lib/ranges.js +1 -1
- package/lib/title.js +7 -3
- package/mixin/attributeParent.js +2 -2
- package/mixin/fixedToken.js +1 -1
- package/mixin/hidden.js +1 -1
- package/mixin/sol.js +2 -2
- package/package.json +6 -3
- package/parser/brackets.js +11 -6
- package/parser/commentAndExt.js +9 -9
- package/parser/converter.js +5 -5
- package/parser/externalLinks.js +4 -4
- package/parser/hrAndDoubleUnderscore.js +4 -4
- package/parser/html.js +4 -4
- package/parser/links.js +9 -9
- package/parser/list.js +7 -7
- package/parser/magicLinks.js +5 -5
- package/parser/quotes.js +3 -3
- package/parser/table.js +8 -8
- package/src/attribute.js +5 -5
- package/src/converterFlags.js +6 -6
- package/src/converterRule.js +1 -1
- package/src/extLink.js +2 -1
- package/src/gallery.js +59 -11
- package/src/heading.js +1 -1
- package/src/imageParameter.js +5 -5
- package/src/index.js +7 -7
- package/src/link/category.js +1 -1
- package/src/link/file.js +1 -1
- package/src/link/galleryImage.js +47 -0
- package/src/link/index.js +15 -14
- package/src/magicLink.js +14 -4
- package/src/nowiki/dd.js +1 -1
- package/src/syntax.js +3 -0
- package/src/table/index.js +7 -7
- package/src/table/td.js +18 -15
- package/src/table/tr.js +4 -4
- package/src/tagPair/ext.js +11 -3
- package/src/transclude.js +9 -7
- package/util/debug.js +1 -1
- package/util/string.js +7 -7
- package/errors/2022-12-07T10:07:09.577Z +0 -1
- package/errors/2022-12-07T10:07:09.577Z.err +0 -11
- package/errors/2022-12-07T10:07:09.577Z.json +0 -5
- package/errors/2022-12-07T10:22:31.325Z +0 -1
- package/errors/2022-12-07T10:22:31.325Z.err +0 -11
- package/errors/2022-12-07T10:22:31.325Z.json +0 -5
package/src/gallery.js
CHANGED
|
@@ -1,30 +1,78 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {text} = require('../util/string'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('..'),
|
|
4
5
|
Token = require('.'),
|
|
5
|
-
|
|
6
|
+
GalleryImageToken = require('./link/galleryImage');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @classdesc `{childNodes: [
|
|
9
|
+
* gallery标签
|
|
10
|
+
* @classdesc `{childNodes: (string|FileToken)[]]}`
|
|
10
11
|
*/
|
|
11
12
|
class GalleryToken extends Token {
|
|
12
13
|
type = 'ext-inner';
|
|
13
14
|
name = 'gallery';
|
|
14
15
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} inner
|
|
18
|
+
* @param {accum} accum
|
|
19
|
+
*/
|
|
20
|
+
constructor(inner, config = Parser.getConfig(), accum = []) {
|
|
21
|
+
super(undefined, config, true, accum, {String: ':', GalleryImageToken: ':'});
|
|
22
|
+
for (const line of inner?.split('\n') ?? []) {
|
|
23
|
+
const matches = /^([^|]+)(?:\|(.*))?/.exec(line);
|
|
24
|
+
if (!matches) {
|
|
25
|
+
this.appendChild(line);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const [, file, alt] = matches;
|
|
29
|
+
let title;
|
|
30
|
+
try {
|
|
31
|
+
title = this.normalizeTitle(decodeURIComponent(file), 6, true);
|
|
32
|
+
} catch {
|
|
33
|
+
title = this.normalizeTitle(file, 6, true);
|
|
34
|
+
}
|
|
35
|
+
if (!title.valid) {
|
|
36
|
+
this.appendChild(line);
|
|
37
|
+
} else {
|
|
38
|
+
this.appendChild(new GalleryImageToken(file, alt, title, config, accum));
|
|
39
|
+
}
|
|
22
40
|
}
|
|
23
41
|
}
|
|
24
42
|
|
|
43
|
+
cloneNode() {
|
|
44
|
+
const cloned = this.cloneChildren(),
|
|
45
|
+
token = Parser.run(() => new GalleryToken(undefined, this.getAttribute('config')));
|
|
46
|
+
token.append(...cloned);
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
toString() {
|
|
26
51
|
return super.toString('\n');
|
|
27
52
|
}
|
|
53
|
+
|
|
54
|
+
getGaps() {
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
text() {
|
|
59
|
+
return text(this.children, '\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {string} file */
|
|
63
|
+
insertImage(file, i = this.childNodes.length) {
|
|
64
|
+
let title;
|
|
65
|
+
try {
|
|
66
|
+
title = this.normalizeTitle(decodeURIComponent(file), 6, true);
|
|
67
|
+
} catch {
|
|
68
|
+
title = this.normalizeTitle(file, 6, true);
|
|
69
|
+
}
|
|
70
|
+
if (!title.valid) {
|
|
71
|
+
throw new SyntaxError(`非法的文件名:${file}`);
|
|
72
|
+
}
|
|
73
|
+
const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
|
|
74
|
+
return this.insertAt(token, i);
|
|
75
|
+
}
|
|
28
76
|
}
|
|
29
77
|
|
|
30
78
|
Parser.classes.GalleryToken = __filename;
|
package/src/heading.js
CHANGED
package/src/imageParameter.js
CHANGED
|
@@ -22,7 +22,7 @@ class ImageParameterToken extends Token {
|
|
|
22
22
|
* @returns {T extends 'link' ? string|Symbol : boolean}
|
|
23
23
|
*/
|
|
24
24
|
static #validate(key, value, config = Parser.getConfig()) {
|
|
25
|
-
value = value.replace(/\
|
|
25
|
+
value = value.replace(/\0\d+t\x7f/g, '').trim();
|
|
26
26
|
if (key === 'width') {
|
|
27
27
|
return /^\d*(?:x\d*)?$/.test(value);
|
|
28
28
|
} else if (['alt', 'class', 'manualthumb', 'frameless', 'framed', 'thumbnail'].includes(key)) {
|
|
@@ -31,11 +31,11 @@ class ImageParameterToken extends Token {
|
|
|
31
31
|
if (!value) {
|
|
32
32
|
return this.#noLink;
|
|
33
33
|
}
|
|
34
|
-
const regex =
|
|
34
|
+
const regex = RegExp(`(?:${config.protocol}|//)${extUrlChar}(?=\0\\d+t\x7f|$)`, 'iu');
|
|
35
35
|
if (regex.test(value)) {
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
38
|
-
if (/^\[\[
|
|
38
|
+
if (/^\[\[.+\]\]$/.test(value)) {
|
|
39
39
|
value = value.slice(2, -2);
|
|
40
40
|
}
|
|
41
41
|
if (value.includes('%')) {
|
|
@@ -106,11 +106,11 @@ class ImageParameterToken extends Token {
|
|
|
106
106
|
constructor(str, config = Parser.getConfig(), accum = []) {
|
|
107
107
|
const regexes = Object.entries(config.img).map(
|
|
108
108
|
/** @returns {[string, string, RegExp]} */
|
|
109
|
-
([syntax, param]) => [syntax, param,
|
|
109
|
+
([syntax, param]) => [syntax, param, RegExp(`^(\\s*)${syntax.replace('$1', '(.*)')}(\\s*)$`)],
|
|
110
110
|
),
|
|
111
111
|
param = regexes.find(([,, regex]) => regex.test(str));
|
|
112
112
|
if (param) {
|
|
113
|
-
const mt =
|
|
113
|
+
const mt = param[2].exec(str);
|
|
114
114
|
if (mt.length === 4 && !ImageParameterToken.#validate(param[1], mt[2], config)) {
|
|
115
115
|
// pass
|
|
116
116
|
} else {
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/*
|
|
4
4
|
* PHP解析器的步骤:
|
|
5
5
|
* -1. 替换签名和`{{subst:}}`,参见Parser::preSaveTransform;这在revision中不可能保留,可以跳过
|
|
6
|
-
* 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
|
-
* \
|
|
22
|
+
* \0\d+.\x7f标记Token:
|
|
23
23
|
* e: ExtToken
|
|
24
24
|
* c: CommentToken、NoIncludeToken和IncludeToken
|
|
25
25
|
* !: `{{!}}`专用
|
|
@@ -52,7 +52,7 @@ class Token extends AstElement {
|
|
|
52
52
|
type = 'root';
|
|
53
53
|
/** 解析阶段,参见顶部注释。只对plain Token有意义。 */ #stage = 0;
|
|
54
54
|
#config;
|
|
55
|
-
/** 这个数组起两个作用:1. 数组中的Token会在build时替换`/\
|
|
55
|
+
/** 这个数组起两个作用:1. 数组中的Token会在build时替换`/\0\d+.\x7f/`标记;2. 数组中的Token会依次执行parseOnce和build方法。 */
|
|
56
56
|
#accum;
|
|
57
57
|
/** @type {Record<string, Ranges>} */ #acceptable;
|
|
58
58
|
#protectedChildren = new Ranges();
|
|
@@ -66,7 +66,7 @@ class Token extends AstElement {
|
|
|
66
66
|
constructor(wikitext, config = Parser.getConfig(), halfParsed = false, accum = [], acceptable = null) {
|
|
67
67
|
super();
|
|
68
68
|
if (typeof wikitext === 'string') {
|
|
69
|
-
this.appendChild(halfParsed ? wikitext : wikitext.replace(/[\
|
|
69
|
+
this.appendChild(halfParsed ? wikitext : wikitext.replace(/[\0\x7f]/g, ''));
|
|
70
70
|
}
|
|
71
71
|
this.#config = config;
|
|
72
72
|
this.#accum = accum;
|
|
@@ -474,7 +474,7 @@ class Token extends AstElement {
|
|
|
474
474
|
if (!Parser.debugging && externalUse('buildFromStr')) {
|
|
475
475
|
this.debugOnly('buildFromStr');
|
|
476
476
|
}
|
|
477
|
-
return str.split(/[\
|
|
477
|
+
return str.split(/[\0\x7f]/).map((s, i) => {
|
|
478
478
|
if (i % 2 === 0) {
|
|
479
479
|
return s;
|
|
480
480
|
} else if (!isNaN(s.at(-1))) {
|
|
@@ -494,7 +494,7 @@ class Token extends AstElement {
|
|
|
494
494
|
}
|
|
495
495
|
this.#stage = MAX_STAGE;
|
|
496
496
|
const {childNodes: {length}, firstChild} = this;
|
|
497
|
-
if (length !== 1 || typeof firstChild !== 'string' || !firstChild.includes('\
|
|
497
|
+
if (length !== 1 || typeof firstChild !== 'string' || !firstChild.includes('\0')) {
|
|
498
498
|
return this;
|
|
499
499
|
}
|
|
500
500
|
this.replaceChildren(...this.buildFromStr(firstChild));
|
|
@@ -556,7 +556,7 @@ class Token extends AstElement {
|
|
|
556
556
|
if (table instanceof TableToken && table.type !== 'td') {
|
|
557
557
|
table.normalize();
|
|
558
558
|
const [, child] = table.childNodes;
|
|
559
|
-
if (typeof child === 'string' && child.includes('\
|
|
559
|
+
if (typeof child === 'string' && child.includes('\0')) {
|
|
560
560
|
table.removeAt(1);
|
|
561
561
|
const inner = new Token(child, this.#config, true, this.#accum);
|
|
562
562
|
table.insertAt(inner, 1);
|
package/src/link/category.js
CHANGED
package/src/link/file.js
CHANGED
|
@@ -64,7 +64,7 @@ class FileToken extends LinkToken {
|
|
|
64
64
|
super(link, undefined, title, config, accum);
|
|
65
65
|
this.setAttribute('acceptable', {AtomToken: 0, ImageParameterToken: '1:'});
|
|
66
66
|
this.append(...explode('-{', '}-', '|', text).map(part => new ImageParameterToken(part, config, accum)));
|
|
67
|
-
this.seal(['setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick']);
|
|
67
|
+
this.seal(['setLangLink', 'setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick']);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('../..'),
|
|
4
|
+
Token = require('..'),
|
|
5
|
+
FileToken = require('./file');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 图片
|
|
9
|
+
* @classdesc `{childNodes: [AtomToken, ...ImageParameterToken]}`
|
|
10
|
+
*/
|
|
11
|
+
class GalleryImageToken extends FileToken {
|
|
12
|
+
type = 'gallery-image';
|
|
13
|
+
|
|
14
|
+
size = undefined;
|
|
15
|
+
width = undefined;
|
|
16
|
+
height = undefined;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} link
|
|
20
|
+
* @param {string|undefined} text
|
|
21
|
+
* @param {Title} title
|
|
22
|
+
* @param {accum} accum
|
|
23
|
+
*/
|
|
24
|
+
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
25
|
+
let token;
|
|
26
|
+
if (text !== undefined) {
|
|
27
|
+
token = new Token(text, config, true, accum);
|
|
28
|
+
token.type = 'temp';
|
|
29
|
+
token.setAttribute('stage', 1);
|
|
30
|
+
for (let n = 1; n < Parser.MAX_STAGE; n++) {
|
|
31
|
+
token.parseOnce();
|
|
32
|
+
}
|
|
33
|
+
accum.splice(accum.indexOf(token), 1);
|
|
34
|
+
}
|
|
35
|
+
const newConfig = structuredClone(config);
|
|
36
|
+
newConfig.img = Object.fromEntries(Object.entries(config.img).filter(([, param]) => param !== 'width'));
|
|
37
|
+
super(link, token?.toString(), title, newConfig, accum);
|
|
38
|
+
this.seal(['size', 'width', 'height']);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getPadding() {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Parser.classes.GalleryImageToken = __filename;
|
|
47
|
+
module.exports = GalleryImageToken;
|
package/src/link/index.js
CHANGED
|
@@ -47,16 +47,16 @@ class LinkToken extends Token {
|
|
|
47
47
|
title: this.name, interwiki: this.interwiki, fragment: this.fragment,
|
|
48
48
|
}, this.getAttribute('config'));
|
|
49
49
|
token.firstElementChild.safeReplaceWith(link);
|
|
50
|
-
token.
|
|
50
|
+
token.append(...linkText);
|
|
51
51
|
return token.afterBuild();
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
afterBuild() {
|
|
56
|
-
if (this.name.includes('\
|
|
56
|
+
if (this.name.includes('\0')) {
|
|
57
57
|
this.setAttribute('name', text(this.buildFromStr(this.name)));
|
|
58
58
|
}
|
|
59
|
-
if (this.fragment.includes('\
|
|
59
|
+
if (this.fragment.includes('\0')) {
|
|
60
60
|
this.setAttribute('fragment', text(this.buildFromStr(this.fragment)));
|
|
61
61
|
}
|
|
62
62
|
const that = this;
|
|
@@ -91,7 +91,7 @@ class LinkToken extends Token {
|
|
|
91
91
|
|
|
92
92
|
toString() {
|
|
93
93
|
const str = super.toString('|');
|
|
94
|
-
return this.
|
|
94
|
+
return this.type === 'gallery-image' ? str : `[[${str}]]`;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
getPadding() {
|
|
@@ -103,19 +103,20 @@ class LinkToken extends Token {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
text() {
|
|
106
|
-
|
|
106
|
+
const str = super.text('|');
|
|
107
|
+
return this.type === 'gallery-image' ? str : `[[${str}]]`;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
/** @param {string} link */
|
|
110
111
|
setTarget(link) {
|
|
111
112
|
link = String(link);
|
|
112
|
-
if (!/^\s*[:#]/.test(link)) {
|
|
113
|
+
if (link.type === 'link' && !/^\s*[:#]/.test(link)) {
|
|
113
114
|
link = `:${link}`;
|
|
114
115
|
}
|
|
115
116
|
const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
116
117
|
{childNodes: {length}, firstElementChild} = root;
|
|
117
118
|
if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childNodes.length !== 1) {
|
|
118
|
-
const msgs = {link: '内链', file: '文件链接', category: '分类'};
|
|
119
|
+
const msgs = {link: '内链', file: '文件链接', category: '分类', 'gallery-image': '文件链接'};
|
|
119
120
|
throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
|
|
120
121
|
}
|
|
121
122
|
const {firstChild} = firstElementChild;
|
|
@@ -153,7 +154,7 @@ class LinkToken extends Token {
|
|
|
153
154
|
|
|
154
155
|
/** @param {string} fragment */
|
|
155
156
|
#setFragment(fragment, page = true) {
|
|
156
|
-
fragment = String(fragment).replace(/[<>[]
|
|
157
|
+
fragment = String(fragment).replace(/[<>[]#|=!\]/g, p => encodeURIComponent(p));
|
|
157
158
|
const include = this.getAttribute('include'),
|
|
158
159
|
config = this.getAttribute('config'),
|
|
159
160
|
root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
|
|
@@ -212,19 +213,19 @@ class LinkToken extends Token {
|
|
|
212
213
|
if (/[#%]/.test(linkText)) {
|
|
213
214
|
throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
|
|
214
215
|
}
|
|
215
|
-
const m1 =
|
|
216
|
+
const m1 = /^:?(?:[ \w\x80-\xff-]+:)?([^(]+)\(.+\)$/.exec(linkText);
|
|
216
217
|
if (m1) {
|
|
217
|
-
this.setLinkText(m1[1]);
|
|
218
|
+
this.setLinkText(m1[1].trim());
|
|
218
219
|
return;
|
|
219
220
|
}
|
|
220
|
-
const m2 =
|
|
221
|
+
const m2 = /^:?(?:[ \w\x80-\xff-]+:)?([^(]+)(.+)$/.exec(linkText);
|
|
221
222
|
if (m2) {
|
|
222
|
-
this.setLinkText(m2[1]);
|
|
223
|
+
this.setLinkText(m2[1].trim());
|
|
223
224
|
return;
|
|
224
225
|
}
|
|
225
|
-
const m3 =
|
|
226
|
+
const m3 = /^:?(?:[ \w\x80-\xff-]+:)?(.+?)(?:(?<!\()\(.+\))?(?:, |,|، )./.exec(linkText);
|
|
226
227
|
if (m3) {
|
|
227
|
-
this.setLinkText(m3[1]);
|
|
228
|
+
this.setLinkText(m3[1].trim());
|
|
228
229
|
return;
|
|
229
230
|
}
|
|
230
231
|
this.setLinkText(linkText);
|
package/src/magicLink.js
CHANGED
|
@@ -12,13 +12,13 @@ class MagicLinkToken extends Token {
|
|
|
12
12
|
#protocolRegex;
|
|
13
13
|
|
|
14
14
|
get protocol() {
|
|
15
|
-
return this.
|
|
15
|
+
return this.#protocolRegex.exec(this.text())?.[0];
|
|
16
16
|
}
|
|
17
17
|
set protocol(value) {
|
|
18
18
|
if (typeof value !== 'string') {
|
|
19
19
|
this.typeError('protocol', 'String');
|
|
20
20
|
}
|
|
21
|
-
if (!
|
|
21
|
+
if (!RegExp(`${this.#protocolRegex.source}$`, 'i').test(value)) {
|
|
22
22
|
throw new RangeError(`非法的外链协议:${value}`);
|
|
23
23
|
}
|
|
24
24
|
this.replaceChildren(this.text().replace(this.#protocolRegex, value));
|
|
@@ -33,11 +33,11 @@ class MagicLinkToken extends Token {
|
|
|
33
33
|
if (doubleSlash) {
|
|
34
34
|
this.type = 'ext-link-url';
|
|
35
35
|
}
|
|
36
|
-
this.#protocolRegex =
|
|
36
|
+
this.#protocolRegex = RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'i');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
afterBuild() {
|
|
40
|
-
const ParameterToken = require('./parameter'),
|
|
40
|
+
const ParameterToken = require('./parameter'),
|
|
41
41
|
/** @type {ParameterToken} */ parameter = this.closest('parameter');
|
|
42
42
|
if (parameter?.getValue() === this.text()) {
|
|
43
43
|
this.replaceWith(this.toString());
|
|
@@ -45,6 +45,16 @@ class MagicLinkToken extends Token {
|
|
|
45
45
|
return this;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
cloneNode() {
|
|
49
|
+
const cloned = this.cloneChildren(),
|
|
50
|
+
token = Parser.run(() => new MagicLinkToken(
|
|
51
|
+
undefined, this.type === 'ext-link-url', this.getAttribute('config'),
|
|
52
|
+
));
|
|
53
|
+
token.append(...cloned);
|
|
54
|
+
token.afterBuild();
|
|
55
|
+
return token;
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
getUrl() {
|
|
49
59
|
let url = this.text();
|
|
50
60
|
if (url.startsWith('//')) {
|
package/src/nowiki/dd.js
CHANGED
|
@@ -32,7 +32,7 @@ class DdToken extends NowikiToken {
|
|
|
32
32
|
/** @param {string} str */
|
|
33
33
|
setText(str) {
|
|
34
34
|
const src = this.type === 'dd' ? ':' : ';:*#';
|
|
35
|
-
if (
|
|
35
|
+
if (RegExp(`[^${src}]`).test(str)) {
|
|
36
36
|
throw new RangeError(`${this.constructor.name} 仅能包含${src.split('').map(c => `"${c}"`).join('、')}!`);
|
|
37
37
|
}
|
|
38
38
|
this.#update(str);
|
package/src/syntax.js
CHANGED
|
@@ -19,6 +19,9 @@ class SyntaxToken extends Token {
|
|
|
19
19
|
* @param {acceptable} acceptable
|
|
20
20
|
*/
|
|
21
21
|
constructor(wikitext, pattern, type = 'plain', config = Parser.getConfig(), accum = [], acceptable = null) {
|
|
22
|
+
if (pattern.global) {
|
|
23
|
+
throw new RangeError(`SyntaxToken 的语法正则不能含有 g 修饰符:${pattern}`);
|
|
24
|
+
}
|
|
22
25
|
super(wikitext, config, true, accum, acceptable);
|
|
23
26
|
this.type = type;
|
|
24
27
|
this.#pattern = pattern;
|
package/src/table/index.js
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
const assert = require('assert/strict'),
|
|
4
4
|
{noWrap} = require('../../util/string'),
|
|
5
5
|
/** @type {Parser} */ Parser = require('../..'),
|
|
6
|
-
Token = require('..'),
|
|
6
|
+
Token = require('..'),
|
|
7
7
|
TrToken = require('./tr'),
|
|
8
8
|
TdToken = require('./td'),
|
|
9
9
|
SyntaxToken = require('../syntax'),
|
|
10
|
-
AttributeToken = require('../attribute');
|
|
10
|
+
AttributeToken = require('../attribute');
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @param {TableCoords} coords1
|
|
14
14
|
* @param {TableCoords} coords2
|
|
15
15
|
*/
|
|
16
16
|
const cmpCoords = (coords1, coords2) => {
|
|
17
|
-
const diff = coords1
|
|
18
|
-
return diff === 0 ? coords1
|
|
17
|
+
const diff = coords1.row - coords2.row;
|
|
18
|
+
return diff === 0 ? coords1.column - coords2.column : diff;
|
|
19
19
|
};
|
|
20
20
|
const isRowEnd = /** @param {Token} */ ({type}) => ['tr', 'table-syntax'].includes(type);
|
|
21
21
|
|
|
@@ -55,8 +55,8 @@ class Layout extends Array {
|
|
|
55
55
|
class TableToken extends TrToken {
|
|
56
56
|
type = 'table';
|
|
57
57
|
|
|
58
|
-
static openingPattern = /^(
|
|
59
|
-
static closingPattern = /^\n[^\S\n]*(
|
|
58
|
+
static openingPattern = /^(?:\{\||\{\{\{\s*!\s*\}\}|\{\{\s*\(!\s*\}\})$/;
|
|
59
|
+
static closingPattern = /^\n[^\S\n]*(?:\|\}|\{\{\s*!\s*\}\}\}|\{\{\s*!\)\s*\}\})$/;
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* @param {string} syntax
|
|
@@ -192,7 +192,7 @@ class TableToken extends TrToken {
|
|
|
192
192
|
{length} = rows,
|
|
193
193
|
/** @type {Layout} */ layout = new Layout(length).fill().map(() => []);
|
|
194
194
|
for (const [i, rowToken] of rows.entries()) {
|
|
195
|
-
if (i > stop.row ?? stop.y) {
|
|
195
|
+
if (i > (stop.row ?? stop.y)) {
|
|
196
196
|
break;
|
|
197
197
|
}
|
|
198
198
|
const rowLayout = layout[i];
|
package/src/table/td.js
CHANGED
|
@@ -45,7 +45,7 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
45
45
|
*/
|
|
46
46
|
getSyntax() {
|
|
47
47
|
const syntax = this.firstElementChild.text(),
|
|
48
|
-
|
|
48
|
+
esc = syntax.includes('{{');
|
|
49
49
|
let subtype = 'td';
|
|
50
50
|
if (syntax.endsWith('!')) {
|
|
51
51
|
subtype = 'th';
|
|
@@ -53,14 +53,14 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
53
53
|
subtype = 'caption';
|
|
54
54
|
}
|
|
55
55
|
if (this.isIndependent()) {
|
|
56
|
-
return {subtype, escape, correction: false};
|
|
56
|
+
return {subtype, escape: esc, correction: false};
|
|
57
57
|
}
|
|
58
58
|
const {previousElementSibling} = this;
|
|
59
59
|
if (previousElementSibling?.type !== 'td') {
|
|
60
|
-
return {subtype, escape, correction: true};
|
|
60
|
+
return {subtype, escape: esc, correction: true};
|
|
61
61
|
}
|
|
62
62
|
const result = previousElementSibling.getSyntax();
|
|
63
|
-
result.escape ||=
|
|
63
|
+
result.escape ||= esc;
|
|
64
64
|
result.correction = previousElementSibling.lastElementChild.offsetHeight > 1;
|
|
65
65
|
if (subtype === 'th' && result.subtype !== 'th') {
|
|
66
66
|
result.subtype = 'th';
|
|
@@ -69,7 +69,8 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
69
69
|
return result;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
static openingPattern
|
|
72
|
+
static openingPattern
|
|
73
|
+
= /^(?:\n[\S\n]*(?:[|!]|\|\+|\{\{\s*!\s*\}\}\+?)|(?:\||\{\{\s*!\s*\}\}){2}|!!|\{\{\s*!!\s*\}\})$/;
|
|
73
74
|
|
|
74
75
|
getRowCount = undefined;
|
|
75
76
|
getNthCol = undefined;
|
|
@@ -81,9 +82,9 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
81
82
|
* @param {accum} accum
|
|
82
83
|
*/
|
|
83
84
|
constructor(syntax, inner, config = Parser.getConfig(), accum = []) {
|
|
84
|
-
let innerSyntax =
|
|
85
|
+
let innerSyntax = /\||\0\d+!\x7f/.exec(inner),
|
|
85
86
|
attr = innerSyntax ? inner.slice(0, innerSyntax.index) : '';
|
|
86
|
-
if (/\[\[
|
|
87
|
+
if (/\[\[|-\{/.test(attr)) {
|
|
87
88
|
innerSyntax = null;
|
|
88
89
|
attr = '';
|
|
89
90
|
}
|
|
@@ -91,6 +92,7 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
91
92
|
if (innerSyntax) {
|
|
92
93
|
[this.#innerSyntax] = innerSyntax;
|
|
93
94
|
}
|
|
95
|
+
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
94
96
|
const innerToken = new Token(inner?.slice(innerSyntax?.index + this.#innerSyntax.length), config, true, accum);
|
|
95
97
|
innerToken.type = 'td-inner';
|
|
96
98
|
this.setAttribute('acceptable', {SyntaxToken: 0, AttributeToken: 1, Token: 2})
|
|
@@ -98,7 +100,7 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
cloneNode() {
|
|
101
|
-
const token = super.cloneNode();
|
|
103
|
+
const /** @type {TdToken} */ token = super.cloneNode();
|
|
102
104
|
token.setAttribute('innerSyntax', this.#innerSyntax);
|
|
103
105
|
return token;
|
|
104
106
|
}
|
|
@@ -141,6 +143,7 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
141
143
|
* @template {string} T
|
|
142
144
|
* @param {T} key
|
|
143
145
|
* @param {TokenAttribute<T>} value
|
|
146
|
+
* @return {this}
|
|
144
147
|
*/
|
|
145
148
|
setAttribute(key, value) {
|
|
146
149
|
if (key !== 'innerSyntax') {
|
|
@@ -153,7 +156,7 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
afterBuild() {
|
|
156
|
-
if (this.#innerSyntax.includes('\
|
|
159
|
+
if (this.#innerSyntax.includes('\0')) {
|
|
157
160
|
this.#innerSyntax = this.buildFromStr(this.#innerSyntax).map(String).join('');
|
|
158
161
|
}
|
|
159
162
|
return this;
|
|
@@ -162,8 +165,8 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
162
165
|
static #aliases = {td: '\n|', th: '\n!', caption: '\n|+'};
|
|
163
166
|
|
|
164
167
|
/** @param {string} syntax */
|
|
165
|
-
setSyntax(syntax,
|
|
166
|
-
super.setSyntax(TdToken.#aliases[syntax] ?? syntax,
|
|
168
|
+
setSyntax(syntax, esc = false) {
|
|
169
|
+
super.setSyntax(TdToken.#aliases[syntax] ?? syntax, esc);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
/** @complexity `n` */
|
|
@@ -171,17 +174,17 @@ class TdToken extends fixedToken(TrToken) {
|
|
|
171
174
|
if (this.children[1].toString()) {
|
|
172
175
|
this.#innerSyntax ||= '|';
|
|
173
176
|
}
|
|
174
|
-
const {subtype, escape, correction} = this.getSyntax();
|
|
177
|
+
const {subtype, escape: esc, correction} = this.getSyntax();
|
|
175
178
|
if (correction) {
|
|
176
|
-
this.setSyntax(subtype,
|
|
179
|
+
this.setSyntax(subtype, esc);
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
/** @complexity `n` */
|
|
181
184
|
independence() {
|
|
182
185
|
if (!this.isIndependent()) {
|
|
183
|
-
const {subtype, escape} = this.getSyntax();
|
|
184
|
-
this.setSyntax(subtype,
|
|
186
|
+
const {subtype, escape: esc} = this.getSyntax();
|
|
187
|
+
this.setSyntax(subtype, esc);
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
|
package/src/table/tr.js
CHANGED
|
@@ -13,7 +13,7 @@ const attributeParent = require('../../mixin/attributeParent'),
|
|
|
13
13
|
class TrToken extends attributeParent(Token, 1) {
|
|
14
14
|
type = 'tr';
|
|
15
15
|
|
|
16
|
-
static openingPattern = /^\n[^\S\n]*(
|
|
16
|
+
static openingPattern = /^\n[^\S\n]*(?:\|-+|\{\{\s*!\s*\}\}-+|\{\{\s*!-\s*\}\}-*)$/;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @param {string} syntax
|
|
@@ -93,10 +93,10 @@ class TrToken extends attributeParent(Token, 1) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** @param {string} syntax */
|
|
96
|
-
setSyntax(syntax,
|
|
96
|
+
setSyntax(syntax, esc = false) {
|
|
97
97
|
const {firstElementChild} = this;
|
|
98
98
|
firstElementChild.replaceChildren(syntax);
|
|
99
|
-
if (
|
|
99
|
+
if (esc) {
|
|
100
100
|
TrToken.escape(firstElementChild);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -202,7 +202,7 @@ class TrToken extends attributeParent(Token, 1) {
|
|
|
202
202
|
if (n < 0 || n > nCols || n === nCols && !insert) {
|
|
203
203
|
throw new RangeError(`不存在第 ${n} 个单元格!`);
|
|
204
204
|
}
|
|
205
|
-
const TdToken = require('./td');
|
|
205
|
+
const TdToken = require('./td');
|
|
206
206
|
let last = 0;
|
|
207
207
|
for (const child of this.children.slice(2)) {
|
|
208
208
|
if (child instanceof TdToken) {
|
package/src/tagPair/ext.js
CHANGED
|
@@ -23,7 +23,7 @@ class ExtToken extends attributeParent(TagPairToken) {
|
|
|
23
23
|
attrToken = new AttributeToken(attr, 'ext-attr', lcName, config, accum),
|
|
24
24
|
newConfig = structuredClone(config),
|
|
25
25
|
ext = new Set(newConfig.ext);
|
|
26
|
-
let /** @type {acceptable} */ acceptable, innerToken;
|
|
26
|
+
let /** @type {acceptable} */ acceptable, /** @type {Token} */ innerToken;
|
|
27
27
|
switch (lcName) {
|
|
28
28
|
case 'choose':
|
|
29
29
|
ext.add('option');
|
|
@@ -42,15 +42,23 @@ class ExtToken extends attributeParent(TagPairToken) {
|
|
|
42
42
|
innerToken = new Token(inner, newConfig, false, accum);
|
|
43
43
|
break;
|
|
44
44
|
}
|
|
45
|
+
case 'gallery': {
|
|
46
|
+
ext.delete(lcName);
|
|
47
|
+
newConfig.ext = [...ext];
|
|
48
|
+
const GalleryToken = require('../gallery');
|
|
49
|
+
acceptable = {AttributeToken: 0, GalleryToken: 1};
|
|
50
|
+
innerToken = new GalleryToken(inner, newConfig, accum);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
45
53
|
/*
|
|
46
54
|
* 更多定制扩展的代码示例:
|
|
47
55
|
* ```
|
|
48
56
|
* case 'extensionName': {
|
|
49
|
-
* ext.delete(
|
|
57
|
+
* ext.delete(lcName);
|
|
50
58
|
* newConfig.ext = [...ext];
|
|
51
59
|
* const ExtensionToken = require('../extension');
|
|
52
60
|
* acceptable = {AttributeToken: 0, ExtensionToken: 1};
|
|
53
|
-
* innerToken = new ExtensionToken(
|
|
61
|
+
* innerToken = new ExtensionToken(inner, newConfig, accum);
|
|
54
62
|
* break;
|
|
55
63
|
* }
|
|
56
64
|
* ```
|