wikiparser-node 0.0.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/.eslintrc.json +229 -0
- package/LICENSE +674 -0
- package/README.md +1896 -0
- package/config/default.json +766 -0
- package/config/llwiki.json +686 -0
- package/config/moegirl.json +721 -0
- package/index.js +159 -0
- package/jsconfig.json +7 -0
- package/lib/element.js +690 -0
- package/lib/node.js +357 -0
- package/lib/ranges.js +122 -0
- package/lib/title.js +57 -0
- package/mixin/attributeParent.js +67 -0
- package/mixin/fixedToken.js +32 -0
- package/mixin/hidden.js +22 -0
- package/package.json +30 -0
- package/parser/brackets.js +107 -0
- package/parser/commentAndExt.js +61 -0
- package/parser/externalLinks.js +30 -0
- package/parser/hrAndDoubleUnderscore.js +26 -0
- package/parser/html.js +41 -0
- package/parser/links.js +92 -0
- package/parser/magicLinks.js +40 -0
- package/parser/quotes.js +63 -0
- package/parser/table.js +97 -0
- package/src/arg.js +150 -0
- package/src/atom/hidden.js +10 -0
- package/src/atom/index.js +33 -0
- package/src/attribute.js +342 -0
- package/src/extLink.js +116 -0
- package/src/heading.js +91 -0
- package/src/html.js +144 -0
- package/src/imageParameter.js +172 -0
- package/src/index.js +602 -0
- package/src/link/category.js +88 -0
- package/src/link/file.js +201 -0
- package/src/link/index.js +214 -0
- package/src/listToken.js +47 -0
- package/src/magicLink.js +66 -0
- package/src/nowiki/comment.js +45 -0
- package/src/nowiki/doubleUnderscore.js +42 -0
- package/src/nowiki/hr.js +41 -0
- package/src/nowiki/index.js +37 -0
- package/src/nowiki/noinclude.js +24 -0
- package/src/nowiki/quote.js +37 -0
- package/src/onlyinclude.js +42 -0
- package/src/parameter.js +165 -0
- package/src/syntax.js +80 -0
- package/src/table/index.js +867 -0
- package/src/table/td.js +259 -0
- package/src/table/tr.js +244 -0
- package/src/tagPair/ext.js +85 -0
- package/src/tagPair/include.js +45 -0
- package/src/tagPair/index.js +91 -0
- package/src/transclude.js +627 -0
- package/tool/index.js +898 -0
- package/typings/element.d.ts +28 -0
- package/typings/index.d.ts +49 -0
- package/typings/node.d.ts +23 -0
- package/typings/parser.d.ts +9 -0
- package/typings/table.d.ts +14 -0
- package/typings/token.d.ts +21 -0
- package/typings/tool.d.ts +10 -0
- package/util/debug.js +70 -0
- package/util/string.js +60 -0
package/src/link/file.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {explode} = require('../../util/string'),
|
|
4
|
+
{typeError, externalUse} = require('../../util/debug'),
|
|
5
|
+
/** @type {Parser} */ Parser = require('../..'),
|
|
6
|
+
LinkToken = require('.'),
|
|
7
|
+
ImageParameterToken = require('../imageParameter');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 图片
|
|
11
|
+
* @classdesc `{childNodes: [AtomToken, ...ImageParameterToken]}`
|
|
12
|
+
*/
|
|
13
|
+
class FileToken extends LinkToken {
|
|
14
|
+
type = 'file';
|
|
15
|
+
/** @type {Set<string>} */ #keys = new Set();
|
|
16
|
+
/** @type {Record<string, Set<ImageParameterToken>>} */ #args = {};
|
|
17
|
+
|
|
18
|
+
setFragment = undefined;
|
|
19
|
+
asSelfLink = undefined;
|
|
20
|
+
setLinkText = undefined;
|
|
21
|
+
pipeTrick = undefined;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} link
|
|
25
|
+
* @param {string|undefined} text
|
|
26
|
+
* @param {Title} title
|
|
27
|
+
* @param {accum} accum
|
|
28
|
+
* @complexity `n`
|
|
29
|
+
*/
|
|
30
|
+
constructor(link, text, title, config = Parser.getConfig(), accum = []) {
|
|
31
|
+
super(link, undefined, title, config, accum);
|
|
32
|
+
this.setAttribute('acceptable', {AtomToken: 0, ImageParameterToken: '1:'});
|
|
33
|
+
this.append(...explode('-{', '}-', '|', text).map(part => new ImageParameterToken(part, config, accum)));
|
|
34
|
+
this.seal(['setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick']);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {number} i
|
|
39
|
+
* @complexity `n`
|
|
40
|
+
*/
|
|
41
|
+
removeAt(i) {
|
|
42
|
+
const /** @type {ImageParameterToken} */ token = super.removeAt(i),
|
|
43
|
+
args = this.getArgs(token.name, false, false);
|
|
44
|
+
args.delete(token);
|
|
45
|
+
if (args.size === 0) {
|
|
46
|
+
this.#keys.delete(token.name);
|
|
47
|
+
}
|
|
48
|
+
return token;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {ImageParameterToken} token
|
|
53
|
+
* @complexity `n`
|
|
54
|
+
*/
|
|
55
|
+
insertAt(token, i = this.childElementCount) {
|
|
56
|
+
if (!Parser.running) {
|
|
57
|
+
this.getArgs(token.name, false, false).add(token);
|
|
58
|
+
this.#keys.add(token.name);
|
|
59
|
+
}
|
|
60
|
+
return super.insertAt(token, i);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @returns {ImageParameterToken[]} */
|
|
64
|
+
getAllArgs() {
|
|
65
|
+
return this.childNodes.slice(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @complexity `n` */
|
|
69
|
+
getFrameArgs() {
|
|
70
|
+
const args = this.getAllArgs()
|
|
71
|
+
.filter(({name}) => ['manualthumb', 'frameless', 'framed', 'thumbnail'].includes(name));
|
|
72
|
+
if (args.length > 1) {
|
|
73
|
+
Parser.error(`警告:图片 ${this.name} 带有 ${args.length} 个框架参数,只有第 1 个 ${args[0].name} 会生效!`);
|
|
74
|
+
}
|
|
75
|
+
return args;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {string} key
|
|
80
|
+
* @complexity `n`
|
|
81
|
+
*/
|
|
82
|
+
getArgs(key, copy = true) {
|
|
83
|
+
if (typeof key !== 'string') {
|
|
84
|
+
typeError(this, 'getArgs', 'String');
|
|
85
|
+
} else if (!copy && !Parser.debugging && externalUse('getArgs')) {
|
|
86
|
+
this.debugOnly('getArgs');
|
|
87
|
+
}
|
|
88
|
+
let args = this.#args[key];
|
|
89
|
+
if (!args) {
|
|
90
|
+
args = new Set(this.getAllArgs().filter(({name}) => key === name));
|
|
91
|
+
this.#args[key] = args;
|
|
92
|
+
}
|
|
93
|
+
return copy ? new Set(args) : args;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} key
|
|
98
|
+
* @complexity `n`
|
|
99
|
+
*/
|
|
100
|
+
hasArg(key) {
|
|
101
|
+
return this.getArgs(key, false).size > 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} key
|
|
106
|
+
* @complexity `n`
|
|
107
|
+
*/
|
|
108
|
+
getArg(key) {
|
|
109
|
+
return [...this.getArgs(key, false)].sort((a, b) => a.comparePosition(b)).at(-1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} key
|
|
114
|
+
* @complexity `n`
|
|
115
|
+
*/
|
|
116
|
+
removeArg(key) {
|
|
117
|
+
for (const token of this.getArgs(key, false)) {
|
|
118
|
+
this.removeChild(token);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @complexity `n` */
|
|
123
|
+
getKeys() {
|
|
124
|
+
const args = this.getAllArgs();
|
|
125
|
+
if (this.#keys.size === 0 && args.length) {
|
|
126
|
+
for (const {name} of args) {
|
|
127
|
+
this.#keys.add(name);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return [...this.#keys];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} key
|
|
135
|
+
* @complexity `n`
|
|
136
|
+
*/
|
|
137
|
+
getValues(key) {
|
|
138
|
+
return [...this.getArgs(key, false)].map(token => token.getValue());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @template {string|undefined} T
|
|
143
|
+
* @param {T} key
|
|
144
|
+
* @returns {T extends undefined ? Object<string, string> : string|true}
|
|
145
|
+
* @complexity `n`
|
|
146
|
+
*/
|
|
147
|
+
getValue(key) {
|
|
148
|
+
if (key !== undefined) {
|
|
149
|
+
return this.getArg(key)?.getValue();
|
|
150
|
+
}
|
|
151
|
+
return Object.fromEntries(this.getKeys().map(k => [k, this.getValue(k)]));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {string} key
|
|
156
|
+
* @param {string|boolean} value
|
|
157
|
+
* @complexity `n`
|
|
158
|
+
*/
|
|
159
|
+
setValue(key, value) {
|
|
160
|
+
if (typeof key !== 'string') {
|
|
161
|
+
typeError(this, 'setValue', 'String');
|
|
162
|
+
} else if (value === false) {
|
|
163
|
+
this.removeArg(key);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const token = this.getArg(key);
|
|
167
|
+
value = value === true ? value : String(value);
|
|
168
|
+
if (token) {
|
|
169
|
+
token.setValue(value);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
let syntax = '';
|
|
173
|
+
const config = this.getAttribute('config');
|
|
174
|
+
if (key !== 'caption') {
|
|
175
|
+
syntax = Object.entries(config.img).find(([, name]) => name === key)?.[0];
|
|
176
|
+
if (!syntax) {
|
|
177
|
+
throw new RangeError(`未定义的图片参数: ${key}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (value === true) {
|
|
181
|
+
if (syntax.includes('$1')) {
|
|
182
|
+
typeError(this, 'setValue', 'Boolean');
|
|
183
|
+
}
|
|
184
|
+
const newArg = Parser.run(() => new ImageParameterToken(syntax, config));
|
|
185
|
+
this.appendChild(newArg);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const wikitext = `[[File:F|${syntax ? syntax.replace('$1', value) : value}]]`,
|
|
189
|
+
root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
|
|
190
|
+
{childNodes: {length}, firstElementChild} = root;
|
|
191
|
+
if (length !== 1 || !firstElementChild?.matches('file#File:F')
|
|
192
|
+
|| firstElementChild.childElementCount !== 2 || firstElementChild.lastElementChild.name !== key
|
|
193
|
+
) {
|
|
194
|
+
throw new SyntaxError(`非法的 ${key} 参数:${value.replaceAll('\n', '\\n')}`);
|
|
195
|
+
}
|
|
196
|
+
this.appendChild(firstElementChild.lastChild);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Parser.classes.FileToken = __filename;
|
|
201
|
+
module.exports = FileToken;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Title = require('../../lib/title'), // eslint-disable-line no-unused-vars
|
|
4
|
+
{text} = require('../../util/string'),
|
|
5
|
+
{undo} = require('../../util/debug'),
|
|
6
|
+
/** @type {Parser} */ Parser = require('../..'),
|
|
7
|
+
Token = require('..');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 内链
|
|
11
|
+
* @classdesc `{childNodes: [AtomToken, ?Token]}`
|
|
12
|
+
*/
|
|
13
|
+
class LinkToken extends Token {
|
|
14
|
+
type = 'link';
|
|
15
|
+
selfLink;
|
|
16
|
+
fragment;
|
|
17
|
+
interwiki;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} link
|
|
21
|
+
* @param {string|undefined} linkText
|
|
22
|
+
* @param {Title} title
|
|
23
|
+
* @param {accum} accum
|
|
24
|
+
*/
|
|
25
|
+
constructor(link, linkText, title, config = Parser.getConfig(), accum = []) {
|
|
26
|
+
super(undefined, config, true, accum, {AtomToken: 0, Token: 1});
|
|
27
|
+
const AtomToken = require('../atom');
|
|
28
|
+
this.appendChild(new AtomToken(link, 'link-target', config, accum, {
|
|
29
|
+
'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
|
|
30
|
+
}));
|
|
31
|
+
if (linkText !== undefined) {
|
|
32
|
+
const inner = new Token(linkText, config, true, accum);
|
|
33
|
+
inner.type = 'link-text';
|
|
34
|
+
this.appendChild(inner.setAttribute('stage', 7));
|
|
35
|
+
}
|
|
36
|
+
this.selfLink = !title.title;
|
|
37
|
+
this.fragment = title.fragment;
|
|
38
|
+
this.interwiki = title.interwiki;
|
|
39
|
+
this.setAttribute('name', title.title).seal(['selfLink', 'fragment', 'interwiki']).protectChildren(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cloneNode() {
|
|
43
|
+
const [link, ...linkText] = this.cloneChildren();
|
|
44
|
+
return Parser.run(() => {
|
|
45
|
+
const /** @type {typeof LinkToken} */ Constructor = this.constructor,
|
|
46
|
+
token = new Constructor('', undefined, {
|
|
47
|
+
title: this.name, interwiki: this.interwiki, fragment: this.fragment,
|
|
48
|
+
}, this.getAttribute('config'));
|
|
49
|
+
token.firstElementChild.safeReplaceWith(link);
|
|
50
|
+
token.appendChild(...linkText);
|
|
51
|
+
return token.afterBuild();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
afterBuild() {
|
|
56
|
+
if (this.name.includes('\x00')) {
|
|
57
|
+
this.setAttribute('name', text(this.buildFromStr(this.name)));
|
|
58
|
+
}
|
|
59
|
+
if (this.fragment.includes('\x00')) {
|
|
60
|
+
this.setAttribute('fragment', text(this.buildFromStr(this.fragment)));
|
|
61
|
+
}
|
|
62
|
+
const that = this;
|
|
63
|
+
const /** @type {AstListener} */ linkListener = (e, data) => {
|
|
64
|
+
const {prevTarget} = e;
|
|
65
|
+
if (prevTarget?.type === 'link-target') {
|
|
66
|
+
const name = prevTarget.text(),
|
|
67
|
+
{title, interwiki, fragment, ns, valid} = that.normalizeTitle(name);
|
|
68
|
+
if (!valid) {
|
|
69
|
+
undo(e, data);
|
|
70
|
+
throw new Error(`非法的内链目标:${name}`);
|
|
71
|
+
} else if (that.type === 'category' && (interwiki || ns !== 14)
|
|
72
|
+
|| that.type === 'file' && (interwiki || ns !== 6)
|
|
73
|
+
) {
|
|
74
|
+
undo(e, data);
|
|
75
|
+
throw new Error(`${that.type === 'file' ? '文件' : '分类'}链接不可更改命名空间:${name}`);
|
|
76
|
+
} else if (that.type === 'link' && !interwiki && [6, 14].includes(ns) && !name.trim().startsWith(':')) {
|
|
77
|
+
const {firstChild} = prevTarget;
|
|
78
|
+
if (typeof firstChild === 'string') {
|
|
79
|
+
prevTarget.setText(`:${firstChild}`);
|
|
80
|
+
} else {
|
|
81
|
+
prevTarget.prepend(':');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
that.setAttribute('selfLink', !title).setAttribute('interwiki', interwiki)
|
|
85
|
+
.setAttribute('name', title).setAttribute('fragment', fragment);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toString() {
|
|
93
|
+
return `[[${super.toString('|')}]]`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getPadding() {
|
|
97
|
+
return 2;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getGaps() {
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
text() {
|
|
105
|
+
return `[[${super.text('|')}]]`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @returns {[number, string][]} */
|
|
109
|
+
plain() {
|
|
110
|
+
return this.childElementCount === 1 ? [] : this.lastElementChild.plain();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @param {string} link */
|
|
114
|
+
setTarget(link) {
|
|
115
|
+
link = String(link);
|
|
116
|
+
if (!/^\s*[:#]/.test(link)) {
|
|
117
|
+
link = `:${link}`;
|
|
118
|
+
}
|
|
119
|
+
const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
120
|
+
{childNodes: {length}, firstElementChild} = root;
|
|
121
|
+
if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childElementCount !== 1) {
|
|
122
|
+
const msgs = {link: '内链', file: '文件链接', category: '分类'};
|
|
123
|
+
throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
|
|
124
|
+
}
|
|
125
|
+
const {firstChild} = firstElementChild;
|
|
126
|
+
root.destroy();
|
|
127
|
+
firstElementChild.destroy();
|
|
128
|
+
this.firstElementChild.safeReplaceWith(firstChild);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @param {string} fragment */
|
|
132
|
+
#setFragment(fragment, page = true) {
|
|
133
|
+
fragment = String(fragment).replace(/[<>[]#|=!]/g, p => encodeURIComponent(p));
|
|
134
|
+
const include = this.getAttribute('include'),
|
|
135
|
+
config = this.getAttribute('config'),
|
|
136
|
+
root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
|
|
137
|
+
{childNodes: {length}, firstElementChild} = root;
|
|
138
|
+
if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childElementCount !== 1) {
|
|
139
|
+
throw new SyntaxError(`非法的 fragment:${fragment}`);
|
|
140
|
+
}
|
|
141
|
+
if (page) {
|
|
142
|
+
Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
|
|
143
|
+
}
|
|
144
|
+
const {firstChild} = firstElementChild;
|
|
145
|
+
root.destroy();
|
|
146
|
+
firstElementChild.destroy();
|
|
147
|
+
this.firstElementChild.safeReplaceWith(firstChild);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** @param {string} fragment */
|
|
151
|
+
setFragment(fragment) {
|
|
152
|
+
this.#setFragment(fragment);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
asSelfLink(fragment = this.fragment) {
|
|
156
|
+
fragment = String(fragment);
|
|
157
|
+
if (!fragment.trim()) {
|
|
158
|
+
throw new RangeError(`${this.constructor.name}.asSelfLink 方法必须指定非空的 fragment!`);
|
|
159
|
+
}
|
|
160
|
+
this.#setFragment(fragment, false);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setLinkText(linkText = '') {
|
|
164
|
+
linkText = String(linkText);
|
|
165
|
+
let lastElementChild;
|
|
166
|
+
const config = this.getAttribute('config');
|
|
167
|
+
if (linkText) {
|
|
168
|
+
const root = Parser.parse(`[[${
|
|
169
|
+
this.type === 'category' ? 'Category:' : ''
|
|
170
|
+
}L|${linkText}]]`, this.getAttribute('include'), 6, config),
|
|
171
|
+
{childNodes: {length}, firstElementChild} = root;
|
|
172
|
+
if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childElementCount !== 2) {
|
|
173
|
+
throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${
|
|
174
|
+
linkText.replaceAll('\n', '\\n')
|
|
175
|
+
}`);
|
|
176
|
+
}
|
|
177
|
+
({lastElementChild} = firstElementChild);
|
|
178
|
+
} else {
|
|
179
|
+
lastElementChild = Parser.run(() => new Token('', config));
|
|
180
|
+
lastElementChild.setAttribute('stage', 7).type = 'link-text';
|
|
181
|
+
}
|
|
182
|
+
if (this.childElementCount === 1) {
|
|
183
|
+
this.appendChild(lastElementChild);
|
|
184
|
+
} else {
|
|
185
|
+
this.lastElementChild.safeReplaceWith(lastElementChild);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
pipeTrick() {
|
|
190
|
+
const linkText = this.firstElementChild.text();
|
|
191
|
+
if (/[#%]/.test(linkText)) {
|
|
192
|
+
throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
|
|
193
|
+
}
|
|
194
|
+
const m1 = linkText.match(/^:?(?:[ \w\x80-\xff-]+:)?(.+?) ?\(.+\)$/);
|
|
195
|
+
if (m1) {
|
|
196
|
+
this.setLinkText(m1[1]);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const m2 = linkText.match(/^:?(?:[ \w\x80-\xff-]+:)?(.+?) ?(.+)$/);
|
|
200
|
+
if (m2) {
|
|
201
|
+
this.setLinkText(m2[1]);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const m3 = linkText.match(/^:?(?:[ \w\x80-\xff-]+:)?(.+?)(?: ?\(.+\))?(?:, |,|، ).+/);
|
|
205
|
+
if (m3) {
|
|
206
|
+
this.setLinkText(m3[1]);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.setLinkText(linkText);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Parser.classes.LinkToken = __filename;
|
|
214
|
+
module.exports = LinkToken;
|
package/src/listToken.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const Token = require('.'),
|
|
3
|
+
AtomToken = require('./atom'),
|
|
4
|
+
{fixToken} = require('./util');
|
|
5
|
+
|
|
6
|
+
class ListToken extends fixToken(Token) {
|
|
7
|
+
type = 'list';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} syntax
|
|
11
|
+
* @param {?string|number|Token|(string|Token)[]} content
|
|
12
|
+
* @param {Object<string, any>} config
|
|
13
|
+
* @param {Token} parent
|
|
14
|
+
* @param {Token[]} accum
|
|
15
|
+
*/
|
|
16
|
+
constructor(syntax, content, config = require(Token.config), parent = null, accum = [], isTable = false) {
|
|
17
|
+
if (/[^:;#*]/.test(syntax)) {
|
|
18
|
+
throw new RangeError('List语法只接受":"、";"、"#"或"*"!');
|
|
19
|
+
}
|
|
20
|
+
super(new AtomToken(syntax, 'list-syntax'), config, true, parent, accum, ['AtomToken', 'Token']);
|
|
21
|
+
const inner = new Token(content, config, true, this, accum);
|
|
22
|
+
inner.type = 'list-inner';
|
|
23
|
+
inner.set('stage', isTable ? 4 : 10);
|
|
24
|
+
this.lists = new Set(syntax.split(''));
|
|
25
|
+
this.seal();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isDt() {
|
|
29
|
+
return this.$children[0].contains(';');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
idDd() {
|
|
33
|
+
return this.$children[0].contains(':');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
isOl() {
|
|
37
|
+
return this.$children[0].contains('#');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
isUl() {
|
|
41
|
+
return this.$children[0].contains('*');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Token.classes.ListToken = ListToken;
|
|
46
|
+
|
|
47
|
+
module.exports = ListToken;
|
package/src/magicLink.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {typeError} = require('../util/debug'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('..'),
|
|
5
|
+
Token = require('.');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 自由外链
|
|
9
|
+
* @classdesc `{childNodes: [...string|CommentToken|IncludeToken|NoincludeToken]}`
|
|
10
|
+
*/
|
|
11
|
+
class MagicLinkToken extends Token {
|
|
12
|
+
type = 'free-ext-link';
|
|
13
|
+
#protocolRegex;
|
|
14
|
+
|
|
15
|
+
get protocol() {
|
|
16
|
+
return this.text().match(this.#protocolRegex)?.[0];
|
|
17
|
+
}
|
|
18
|
+
/** @param {string} value */
|
|
19
|
+
set protocol(value) {
|
|
20
|
+
if (typeof value !== 'string') {
|
|
21
|
+
typeError(this, 'protocol', 'String');
|
|
22
|
+
}
|
|
23
|
+
if (!new RegExp(`${this.#protocolRegex.source}$`, 'i').test(value)) {
|
|
24
|
+
throw new RangeError(`非法的外链协议:${value}`);
|
|
25
|
+
}
|
|
26
|
+
this.replaceChildren(this.text().replace(this.#protocolRegex, value));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} url
|
|
31
|
+
* @param {accum} accum
|
|
32
|
+
*/
|
|
33
|
+
constructor(url, doubleSlash = false, config = Parser.getConfig(), accum = []) {
|
|
34
|
+
super(url, config, true, accum, {'Stage-1': ':', '!ExtToken': ''});
|
|
35
|
+
if (doubleSlash) {
|
|
36
|
+
this.type = 'ext-link-url';
|
|
37
|
+
}
|
|
38
|
+
this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'i');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getUrl() {
|
|
42
|
+
const url = this.text();
|
|
43
|
+
try {
|
|
44
|
+
return new URL(url);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
if (e instanceof TypeError && e.message === 'Invalid URL') {
|
|
47
|
+
throw new Error(`非标准协议的外部链接:${url}`);
|
|
48
|
+
}
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @param {string|URL} url */
|
|
54
|
+
setTarget(url) {
|
|
55
|
+
url = String(url);
|
|
56
|
+
const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
|
|
57
|
+
{childNodes: {length}, firstElementChild} = root;
|
|
58
|
+
if (length !== 1 || firstElementChild?.type !== 'free-ext-link') {
|
|
59
|
+
throw new SyntaxError(`非法的自由外链目标:${url}`);
|
|
60
|
+
}
|
|
61
|
+
this.replaceChildren(...firstElementChild.childNodes);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Parser.classes.MagicLinkToken = __filename;
|
|
66
|
+
module.exports = MagicLinkToken;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const hidden = require('../../mixin/hidden'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('../..'),
|
|
5
|
+
NowikiToken = require('.');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* HTML注释,不可见
|
|
9
|
+
* @classdesc `{childNodes: [string]}`
|
|
10
|
+
*/
|
|
11
|
+
class CommentToken extends hidden(NowikiToken) {
|
|
12
|
+
type = 'comment';
|
|
13
|
+
closed;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} wikitext
|
|
17
|
+
* @param {accum} accum
|
|
18
|
+
*/
|
|
19
|
+
constructor(wikitext, closed = true, config = Parser.getConfig(), accum = []) {
|
|
20
|
+
super(wikitext, config, accum);
|
|
21
|
+
this.closed = closed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @this {CommentToken & {firstChild: string}} */
|
|
25
|
+
cloneNode() {
|
|
26
|
+
return Parser.run(() => new CommentToken(this.firstChild, this.closed, this.getAttribute('config')));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @this {CommentToken & {firstChild: string}} */
|
|
30
|
+
toString() {
|
|
31
|
+
const {firstChild, closed, nextSibling} = this;
|
|
32
|
+
if (!closed && nextSibling) {
|
|
33
|
+
Parser.error('自动闭合HTML注释', firstChild.replaceAll('\n', '\\n'));
|
|
34
|
+
this.closed = true;
|
|
35
|
+
}
|
|
36
|
+
return `<!--${firstChild}${this.closed ? '-->' : ''}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getPadding() {
|
|
40
|
+
return 4;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Parser.classes.CommentToken = __filename;
|
|
45
|
+
module.exports = CommentToken;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const hidden = require('../../mixin/hidden'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('../..'),
|
|
5
|
+
NowikiToken = require('.');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 状态开关
|
|
9
|
+
* @classdesc `{childNodes: [string]}`
|
|
10
|
+
*/
|
|
11
|
+
class DoubleUnderscoreToken extends hidden(NowikiToken) {
|
|
12
|
+
type = 'double-underscore';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} word
|
|
16
|
+
* @param {accum} accum
|
|
17
|
+
*/
|
|
18
|
+
constructor(word, config = Parser.getConfig(), accum = []) {
|
|
19
|
+
super(word, config, accum);
|
|
20
|
+
this.setAttribute('name', word.toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
cloneNode() {
|
|
24
|
+
return Parser.run(() => new DoubleUnderscoreToken(this.firstChild, this.getAttribute('config')));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @this {DoubleUnderscoreToken & {firstChild: string}} */
|
|
28
|
+
toString() {
|
|
29
|
+
return `__${this.firstChild}__`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getPadding() {
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setText() {
|
|
37
|
+
throw new Error(`禁止修改 ${this.constructor.name}!`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Parser.classes.DoubleUnderscoreToken = __filename;
|
|
42
|
+
module.exports = DoubleUnderscoreToken;
|
package/src/nowiki/hr.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('../..'),
|
|
4
|
+
NowikiToken = require('.');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `<hr>`
|
|
8
|
+
* @classdesc `{childNodes: [string]}`
|
|
9
|
+
*/
|
|
10
|
+
class HrToken extends NowikiToken {
|
|
11
|
+
type = 'hr';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {number} n
|
|
15
|
+
* @param {accum} accum
|
|
16
|
+
*/
|
|
17
|
+
constructor(n, config = Parser.getConfig(), accum = []) {
|
|
18
|
+
super('-'.repeat(n), config, accum);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @this {HrToken & {firstChild: string}} */
|
|
22
|
+
cloneNode() {
|
|
23
|
+
return Parser.run(() => new HrToken(this.firstChild.length, this.getAttribute('config')));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @returns {[number, string][]} */
|
|
27
|
+
plain() {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @param {string} str */
|
|
32
|
+
setText(str) {
|
|
33
|
+
if (!/^-{4,}$/.test(str)) {
|
|
34
|
+
throw new RangeError('<hr>总是写作不少于4个的连续"-"!');
|
|
35
|
+
}
|
|
36
|
+
return super.setText(str);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Parser.classes.HrToken = __filename;
|
|
41
|
+
module.exports = HrToken;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fixedToken = require('../../mixin/fixedToken'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('../..'),
|
|
5
|
+
Token = require('..');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 纯文字Token,不会被解析
|
|
9
|
+
* @classdesc `{childNodes: [string]}`
|
|
10
|
+
*/
|
|
11
|
+
class NowikiToken extends fixedToken(Token) {
|
|
12
|
+
type = 'ext-inner';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} wikitext
|
|
16
|
+
* @param {accum} accum
|
|
17
|
+
*/
|
|
18
|
+
constructor(wikitext, config = Parser.getConfig(), accum = []) {
|
|
19
|
+
super(wikitext, config, false, accum);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @this {NowikiToken & {firstChild: string}} */
|
|
23
|
+
cloneNode() {
|
|
24
|
+
const /** @type {typeof NowikiToken} */ Constructor = this.constructor,
|
|
25
|
+
token = Parser.run(() => new Constructor(this.firstChild, this.getAttribute('config')));
|
|
26
|
+
token.type = this.type;
|
|
27
|
+
return token;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {string} str */
|
|
31
|
+
setText(str) {
|
|
32
|
+
return super.setText(str, 0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Parser.classes.NowikiToken = __filename;
|
|
37
|
+
module.exports = NowikiToken;
|