wikiparser-node 0.3.1 → 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/README.md +1 -1
- package/config/default.json +13 -17
- package/config/llwiki.json +11 -79
- package/config/moegirl.json +7 -1
- package/config/zhwiki.json +1269 -0
- package/index.js +130 -97
- package/lib/element.js +410 -518
- package/lib/node.js +493 -115
- package/lib/ranges.js +27 -19
- package/lib/text.js +175 -0
- package/lib/title.js +14 -6
- package/mixin/attributeParent.js +70 -24
- package/mixin/fixedToken.js +18 -10
- package/mixin/hidden.js +6 -4
- package/mixin/sol.js +39 -12
- package/package.json +17 -4
- package/parser/brackets.js +18 -18
- package/parser/commentAndExt.js +16 -14
- package/parser/converter.js +14 -13
- package/parser/externalLinks.js +12 -11
- package/parser/hrAndDoubleUnderscore.js +24 -14
- package/parser/html.js +8 -7
- package/parser/links.js +13 -13
- package/parser/list.js +12 -11
- package/parser/magicLinks.js +11 -10
- package/parser/quotes.js +6 -5
- package/parser/selector.js +175 -0
- package/parser/table.js +31 -24
- package/src/arg.js +91 -43
- package/src/atom/hidden.js +5 -2
- package/src/atom/index.js +17 -9
- package/src/attribute.js +210 -101
- package/src/converter.js +78 -43
- package/src/converterFlags.js +104 -45
- package/src/converterRule.js +136 -78
- package/src/extLink.js +81 -27
- package/src/gallery.js +63 -20
- package/src/heading.js +58 -20
- package/src/html.js +138 -48
- package/src/imageParameter.js +93 -58
- package/src/index.js +314 -186
- package/src/link/category.js +22 -54
- package/src/link/file.js +83 -32
- package/src/link/galleryImage.js +21 -7
- package/src/link/index.js +170 -81
- package/src/magicLink.js +64 -14
- package/src/nowiki/comment.js +36 -10
- package/src/nowiki/dd.js +37 -22
- package/src/nowiki/doubleUnderscore.js +21 -7
- package/src/nowiki/hr.js +11 -7
- package/src/nowiki/index.js +16 -9
- package/src/nowiki/list.js +2 -2
- package/src/nowiki/noinclude.js +8 -4
- package/src/nowiki/quote.js +38 -7
- package/src/onlyinclude.js +24 -7
- package/src/parameter.js +102 -62
- package/src/syntax.js +23 -20
- package/src/table/index.js +282 -174
- package/src/table/td.js +112 -61
- package/src/table/tr.js +135 -74
- package/src/tagPair/ext.js +30 -23
- package/src/tagPair/include.js +26 -11
- package/src/tagPair/index.js +72 -29
- package/src/transclude.js +235 -127
- package/tool/index.js +42 -32
- package/util/debug.js +21 -18
- package/util/diff.js +76 -0
- package/util/lint.js +40 -0
- package/util/string.js +56 -26
- package/.eslintrc.json +0 -319
- package/errors/README +0 -1
- package/jsconfig.json +0 -7
- package/printed/README +0 -1
- package/typings/element.d.ts +0 -28
- package/typings/index.d.ts +0 -52
- package/typings/node.d.ts +0 -23
- package/typings/parser.d.ts +0 -9
- package/typings/table.d.ts +0 -14
- package/typings/token.d.ts +0 -22
- package/typings/tool.d.ts +0 -10
package/src/heading.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {generateForSelf} = require('../util/lint'),
|
|
4
|
+
fixedToken = require('../mixin/fixedToken'),
|
|
4
5
|
sol = require('../mixin/sol'),
|
|
5
|
-
|
|
6
|
+
Parser = require('..'),
|
|
6
7
|
Token = require('.');
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -12,9 +13,14 @@ const fixedToken = require('../mixin/fixedToken'),
|
|
|
12
13
|
class HeadingToken extends fixedToken(sol(Token)) {
|
|
13
14
|
type = 'heading';
|
|
14
15
|
|
|
16
|
+
/** 内部wikitext */
|
|
17
|
+
get innerText() {
|
|
18
|
+
return this.firstChild.text();
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
|
-
* @param {number} level
|
|
17
|
-
* @param {string[]} input
|
|
22
|
+
* @param {number} level 标题层级
|
|
23
|
+
* @param {string[]} input 标题文字
|
|
18
24
|
* @param {accum} accum
|
|
19
25
|
*/
|
|
20
26
|
constructor(level, input, config = Parser.getConfig(), accum = []) {
|
|
@@ -23,57 +29,89 @@ class HeadingToken extends fixedToken(sol(Token)) {
|
|
|
23
29
|
const token = new Token(input[0], config, true, accum);
|
|
24
30
|
token.type = 'heading-title';
|
|
25
31
|
token.setAttribute('name', this.name).setAttribute('stage', 2);
|
|
26
|
-
const SyntaxToken = require('./syntax')
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
const SyntaxToken = require('./syntax');
|
|
33
|
+
const trail = new SyntaxToken(input[1], /^[^\S\n]*$/u, 'heading-trail', config, accum, {
|
|
34
|
+
'Stage-1': ':', '!ExtToken': '',
|
|
35
|
+
});
|
|
30
36
|
this.append(token, trail);
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
/** @override */
|
|
33
40
|
cloneNode() {
|
|
34
|
-
const [title, trail] = this.
|
|
41
|
+
const [title, trail] = this.cloneChildNodes(),
|
|
35
42
|
token = Parser.run(() => new HeadingToken(Number(this.name), [], this.getAttribute('config')));
|
|
36
|
-
token.
|
|
37
|
-
token.
|
|
43
|
+
token.firsthild.safeReplaceWith(title);
|
|
44
|
+
token.lastChild.safeReplaceWith(trail);
|
|
38
45
|
return token;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
48
|
+
/**
|
|
49
|
+
* @override
|
|
50
|
+
* @this {{prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'} & HeadingToken}
|
|
51
|
+
* @param {string} selector
|
|
52
|
+
*/
|
|
53
|
+
toString(selector) {
|
|
43
54
|
const equals = '='.repeat(Number(this.name));
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
return selector && this.matches(selector)
|
|
56
|
+
? ''
|
|
57
|
+
: `${this.prependNewLine()}${equals}${
|
|
58
|
+
this.firstChild.toString(selector)
|
|
59
|
+
}${equals}${this.lastChild.toString(selector)}${this.appendNewLine()}`;
|
|
47
60
|
}
|
|
48
61
|
|
|
62
|
+
/** @override */
|
|
49
63
|
getPadding() {
|
|
50
64
|
return super.getPadding() + Number(this.name);
|
|
51
65
|
}
|
|
52
66
|
|
|
67
|
+
/** @override */
|
|
53
68
|
getGaps() {
|
|
54
69
|
return Number(this.name);
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
/** @override */
|
|
73
|
+
print() {
|
|
74
|
+
const equals = '='.repeat(Number(this.name));
|
|
75
|
+
return super.print({pre: equals, sep: equals});
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
/**
|
|
79
|
+
* @override
|
|
80
|
+
* @param {number} start 起始位置
|
|
81
|
+
*/
|
|
82
|
+
lint(start = 0) {
|
|
83
|
+
const errors = super.lint(start);
|
|
84
|
+
if (this.name === '1') {
|
|
85
|
+
errors.push(generateForSelf(this, this.getRootNode().posFromIndex(start), '<h1>'));
|
|
86
|
+
}
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @override
|
|
58
92
|
* @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}}
|
|
59
93
|
* @returns {string}
|
|
60
94
|
*/
|
|
61
95
|
text() {
|
|
62
96
|
const equals = '='.repeat(Number(this.name));
|
|
63
|
-
return `${this.prependNewLine()}${equals}${this.
|
|
97
|
+
return `${this.prependNewLine()}${equals}${this.firstChild.text()}${equals}${this.appendNewLine()}`;
|
|
64
98
|
}
|
|
65
99
|
|
|
66
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* 设置标题层级
|
|
102
|
+
* @param {number} n 标题层级
|
|
103
|
+
*/
|
|
67
104
|
setLevel(n) {
|
|
68
105
|
if (typeof n !== 'number') {
|
|
69
106
|
this.typeError('setLevel', 'Number');
|
|
70
107
|
}
|
|
71
108
|
n = Math.min(Math.max(n, 1), 6);
|
|
72
|
-
this.setAttribute('name', String(n)).
|
|
109
|
+
this.setAttribute('name', String(n)).firstChild.setAttribute('name', this.name);
|
|
73
110
|
}
|
|
74
111
|
|
|
112
|
+
/** 移除标题后的不可见内容 */
|
|
75
113
|
removeTrail() {
|
|
76
|
-
this.
|
|
114
|
+
this.lastChild.replaceChildren();
|
|
77
115
|
}
|
|
78
116
|
}
|
|
79
117
|
|
package/src/html.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {noWrap} = require('../util/string'),
|
|
4
|
+
{generateForSelf} = require('../util/lint'),
|
|
4
5
|
fixedToken = require('../mixin/fixedToken'),
|
|
5
6
|
attributeParent = require('../mixin/attributeParent'),
|
|
6
|
-
|
|
7
|
+
Parser = require('..'),
|
|
7
8
|
Token = require('.');
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -12,57 +13,136 @@ const {noWrap} = require('../util/string'),
|
|
|
12
13
|
*/
|
|
13
14
|
class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
14
15
|
type = 'html';
|
|
15
|
-
closing;
|
|
16
|
-
selfClosing;
|
|
16
|
+
#closing;
|
|
17
|
+
#selfClosing;
|
|
17
18
|
#tag;
|
|
18
19
|
|
|
20
|
+
/** getter */
|
|
21
|
+
get closing() {
|
|
22
|
+
return this.#closing;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @throws `Error` 自闭合标签或空标签 */
|
|
26
|
+
set closing(value) {
|
|
27
|
+
if (!value) {
|
|
28
|
+
this.#closing = false;
|
|
29
|
+
return;
|
|
30
|
+
} else if (this.#selfClosing) {
|
|
31
|
+
throw new Error(`这是一个自闭合标签!`);
|
|
32
|
+
}
|
|
33
|
+
const {html: [,, tags]} = this.getAttribute('config');
|
|
34
|
+
if (tags.includes(this.name)) {
|
|
35
|
+
throw new Error(`这是一个空标签!`);
|
|
36
|
+
}
|
|
37
|
+
this.#closing = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** getter */
|
|
41
|
+
get selfClosing() {
|
|
42
|
+
return this.#selfClosing;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @throws `Error` 闭合标签或无效自闭合标签 */
|
|
46
|
+
set selfClosing(value) {
|
|
47
|
+
if (!value) {
|
|
48
|
+
this.#selfClosing = false;
|
|
49
|
+
return;
|
|
50
|
+
} else if (this.#closing) {
|
|
51
|
+
throw new Error('这是一个闭合标签!');
|
|
52
|
+
}
|
|
53
|
+
const {html: [tags]} = this.getAttribute('config');
|
|
54
|
+
if (tags.includes(this.name)) {
|
|
55
|
+
throw new Error(`<${this.name}>标签自闭合无效!`);
|
|
56
|
+
}
|
|
57
|
+
this.#selfClosing = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
19
60
|
/**
|
|
20
|
-
* @param {string} name
|
|
21
|
-
* @param {AttributeToken} attr
|
|
22
|
-
* @param {boolean} closing
|
|
23
|
-
* @param {boolean} selfClosing
|
|
61
|
+
* @param {string} name 标签名
|
|
62
|
+
* @param {AttributeToken} attr 标签属性
|
|
63
|
+
* @param {boolean} closing 是否闭合
|
|
64
|
+
* @param {boolean} selfClosing 是否自封闭
|
|
24
65
|
* @param {accum} accum
|
|
25
66
|
*/
|
|
26
67
|
constructor(name, attr, closing, selfClosing, config = Parser.getConfig(), accum = []) {
|
|
27
68
|
super(undefined, config, true, accum);
|
|
28
69
|
this.appendChild(attr);
|
|
29
70
|
this.setAttribute('name', name.toLowerCase());
|
|
30
|
-
this
|
|
31
|
-
this
|
|
71
|
+
this.#closing = closing;
|
|
72
|
+
this.#selfClosing = selfClosing;
|
|
32
73
|
this.#tag = name;
|
|
33
74
|
}
|
|
34
75
|
|
|
76
|
+
/** @override */
|
|
35
77
|
cloneNode() {
|
|
36
|
-
const [attr] = this.
|
|
78
|
+
const [attr] = this.cloneChildNodes(),
|
|
37
79
|
config = this.getAttribute('config');
|
|
38
|
-
return Parser.run(() => new HtmlToken(this.#tag, attr, this
|
|
80
|
+
return Parser.run(() => new HtmlToken(this.#tag, attr, this.#closing, this.#selfClosing, config));
|
|
39
81
|
}
|
|
40
82
|
|
|
41
83
|
/**
|
|
84
|
+
* @override
|
|
42
85
|
* @template {string} T
|
|
43
|
-
* @param {T} key
|
|
86
|
+
* @param {T} key 属性键
|
|
44
87
|
* @returns {TokenAttribute<T>}
|
|
45
88
|
*/
|
|
46
89
|
getAttribute(key) {
|
|
47
|
-
|
|
48
|
-
return this.#tag;
|
|
49
|
-
}
|
|
50
|
-
return super.getAttribute(key);
|
|
90
|
+
return key === 'tag' ? this.#tag : super.getAttribute(key);
|
|
51
91
|
}
|
|
52
92
|
|
|
53
|
-
|
|
54
|
-
|
|
93
|
+
/**
|
|
94
|
+
* @override
|
|
95
|
+
* @param {string} selector
|
|
96
|
+
*/
|
|
97
|
+
toString(selector) {
|
|
98
|
+
return selector && this.matches(selector)
|
|
99
|
+
? ''
|
|
100
|
+
: `<${this.#closing ? '/' : ''}${this.#tag}${super.toString(selector)}${this.#selfClosing ? '/' : ''}>`;
|
|
55
101
|
}
|
|
56
102
|
|
|
103
|
+
/** @override */
|
|
57
104
|
getPadding() {
|
|
58
|
-
return this.#tag.length + (this
|
|
105
|
+
return this.#tag.length + (this.#closing ? 2 : 1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @override */
|
|
109
|
+
print() {
|
|
110
|
+
return super.print({
|
|
111
|
+
pre: `<${this.#closing ? '/' : ''}${this.#tag}`, post: `${this.#selfClosing ? '/' : ''}>`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @override
|
|
117
|
+
* @param {number} start 起始位置
|
|
118
|
+
*/
|
|
119
|
+
lint(start = 0) {
|
|
120
|
+
const errors = super.lint(start);
|
|
121
|
+
let /** @type {LintError} */ refError;
|
|
122
|
+
if (this.name === 'h1' && !this.#closing) {
|
|
123
|
+
refError = generateForSelf(this, this.getRootNode().posFromIndex(start), '<h1>');
|
|
124
|
+
errors.push(refError);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
this.findMatchingTag();
|
|
128
|
+
} catch ({message: errorMsg}) {
|
|
129
|
+
const [message] = errorMsg.split(':');
|
|
130
|
+
refError ||= generateForSelf(this, this.getRootNode().posFromIndex(start), '');
|
|
131
|
+
errors.push({...refError, message, severity: message === '未闭合的标签' ? 'warning' : 'error'});
|
|
132
|
+
}
|
|
133
|
+
return errors;
|
|
59
134
|
}
|
|
60
135
|
|
|
136
|
+
/** @override */
|
|
61
137
|
text() {
|
|
62
|
-
return `<${this
|
|
138
|
+
return `<${this.#closing ? '/' : ''}${this.#tag}${super.text()}${this.#selfClosing ? '/' : ''}>`;
|
|
63
139
|
}
|
|
64
140
|
|
|
65
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* 更换标签名
|
|
143
|
+
* @param {string} tag 标签名
|
|
144
|
+
* @throws `RangeError` 非法的HTML标签
|
|
145
|
+
*/
|
|
66
146
|
replaceTag(tag) {
|
|
67
147
|
const name = tag.toLowerCase();
|
|
68
148
|
if (!this.getAttribute('config').html.flat().includes(name)) {
|
|
@@ -71,27 +151,32 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
71
151
|
this.setAttribute('name', name).#tag = tag;
|
|
72
152
|
}
|
|
73
153
|
|
|
74
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* 搜索匹配的标签
|
|
156
|
+
* @complexity `n`
|
|
157
|
+
* @throws `SyntaxError` 同时闭合和自封闭的标签
|
|
158
|
+
* @throws `SyntaxError` 无效自封闭标签
|
|
159
|
+
* @throws `SyntaxError` 未闭合的标签
|
|
160
|
+
*/
|
|
75
161
|
findMatchingTag() {
|
|
76
162
|
const {html} = this.getAttribute('config'),
|
|
77
|
-
{name
|
|
78
|
-
string = noWrap(this
|
|
79
|
-
if (closing && selfClosing) {
|
|
163
|
+
{name: tagName, parentNode} = this,
|
|
164
|
+
string = noWrap(String(this));
|
|
165
|
+
if (this.#closing && this.#selfClosing) {
|
|
80
166
|
throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
|
|
81
|
-
} else if (html[2].includes(
|
|
167
|
+
} else if (html[2].includes(tagName) || this.#selfClosing && html[1].includes(tagName)) { // 自封闭标签
|
|
82
168
|
return this;
|
|
83
|
-
} else if (selfClosing && html[0].includes(
|
|
169
|
+
} else if (this.#selfClosing && html[0].includes(tagName)) {
|
|
84
170
|
throw new SyntaxError(`无效自封闭标签:${string}`);
|
|
85
|
-
} else if (!
|
|
86
|
-
return;
|
|
171
|
+
} else if (!parentNode) {
|
|
172
|
+
return undefined;
|
|
87
173
|
}
|
|
88
|
-
const {
|
|
89
|
-
i =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let imbalance = closing ? -1 : 1;
|
|
174
|
+
const {childNodes} = parentNode,
|
|
175
|
+
i = childNodes.indexOf(this),
|
|
176
|
+
siblings = this.#closing
|
|
177
|
+
? childNodes.slice(0, i).reverse().filter(({type, name}) => type === 'html' && name === tagName)
|
|
178
|
+
: childNodes.slice(i + 1).filter(({type, name}) => type === 'html' && name === tagName);
|
|
179
|
+
let imbalance = this.#closing ? -1 : 1;
|
|
95
180
|
for (const token of siblings) {
|
|
96
181
|
if (token.closing) {
|
|
97
182
|
imbalance--;
|
|
@@ -102,35 +187,40 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
|
|
|
102
187
|
return token;
|
|
103
188
|
}
|
|
104
189
|
}
|
|
105
|
-
throw new SyntaxError(`未${closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
|
|
190
|
+
throw new SyntaxError(`未${this.#closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
|
|
106
191
|
}
|
|
107
192
|
|
|
193
|
+
/** 局部闭合 */
|
|
108
194
|
#localMatch() {
|
|
109
|
-
this
|
|
195
|
+
this.#selfClosing = false;
|
|
110
196
|
const root = Parser.parse(`</${this.name}>`, false, 3, this.getAttribute('config'));
|
|
111
197
|
this.after(root.firstChild);
|
|
112
198
|
}
|
|
113
199
|
|
|
114
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* 修复无效自封闭标签
|
|
202
|
+
* @complexity `n`
|
|
203
|
+
* @throws `Error` 无法修复无效自封闭标签
|
|
204
|
+
*/
|
|
115
205
|
fix() {
|
|
116
206
|
const config = this.getAttribute('config'),
|
|
117
|
-
{
|
|
118
|
-
if (!
|
|
207
|
+
{parentNode, name: tagName, firstChild} = this;
|
|
208
|
+
if (!parentNode || !this.#selfClosing || !config.html[0].includes(tagName)) {
|
|
119
209
|
return;
|
|
120
|
-
} else if (
|
|
210
|
+
} else if (firstChild.text().trim()) {
|
|
121
211
|
this.#localMatch();
|
|
122
212
|
return;
|
|
123
213
|
}
|
|
124
|
-
const {
|
|
125
|
-
i =
|
|
214
|
+
const {childNodes} = parentNode,
|
|
215
|
+
i = childNodes.indexOf(this),
|
|
126
216
|
/** @type {HtmlToken[]} */
|
|
127
|
-
prevSiblings =
|
|
217
|
+
prevSiblings = childNodes.slice(0, i).filter(({type, name}) => type === 'html' && name === tagName),
|
|
128
218
|
imbalance = prevSiblings.reduce((acc, {closing}) => acc + (closing ? 1 : -1), 0);
|
|
129
219
|
if (imbalance < 0) {
|
|
130
|
-
this
|
|
131
|
-
this
|
|
220
|
+
this.#selfClosing = false;
|
|
221
|
+
this.#closing = true;
|
|
132
222
|
} else {
|
|
133
|
-
Parser.warn('无法修复无效自封闭标签', noWrap(this
|
|
223
|
+
Parser.warn('无法修复无效自封闭标签', noWrap(String(this)));
|
|
134
224
|
throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
|
|
135
225
|
}
|
|
136
226
|
}
|