wikiparser-node 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +25 -2
- package/lib/element.js +69 -185
- package/lib/node.js +159 -1
- package/lib/ranges.js +1 -2
- package/lib/text.js +35 -6
- package/lib/title.js +1 -1
- package/mixin/fixedToken.js +4 -4
- package/mixin/sol.js +17 -7
- package/package.json +11 -1
- package/parser/commentAndExt.js +1 -1
- package/parser/converter.js +1 -1
- package/parser/externalLinks.js +1 -1
- package/parser/hrAndDoubleUnderscore.js +6 -5
- package/parser/links.js +1 -2
- package/parser/magicLinks.js +1 -1
- package/parser/selector.js +5 -5
- package/parser/table.js +12 -12
- package/src/arg.js +44 -20
- package/src/attribute.js +34 -7
- package/src/converter.js +13 -5
- package/src/converterFlags.js +42 -5
- package/src/converterRule.js +25 -19
- package/src/extLink.js +20 -14
- package/src/gallery.js +35 -4
- package/src/heading.js +28 -9
- package/src/html.js +46 -18
- package/src/imageParameter.js +13 -7
- package/src/index.js +22 -15
- package/src/link/category.js +6 -6
- package/src/link/file.js +25 -5
- package/src/link/index.js +36 -33
- package/src/magicLink.js +32 -4
- package/src/nowiki/comment.js +14 -0
- package/src/nowiki/doubleUnderscore.js +5 -0
- package/src/nowiki/quote.js +28 -1
- package/src/onlyinclude.js +5 -0
- package/src/parameter.js +48 -35
- package/src/table/index.js +37 -24
- package/src/table/td.js +23 -17
- package/src/table/tr.js +47 -30
- package/src/tagPair/ext.js +4 -5
- package/src/tagPair/include.js +10 -0
- package/src/tagPair/index.js +8 -0
- package/src/transclude.js +79 -46
- package/tool/index.js +1 -1
- package/{test/util.js → util/diff.js} +14 -18
- package/util/lint.js +40 -0
- package/util/string.js +20 -3
- package/.eslintrc.json +0 -714
- package/errors/README +0 -1
- package/jsconfig.json +0 -7
- package/printed/README +0 -1
- package/printed/example.json +0 -120
- package/test/api.js +0 -83
- package/test/real.js +0 -133
- package/test/test.js +0 -28
- package/typings/api.d.ts +0 -13
- package/typings/array.d.ts +0 -28
- package/typings/event.d.ts +0 -24
- package/typings/index.d.ts +0 -94
- package/typings/node.d.ts +0 -29
- package/typings/parser.d.ts +0 -16
- package/typings/table.d.ts +0 -14
- package/typings/token.d.ts +0 -22
- package/typings/tool.d.ts +0 -11
package/src/link/index.js
CHANGED
|
@@ -62,12 +62,12 @@ class LinkToken extends Token {
|
|
|
62
62
|
|
|
63
63
|
/** 链接显示文字 */
|
|
64
64
|
get innerText() {
|
|
65
|
-
if (this.type
|
|
66
|
-
return
|
|
65
|
+
if (this.type === 'link') {
|
|
66
|
+
return this.childNodes.length > 1
|
|
67
|
+
? this.lastChild.text()
|
|
68
|
+
: this.firstChild.text().replace(/^\s*:/u, '');
|
|
67
69
|
}
|
|
68
|
-
return
|
|
69
|
-
? this.lastElementChild.text()
|
|
70
|
-
: this.firstElementChild.text().replace(/^\s*:/u, '');
|
|
70
|
+
return undefined;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -92,7 +92,7 @@ class LinkToken extends Token {
|
|
|
92
92
|
|
|
93
93
|
/** 生成Title对象 */
|
|
94
94
|
#getTitle() {
|
|
95
|
-
return this.normalizeTitle(this.
|
|
95
|
+
return this.normalizeTitle(this.firstChild.text());
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/** @override */
|
|
@@ -102,7 +102,7 @@ class LinkToken extends Token {
|
|
|
102
102
|
/** @type {this & {constructor: typeof LinkToken}} */
|
|
103
103
|
const {constructor} = this,
|
|
104
104
|
token = new constructor('', undefined, this.#getTitle(), this.getAttribute('config'));
|
|
105
|
-
token.
|
|
105
|
+
token.firstChild.safeReplaceWith(link);
|
|
106
106
|
token.append(...linkText);
|
|
107
107
|
return token.afterBuild();
|
|
108
108
|
});
|
|
@@ -161,6 +161,11 @@ class LinkToken extends Token {
|
|
|
161
161
|
return 1;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/** @override */
|
|
165
|
+
print() {
|
|
166
|
+
return super.print(this.type === 'gallery-image' ? {sep: '|'} : {pre: '[[', post: ']]', sep: '|'});
|
|
167
|
+
}
|
|
168
|
+
|
|
164
169
|
/** @override */
|
|
165
170
|
text() {
|
|
166
171
|
const str = super.text('|');
|
|
@@ -178,14 +183,14 @@ class LinkToken extends Token {
|
|
|
178
183
|
link = `:${link}`;
|
|
179
184
|
}
|
|
180
185
|
const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
181
|
-
{childNodes: {length},
|
|
182
|
-
|
|
186
|
+
{childNodes: {length}, firstChild: wikiLink} = root,
|
|
187
|
+
{type, firstChild, childNodes: {length: linkLength}} = wikiLink;
|
|
188
|
+
if (length !== 1 || type !== this.type || linkLength !== 1) {
|
|
183
189
|
const msgs = {link: '内链', file: '文件链接', category: '分类', 'gallery-image': '文件链接'};
|
|
184
190
|
throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
|
|
185
191
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
192
|
+
wikiLink.destroy(true);
|
|
193
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
/**
|
|
@@ -206,15 +211,13 @@ class LinkToken extends Token {
|
|
|
206
211
|
link = link.slice(1);
|
|
207
212
|
}
|
|
208
213
|
const root = Parser.parse(`[[${lang}:${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
|
|
209
|
-
/** @type {Token & {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
) {
|
|
214
|
+
/** @type {Token & {firstChild: LinkToken}} */ {childNodes: {length}, firstChild: wikiLink} = root,
|
|
215
|
+
{type, childNodes: {length: linkLength}, interwiki, firstChild} = wikiLink;
|
|
216
|
+
if (length !== 1 || type !== 'link' || linkLength !== 1 || interwiki !== lang.toLowerCase()) {
|
|
213
217
|
throw new SyntaxError(`非法的跨语言链接目标:${lang}:${link}`);
|
|
214
218
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
219
|
+
wikiLink.destroy(true);
|
|
220
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
/**
|
|
@@ -228,16 +231,16 @@ class LinkToken extends Token {
|
|
|
228
231
|
const include = this.getAttribute('include'),
|
|
229
232
|
config = this.getAttribute('config'),
|
|
230
233
|
root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
|
|
231
|
-
{childNodes: {length},
|
|
232
|
-
|
|
234
|
+
{childNodes: {length}, firstChild: wikiLink} = root,
|
|
235
|
+
{type, childNodes: {length: linkLength}, firstChild} = wikiLink;
|
|
236
|
+
if (length !== 1 || type !== 'link' || linkLength !== 1) {
|
|
233
237
|
throw new SyntaxError(`非法的 fragment:${fragment}`);
|
|
234
238
|
}
|
|
235
239
|
if (page) {
|
|
236
240
|
Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
|
|
237
241
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.firstElementChild.safeReplaceWith(firstChild);
|
|
242
|
+
wikiLink.destroy(true);
|
|
243
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
/**
|
|
@@ -268,25 +271,25 @@ class LinkToken extends Token {
|
|
|
268
271
|
*/
|
|
269
272
|
setLinkText(linkText = '') {
|
|
270
273
|
linkText = String(linkText);
|
|
271
|
-
let
|
|
274
|
+
let lastChild;
|
|
272
275
|
const config = this.getAttribute('config');
|
|
273
276
|
if (linkText) {
|
|
274
277
|
const root = Parser.parse(`[[${
|
|
275
278
|
this.type === 'category' ? 'Category:' : ''
|
|
276
279
|
}L|${linkText}]]`, this.getAttribute('include'), 6, config),
|
|
277
|
-
{childNodes: {length},
|
|
278
|
-
if (length !== 1 ||
|
|
280
|
+
{childNodes: {length}, firstChild: wikiLink} = root;
|
|
281
|
+
if (length !== 1 || wikiLink.type !== this.type || wikiLink.childNodes.length !== 2) {
|
|
279
282
|
throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${noWrap(linkText)}`);
|
|
280
283
|
}
|
|
281
|
-
({
|
|
284
|
+
({lastChild} = wikiLink);
|
|
282
285
|
} else {
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
lastChild = Parser.run(() => new Token('', config));
|
|
287
|
+
lastChild.setAttribute('stage', 7).type = 'link-text';
|
|
285
288
|
}
|
|
286
289
|
if (this.childNodes.length === 1) {
|
|
287
|
-
this.appendChild(
|
|
290
|
+
this.appendChild(lastChild);
|
|
288
291
|
} else {
|
|
289
|
-
this.
|
|
292
|
+
this.lastChild.safeReplaceWith(lastChild);
|
|
290
293
|
}
|
|
291
294
|
}
|
|
292
295
|
|
|
@@ -295,7 +298,7 @@ class LinkToken extends Token {
|
|
|
295
298
|
* @throws `Error` 带有"#"或"%"时不可用
|
|
296
299
|
*/
|
|
297
300
|
pipeTrick() {
|
|
298
|
-
const linkText = this.
|
|
301
|
+
const linkText = this.firstChild.text();
|
|
299
302
|
if (linkText.includes('#') || linkText.includes('%')) {
|
|
300
303
|
throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
|
|
301
304
|
}
|
package/src/magicLink.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {generateForChild} = require('../util/lint'),
|
|
4
|
+
Parser = require('..'),
|
|
5
|
+
AstText = require('../lib/text'),
|
|
4
6
|
Token = require('.');
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -48,6 +50,32 @@ class MagicLinkToken extends Token {
|
|
|
48
50
|
this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'iu');
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @override
|
|
55
|
+
* @param {number} start 起始位置
|
|
56
|
+
*/
|
|
57
|
+
lint(start = 0) {
|
|
58
|
+
const errors = super.lint(start);
|
|
59
|
+
let /** @type {{top: number, left: number}} */ rect;
|
|
60
|
+
for (const child of this.childNodes) {
|
|
61
|
+
const str = String(child);
|
|
62
|
+
if (child.type !== 'text' || !/[,;。:!?()【】]/u.test(str)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
rect ||= this.getRootNode().posFromIndex(start);
|
|
66
|
+
const refError = generateForChild(child, rect, 'URL中的全角标点', 'warning');
|
|
67
|
+
errors.push(...[...str.matchAll(/[,;。:!?()【】]/gu)].map(({index}) => {
|
|
68
|
+
const lines = str.slice(0, index).split('\n'),
|
|
69
|
+
{length: top} = lines,
|
|
70
|
+
{length: left} = lines.at(-1),
|
|
71
|
+
startLine = refError.startLine + top - 1,
|
|
72
|
+
startCol = top > 1 ? left : refError.startCol + left;
|
|
73
|
+
return {...refError, startLine, endLine: startLine, startCol, endCol: startCol + 1};
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
|
|
51
79
|
/** @override */
|
|
52
80
|
afterBuild() {
|
|
53
81
|
const ParameterToken = require('./parameter');
|
|
@@ -96,11 +124,11 @@ class MagicLinkToken extends Token {
|
|
|
96
124
|
setTarget(url) {
|
|
97
125
|
url = String(url);
|
|
98
126
|
const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
|
|
99
|
-
{childNodes: {length},
|
|
100
|
-
if (length !== 1 ||
|
|
127
|
+
{childNodes: {length}, firstChild: freeExtLink} = root;
|
|
128
|
+
if (length !== 1 || freeExtLink.type !== 'free-ext-link') {
|
|
101
129
|
throw new SyntaxError(`非法的自由外链目标:${url}`);
|
|
102
130
|
}
|
|
103
|
-
this.replaceChildren(...
|
|
131
|
+
this.replaceChildren(...freeExtLink.childNodes);
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
134
|
|
package/src/nowiki/comment.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const hidden = require('../../mixin/hidden'),
|
|
4
|
+
{generateForSelf} = require('../../util/lint'),
|
|
4
5
|
Parser = require('../..'),
|
|
5
6
|
NowikiToken = require('.');
|
|
6
7
|
|
|
@@ -51,6 +52,19 @@ class CommentToken extends hidden(NowikiToken) {
|
|
|
51
52
|
getPadding() {
|
|
52
53
|
return 4;
|
|
53
54
|
}
|
|
55
|
+
|
|
56
|
+
/** @override */
|
|
57
|
+
print() {
|
|
58
|
+
return super.print({pre: '<!--', post: this.closed ? '-->' : ''});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @override
|
|
63
|
+
* @param {number} start 起始位置
|
|
64
|
+
*/
|
|
65
|
+
lint(start = 0) {
|
|
66
|
+
return this.closed ? [] : [generateForSelf(this, this.getRootNode().posFromIndex(start), '未闭合的HTML注释')];
|
|
67
|
+
}
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
Parser.classes.CommentToken = __filename;
|
package/src/nowiki/quote.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const {generateForSelf} = require('../../util/lint'),
|
|
4
|
+
Parser = require('../..'),
|
|
5
|
+
AstText = require('../../lib/text'),
|
|
4
6
|
NowikiToken = require('.');
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -19,6 +21,31 @@ class QuoteToken extends NowikiToken {
|
|
|
19
21
|
this.setAttribute('name', String(n));
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @override
|
|
26
|
+
* @this {AstText}
|
|
27
|
+
* @param {number} start 起始位置
|
|
28
|
+
*/
|
|
29
|
+
lint(start = 0) {
|
|
30
|
+
const {previousSibling, nextSibling} = this,
|
|
31
|
+
message = `孤立的"'"`,
|
|
32
|
+
/** @type {LintError[]} */ errors = [];
|
|
33
|
+
let /** @type {LintError} */ refError;
|
|
34
|
+
if (previousSibling?.type === 'text' && previousSibling.data.at(-1) === "'") {
|
|
35
|
+
refError = generateForSelf(this, this.getRootNode().posFromIndex(start), '');
|
|
36
|
+
const {startLine, startCol} = refError,
|
|
37
|
+
[{length}] = previousSibling.data.match(/(?<!')'+$/u);
|
|
38
|
+
errors.push({message, startLine, startCol: startCol - length, endLine: startLine, endCol: startCol});
|
|
39
|
+
}
|
|
40
|
+
if (nextSibling?.type === 'text' && nextSibling.data[0] === "'") {
|
|
41
|
+
refError ||= generateForSelf(this, this.getRootNode().posFromIndex(start), '');
|
|
42
|
+
const {endLine, endCol} = refError,
|
|
43
|
+
[{length}] = nextSibling.data.match(/^'+/u);
|
|
44
|
+
errors.push({message, startLine: endLine, startCol: endCol, endLine, endCol: endCol + length});
|
|
45
|
+
}
|
|
46
|
+
return errors;
|
|
47
|
+
}
|
|
48
|
+
|
|
22
49
|
/**
|
|
23
50
|
* @override
|
|
24
51
|
* @param {string} str 新文本
|
package/src/onlyinclude.js
CHANGED
package/src/parameter.js
CHANGED
|
@@ -14,7 +14,7 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
14
14
|
|
|
15
15
|
/** 是否是匿名参数 */
|
|
16
16
|
get anon() {
|
|
17
|
-
return this.
|
|
17
|
+
return this.firstChild.childNodes.length === 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** getValue()的getter */
|
|
@@ -48,8 +48,8 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
48
48
|
config = this.getAttribute('config');
|
|
49
49
|
return Parser.run(() => {
|
|
50
50
|
const token = new ParameterToken(this.anon ? Number(this.name) : undefined, undefined, config);
|
|
51
|
-
token.
|
|
52
|
-
token.
|
|
51
|
+
token.firstChild.safeReplaceWith(key);
|
|
52
|
+
token.lastChild.safeReplaceWith(value);
|
|
53
53
|
return token.afterBuild();
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -57,7 +57,7 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
57
57
|
/** @override */
|
|
58
58
|
afterBuild() {
|
|
59
59
|
if (!this.anon) {
|
|
60
|
-
const name = this.
|
|
60
|
+
const name = this.firstChild.text().trim(),
|
|
61
61
|
{parentNode} = this;
|
|
62
62
|
this.setAttribute('name', name);
|
|
63
63
|
if (parentNode && parentNode instanceof require('./transclude')) {
|
|
@@ -67,9 +67,9 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
67
67
|
}
|
|
68
68
|
const /** @type {AstListener} */ parameterListener = ({prevTarget}, data) => {
|
|
69
69
|
if (!this.anon) { // 匿名参数不管怎么变动还是匿名
|
|
70
|
-
const {
|
|
71
|
-
if (prevTarget ===
|
|
72
|
-
const newKey =
|
|
70
|
+
const {firstChild, name} = this;
|
|
71
|
+
if (prevTarget === firstChild) {
|
|
72
|
+
const newKey = firstChild.text().trim();
|
|
73
73
|
data.oldKey = name;
|
|
74
74
|
data.newKey = newKey;
|
|
75
75
|
this.setAttribute('name', newKey);
|
|
@@ -87,7 +87,7 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
87
87
|
*/
|
|
88
88
|
toString(selector) {
|
|
89
89
|
return this.anon && !(selector && this.matches(selector))
|
|
90
|
-
? this.
|
|
90
|
+
? this.lastChild.toString(selector)
|
|
91
91
|
: super.toString(selector, '=');
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -96,12 +96,17 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
96
96
|
return this.anon ? 0 : 1;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** @override */
|
|
100
|
+
print() {
|
|
101
|
+
return super.print({sep: this.anon ? '' : '='});
|
|
102
|
+
}
|
|
103
|
+
|
|
99
104
|
/**
|
|
100
105
|
* @override
|
|
101
106
|
* @returns {string}
|
|
102
107
|
*/
|
|
103
108
|
text() {
|
|
104
|
-
return this.anon ? this.
|
|
109
|
+
return this.anon ? this.lastChild.text() : super.text('=');
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
/**
|
|
@@ -114,37 +119,46 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
114
119
|
return this.replaceWith(token);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* 获取参数值
|
|
124
|
+
* @this {ParameterToken & {parentNode: TranscludeToken}}
|
|
125
|
+
*/
|
|
118
126
|
getValue() {
|
|
119
|
-
const
|
|
120
|
-
|
|
127
|
+
const TranscludeToken = require('./transclude');
|
|
128
|
+
const value = this.lastChild.text();
|
|
129
|
+
return this.anon && this.parentNode?.isTemplate() ? value : value.trim();
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
/**
|
|
124
133
|
* 设置参数值
|
|
134
|
+
* @this {ParameterToken & {parentNode: TranscludeToken}}
|
|
125
135
|
* @param {string} value 参数值
|
|
126
136
|
* @throws `SyntaxError` 非法的模板参数
|
|
127
137
|
*/
|
|
128
138
|
setValue(value) {
|
|
129
139
|
value = String(value);
|
|
130
|
-
const
|
|
140
|
+
const TranscludeToken = require('./transclude');
|
|
141
|
+
const templateLike = this.parentNode?.isTemplate(),
|
|
131
142
|
wikitext = `{{${templateLike ? ':T|' : 'lc:'}${this.anon ? '' : '1='}${value}}}`,
|
|
132
143
|
root = Parser.parse(wikitext, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
133
|
-
{childNodes: {length},
|
|
134
|
-
/** @type {ParameterToken} */
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
{childNodes: {length}, firstChild: transclude} = root,
|
|
145
|
+
/** @type {Token & {lastChild: ParameterToken}} */
|
|
146
|
+
{lastChild: parameter, type, name, childNodes: {length: transcludeLength}} = transclude,
|
|
147
|
+
targetType = templateLike ? 'template' : 'magic-word',
|
|
148
|
+
targetName = templateLike ? 'T' : 'lc';
|
|
149
|
+
if (length !== 1 || type !== targetType || name !== targetName || transcludeLength !== 2
|
|
150
|
+
|| parameter.anon !== this.anon || parameter.name !== '1'
|
|
138
151
|
) {
|
|
139
152
|
throw new SyntaxError(`非法的模板参数:${noWrap(value)}`);
|
|
140
153
|
}
|
|
141
|
-
const {lastChild} =
|
|
142
|
-
|
|
143
|
-
this.
|
|
154
|
+
const {lastChild} = parameter;
|
|
155
|
+
parameter.destroy(true);
|
|
156
|
+
this.lastChild.safeReplaceWith(lastChild);
|
|
144
157
|
}
|
|
145
158
|
|
|
146
159
|
/**
|
|
147
160
|
* 修改参数名
|
|
161
|
+
* @this {ParameterToken & {parentNode: TranscludeToken}}
|
|
148
162
|
* @param {string} key 新参数名
|
|
149
163
|
* @param {boolean} force 是否无视冲突命名
|
|
150
164
|
* @throws `Error` 仅用于模板参数
|
|
@@ -155,31 +169,30 @@ class ParameterToken extends fixedToken(Token) {
|
|
|
155
169
|
if (typeof key !== 'string') {
|
|
156
170
|
this.typeError('rename', 'String');
|
|
157
171
|
}
|
|
172
|
+
const TranscludeToken = require('./transclude');
|
|
158
173
|
const {parentNode} = this;
|
|
159
174
|
// 必须检测是否是TranscludeToken
|
|
160
|
-
if (!parentNode || !parentNode
|
|
161
|
-
|| !(parentNode instanceof require('./transclude'))
|
|
162
|
-
) {
|
|
175
|
+
if (!parentNode?.isTemplate() || !(parentNode instanceof require('./transclude'))) {
|
|
163
176
|
throw new Error(`${this.constructor.name}.rename 方法仅用于模板参数!`);
|
|
164
177
|
}
|
|
165
178
|
const root = Parser.parse(`{{:T|${key}=}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
166
|
-
{childNodes: {length},
|
|
167
|
-
|
|
179
|
+
{childNodes: {length}, firstChild: template} = root,
|
|
180
|
+
{type, name, lastChild: parameter, childNodes: {length: templateLength}} = template;
|
|
181
|
+
if (length !== 1 || type !== 'template' || name !== 'T' || templateLength !== 2) {
|
|
168
182
|
throw new SyntaxError(`非法的模板参数名:${key}`);
|
|
169
183
|
}
|
|
170
|
-
const {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} else if (parentNode.hasArg(name)) {
|
|
184
|
+
const {name: parameterName, firstChild} = parameter;
|
|
185
|
+
if (this.name === parameterName) {
|
|
186
|
+
Parser.warn('未改变实际参数名', parameterName);
|
|
187
|
+
} else if (parentNode.hasArg(parameterName)) {
|
|
175
188
|
if (force) {
|
|
176
|
-
Parser.warn('参数更名造成重复参数',
|
|
189
|
+
Parser.warn('参数更名造成重复参数', parameterName);
|
|
177
190
|
} else {
|
|
178
|
-
throw new RangeError(`参数更名造成重复参数:${
|
|
191
|
+
throw new RangeError(`参数更名造成重复参数:${parameterName}`);
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
|
-
|
|
182
|
-
this.
|
|
194
|
+
parameter.destroy(true);
|
|
195
|
+
this.firstChild.safeReplaceWith(firstChild);
|
|
183
196
|
}
|
|
184
197
|
}
|
|
185
198
|
|
package/src/table/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('assert/strict'),
|
|
4
4
|
{noWrap} = require('../../util/string'),
|
|
5
|
+
{generateForChild} = require('../../util/lint'),
|
|
5
6
|
Parser = require('../..'),
|
|
6
7
|
Token = require('..'),
|
|
7
8
|
TrToken = require('./tr'),
|
|
@@ -123,7 +124,7 @@ class TableToken extends TrToken {
|
|
|
123
124
|
|
|
124
125
|
/** 表格是否闭合 */
|
|
125
126
|
get closed() {
|
|
126
|
-
return this.
|
|
127
|
+
return this.lastChild.type === 'table-syntax';
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
set closed(closed) {
|
|
@@ -144,6 +145,18 @@ class TableToken extends TrToken {
|
|
|
144
145
|
});
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
/**
|
|
149
|
+
* @override
|
|
150
|
+
* @param {number} start 起始位置
|
|
151
|
+
*/
|
|
152
|
+
lint(start = 0) {
|
|
153
|
+
const errors = super.lint(start);
|
|
154
|
+
if (!this.closed) {
|
|
155
|
+
errors.push(generateForChild(this.firstChild, this.getRootNode().posFromIndex(start), '未闭合的表格'));
|
|
156
|
+
}
|
|
157
|
+
return errors;
|
|
158
|
+
}
|
|
159
|
+
|
|
147
160
|
/**
|
|
148
161
|
* @override
|
|
149
162
|
* @template {TrToken|SyntaxToken} T
|
|
@@ -154,7 +167,7 @@ class TableToken extends TrToken {
|
|
|
154
167
|
* @throws `SyntaxError` 表格的闭合部分非法
|
|
155
168
|
*/
|
|
156
169
|
insertAt(token, i = this.childNodes.length) {
|
|
157
|
-
const previous = this.
|
|
170
|
+
const previous = this.childNodes.at(i - 1);
|
|
158
171
|
if (token.type === 'td' && previous.type === 'tr') {
|
|
159
172
|
Parser.warn('改为将单元格插入当前行。');
|
|
160
173
|
return previous.appendChild(token);
|
|
@@ -177,11 +190,11 @@ class TableToken extends TrToken {
|
|
|
177
190
|
const config = this.getAttribute('config'),
|
|
178
191
|
accum = this.getAttribute('accum'),
|
|
179
192
|
inner = !halfParsed && Parser.parse(syntax, this.getAttribute('include'), 2, config),
|
|
180
|
-
{
|
|
193
|
+
{lastChild} = this;
|
|
181
194
|
if (!halfParsed && !closingPattern.test(inner.text())) {
|
|
182
195
|
throw new SyntaxError(`表格的闭合部分不符合语法!${noWrap(syntax)}`);
|
|
183
|
-
} else if (
|
|
184
|
-
|
|
196
|
+
} else if (lastChild instanceof SyntaxToken) {
|
|
197
|
+
lastChild.replaceChildren(...inner.childNodes);
|
|
185
198
|
} else {
|
|
186
199
|
this.appendChild(Parser.run(() => {
|
|
187
200
|
const token = new SyntaxToken(syntax, closingPattern, 'table-syntax', config, accum, {
|
|
@@ -202,7 +215,7 @@ class TableToken extends TrToken {
|
|
|
202
215
|
*/
|
|
203
216
|
getRowCount() {
|
|
204
217
|
return super.getRowCount()
|
|
205
|
-
+ this.
|
|
218
|
+
+ this.childNodes.filter(child => child.type === 'tr' && child.getRowCount()).length;
|
|
206
219
|
}
|
|
207
220
|
|
|
208
221
|
/** @override */
|
|
@@ -241,7 +254,7 @@ class TableToken extends TrToken {
|
|
|
241
254
|
} else if (isRow) {
|
|
242
255
|
n--;
|
|
243
256
|
}
|
|
244
|
-
for (const child of this.
|
|
257
|
+
for (const child of this.childNodes.slice(2)) {
|
|
245
258
|
if (child.type === 'tr' && child.getRowCount()) {
|
|
246
259
|
n--;
|
|
247
260
|
if (n < 0) {
|
|
@@ -262,7 +275,7 @@ class TableToken extends TrToken {
|
|
|
262
275
|
getAllRows() {
|
|
263
276
|
return [
|
|
264
277
|
...super.getRowCount() ? [this] : [],
|
|
265
|
-
...this.
|
|
278
|
+
...this.childNodes.filter(child => child.type === 'tr' && child.getRowCount()),
|
|
266
279
|
];
|
|
267
280
|
}
|
|
268
281
|
|
|
@@ -295,7 +308,7 @@ class TableToken extends TrToken {
|
|
|
295
308
|
let j = 0,
|
|
296
309
|
k = 0,
|
|
297
310
|
last;
|
|
298
|
-
for (const cell of rows[i].
|
|
311
|
+
for (const cell of rows[i].childNodes.slice(2)) {
|
|
299
312
|
if (cell instanceof TdToken) {
|
|
300
313
|
if (cell.isIndependent()) {
|
|
301
314
|
last = cell.subtype !== 'caption';
|
|
@@ -365,12 +378,12 @@ class TableToken extends TrToken {
|
|
|
365
378
|
coords = rowLayout?.[x];
|
|
366
379
|
if (coords) {
|
|
367
380
|
return {...coords, start: coords.row === y && rowLayout[x - 1] !== coords};
|
|
368
|
-
} else if (
|
|
369
|
-
return
|
|
381
|
+
} else if (rowLayout || y > 0) {
|
|
382
|
+
return x === rowLayout?.length
|
|
383
|
+
? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
|
|
384
|
+
: undefined;
|
|
370
385
|
}
|
|
371
|
-
return
|
|
372
|
-
? {row: y, column: (rowLayout.findLast(({row}) => row === y)?.column ?? -1) + 1, start: true}
|
|
373
|
-
: undefined;
|
|
386
|
+
return {row: 0, column: 0, start: true};
|
|
374
387
|
}
|
|
375
388
|
|
|
376
389
|
/**
|
|
@@ -474,7 +487,7 @@ class TableToken extends TrToken {
|
|
|
474
487
|
if (coords.column === undefined) {
|
|
475
488
|
const {x, y} = coords;
|
|
476
489
|
coords = this.toRawCoords(coords);
|
|
477
|
-
if (!coords?.start) {
|
|
490
|
+
if (!coords?.start) {
|
|
478
491
|
throw new RangeError(`指定的坐标不是单元格起始点:(${x}, ${y})`);
|
|
479
492
|
}
|
|
480
493
|
}
|
|
@@ -490,10 +503,10 @@ class TableToken extends TrToken {
|
|
|
490
503
|
*/
|
|
491
504
|
#prependTableRow() {
|
|
492
505
|
const row = Parser.run(() => new TrToken('\n|-', undefined, this.getAttribute('config'))),
|
|
493
|
-
{
|
|
494
|
-
[,, plain] =
|
|
506
|
+
{childNodes} = this,
|
|
507
|
+
[,, plain] = childNodes,
|
|
495
508
|
start = plain?.isPlain() ? 3 : 2,
|
|
496
|
-
/** @type {TdToken[]} */ tdChildren =
|
|
509
|
+
/** @type {TdToken[]} */ tdChildren = childNodes.slice(start),
|
|
497
510
|
index = tdChildren.findIndex(({type}) => type !== 'td');
|
|
498
511
|
this.insertAt(row, index === -1 ? -1 : index + start);
|
|
499
512
|
Parser.run(() => {
|
|
@@ -631,11 +644,11 @@ class TableToken extends TrToken {
|
|
|
631
644
|
*/
|
|
632
645
|
removeTableCol(x) {
|
|
633
646
|
for (const [token, start] of this.getFullCol(x)) {
|
|
634
|
-
const {colspan,
|
|
647
|
+
const {colspan, lastChild} = token;
|
|
635
648
|
if (colspan > 1) {
|
|
636
649
|
token.colspan = colspan - 1;
|
|
637
650
|
if (start) {
|
|
638
|
-
|
|
651
|
+
lastChild.replaceChildren();
|
|
639
652
|
}
|
|
640
653
|
} else {
|
|
641
654
|
token.remove();
|
|
@@ -703,7 +716,7 @@ class TableToken extends TrToken {
|
|
|
703
716
|
if (x !== undefined) {
|
|
704
717
|
coords = this.toRawCoords(coords);
|
|
705
718
|
}
|
|
706
|
-
if (coords.start === false || x === undefined) {
|
|
719
|
+
if (coords.start === false || x === undefined) {
|
|
707
720
|
({x, y} = this.toRenderedCoords(coords));
|
|
708
721
|
}
|
|
709
722
|
const splitting = {rowspan: 1, colspan: 1};
|
|
@@ -850,7 +863,7 @@ class TableToken extends TrToken {
|
|
|
850
863
|
const layout = this.getLayout(),
|
|
851
864
|
afterToken = this.getNthRow(after),
|
|
852
865
|
/** @type {TdToken[]} */
|
|
853
|
-
cells = afterToken.
|
|
866
|
+
cells = afterToken.childNodes.filter(child => child instanceof TdToken && child.subtype !== 'caption');
|
|
854
867
|
|
|
855
868
|
/**
|
|
856
869
|
* @type {(i: number, oneRow?: boolean) => number[]}
|
|
@@ -926,7 +939,7 @@ class TableToken extends TrToken {
|
|
|
926
939
|
if (start) {
|
|
927
940
|
const original = token;
|
|
928
941
|
token = token.cloneNode();
|
|
929
|
-
original.
|
|
942
|
+
original.lastChild.replaceChildren();
|
|
930
943
|
token.colspan = 1;
|
|
931
944
|
}
|
|
932
945
|
}
|
|
@@ -934,7 +947,7 @@ class TableToken extends TrToken {
|
|
|
934
947
|
const col = rowLayout.slice(reference + Number(after)).find(({row}) => row === i)?.column;
|
|
935
948
|
rowToken.insertBefore(
|
|
936
949
|
token, col === undefined && rowToken.type === 'table'
|
|
937
|
-
? rowToken.
|
|
950
|
+
? rowToken.childNodes.slice(2).find(isRowEnd)
|
|
938
951
|
: col !== undefined && rowToken.getNthCol(col),
|
|
939
952
|
);
|
|
940
953
|
}
|