wikiparser-node 0.8.0 → 0.8.1-m
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/config/minimum.json +142 -0
- package/index.js +11 -253
- package/lib/element.js +7 -481
- package/lib/node.js +6 -552
- package/lib/text.js +70 -110
- package/lib/title.js +0 -21
- package/mixin/hidden.js +0 -3
- package/package.json +5 -5
- package/parser/brackets.js +0 -1
- package/parser/commentAndExt.js +3 -4
- package/parser/converter.js +0 -1
- package/parser/externalLinks.js +0 -1
- package/parser/hrAndDoubleUnderscore.js +0 -1
- package/parser/html.js +0 -1
- package/parser/links.js +4 -5
- package/parser/list.js +0 -1
- package/parser/magicLinks.js +4 -5
- package/parser/quotes.js +1 -2
- package/parser/table.js +0 -1
- package/src/arg.js +2 -116
- package/src/atom/hidden.js +0 -2
- package/src/atom/index.js +0 -17
- package/src/attribute.js +15 -182
- package/src/attributes.js +4 -308
- package/src/converter.js +2 -108
- package/src/converterFlags.js +0 -187
- package/src/converterRule.js +1 -184
- package/src/extLink.js +1 -120
- package/src/gallery.js +6 -57
- package/src/hasNowiki/index.js +0 -12
- package/src/hasNowiki/pre.js +0 -12
- package/src/heading.js +4 -55
- package/src/html.js +3 -118
- package/src/imageParameter.js +19 -189
- package/src/imagemap.js +1 -60
- package/src/imagemapLink.js +1 -13
- package/src/index.js +3 -529
- package/src/link/category.js +1 -37
- package/src/link/file.js +2 -159
- package/src/link/galleryImage.js +1 -59
- package/src/link/index.js +1 -259
- package/src/magicLink.js +9 -90
- package/src/nested/choose.js +0 -1
- package/src/nested/combobox.js +0 -1
- package/src/nested/index.js +3 -30
- package/src/nested/references.js +0 -1
- package/src/nowiki/comment.js +1 -25
- package/src/nowiki/dd.js +1 -47
- package/src/nowiki/doubleUnderscore.js +1 -31
- package/src/nowiki/hr.js +1 -20
- package/src/nowiki/index.js +1 -23
- package/src/nowiki/list.js +2 -5
- package/src/nowiki/noinclude.js +0 -14
- package/src/nowiki/quote.js +2 -16
- package/src/onlyinclude.js +1 -26
- package/src/paramTag/index.js +1 -24
- package/src/paramTag/inputbox.js +1 -4
- package/src/parameter.js +6 -148
- package/src/syntax.js +0 -68
- package/src/table/index.js +2 -940
- package/src/table/td.js +5 -225
- package/src/table/tr.js +2 -247
- package/src/tagPair/ext.js +3 -24
- package/src/tagPair/include.js +0 -24
- package/src/tagPair/index.js +2 -51
- package/src/transclude.js +20 -519
- package/util/string.js +1 -48
- package/README.md +0 -39
- package/lib/ranges.js +0 -130
- package/mixin/attributeParent.js +0 -117
- package/mixin/fixedToken.js +0 -40
- package/mixin/singleLine.js +0 -31
- package/mixin/sol.js +0 -65
- package/parser/selector.js +0 -177
- package/src/charinsert.js +0 -97
- package/tool/index.js +0 -1202
- package/util/debug.js +0 -73
package/lib/element.js
CHANGED
|
@@ -2,437 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs'),
|
|
4
4
|
path = require('path'),
|
|
5
|
-
{toCase, noWrap, print} = require('../util/string'),
|
|
6
|
-
{nth} = require('./ranges'),
|
|
7
|
-
parseSelector = require('../parser/selector'),
|
|
8
|
-
Parser = require('..'),
|
|
9
5
|
AstNode = require('./node'),
|
|
10
6
|
AstText = require('./text');
|
|
11
7
|
|
|
12
|
-
/**
|
|
13
|
-
* 检测:lang()伪选择器
|
|
14
|
-
* @param {AstElement & {attributes: Records<string, string|true>}} node 节点
|
|
15
|
-
* @param {RegExp} regex 语言正则
|
|
16
|
-
*/
|
|
17
|
-
const matchesLang = ({attributes}, regex) => {
|
|
18
|
-
const /** @type {string} */ lang = attributes?.lang;
|
|
19
|
-
return typeof lang === 'string' && regex.test(lang);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
8
|
/** 类似HTMLElement */
|
|
23
9
|
class AstElement extends AstNode {
|
|
24
10
|
/** @type {string} */ name;
|
|
25
11
|
|
|
26
|
-
/**
|
|
27
|
-
* 检查是否符合某条属性规则
|
|
28
|
-
* @param {string} key 属性键
|
|
29
|
-
* @param {string|undefined} equal 属性规则运算符,`equal`存在时`val`和`i`也一定存在
|
|
30
|
-
* @param {string|undefined} val 属性值
|
|
31
|
-
* @param {string|undefined} i 是否对大小写不敏感
|
|
32
|
-
* @throws `RangeError` 复杂属性
|
|
33
|
-
*/
|
|
34
|
-
#matchesAttr = (key, equal, val, i) => {
|
|
35
|
-
if (!equal) {
|
|
36
|
-
return this.hasAttribute(key);
|
|
37
|
-
} else if (!this.hasAttribute(key)) {
|
|
38
|
-
return equal === '!=';
|
|
39
|
-
}
|
|
40
|
-
val = toCase(val, i);
|
|
41
|
-
let thisVal = this.getAttribute(key);
|
|
42
|
-
if (thisVal instanceof RegExp) {
|
|
43
|
-
thisVal = thisVal.source;
|
|
44
|
-
}
|
|
45
|
-
if (equal === '~=') {
|
|
46
|
-
const thisVals = typeof thisVal === 'string' ? thisVal.split(/\s/u) : thisVal;
|
|
47
|
-
return Boolean(thisVals?.[Symbol.iterator]) && [...thisVals].some(v => toCase(v, i) === val);
|
|
48
|
-
} else if (typeof thisVal !== 'string') {
|
|
49
|
-
throw new RangeError(`复杂属性 ${key} 不能用于选择器!`);
|
|
50
|
-
}
|
|
51
|
-
thisVal = toCase(thisVal, i);
|
|
52
|
-
switch (equal) {
|
|
53
|
-
case '|=':
|
|
54
|
-
return thisVal === val || thisVal.startsWith(`${val}-`);
|
|
55
|
-
case '^=':
|
|
56
|
-
return thisVal.startsWith(val);
|
|
57
|
-
case '$=':
|
|
58
|
-
return thisVal.endsWith(val);
|
|
59
|
-
case '*=':
|
|
60
|
-
return thisVal.includes(val);
|
|
61
|
-
case '!=':
|
|
62
|
-
return thisVal !== val;
|
|
63
|
-
default: // `=`
|
|
64
|
-
return thisVal === val;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
12
|
/** 子节点总数 */
|
|
69
13
|
get length() {
|
|
70
14
|
return this.childNodes.length;
|
|
71
15
|
}
|
|
72
16
|
|
|
73
|
-
/**
|
|
74
|
-
* 全部非文本子节点
|
|
75
|
-
* @complexity `n`
|
|
76
|
-
*/
|
|
77
|
-
get children() {
|
|
78
|
-
const /** @type {this[]} */ children = this.childNodes.filter(({type}) => type !== 'text');
|
|
79
|
-
return children;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* 首位非文本子节点
|
|
84
|
-
* @returns {this}
|
|
85
|
-
*/
|
|
86
|
-
get firstElementChild() {
|
|
87
|
-
return this.childNodes.find(({type}) => type !== 'text');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* 末位非文本子节点
|
|
92
|
-
* @complexity `n`
|
|
93
|
-
*/
|
|
94
|
-
get lastElementChild() {
|
|
95
|
-
return this.children.at(-1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 非文本子节点总数
|
|
100
|
-
* @complexity `n`
|
|
101
|
-
*/
|
|
102
|
-
get childElementCount() {
|
|
103
|
-
return this.children.length;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** 父节点 */
|
|
107
|
-
get parentElement() {
|
|
108
|
-
return this.parentNode;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* AstElement.prototype.text()的getter写法
|
|
113
|
-
* @complexity `n`
|
|
114
|
-
*/
|
|
115
|
-
get outerText() {
|
|
116
|
-
return this.text();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 不可见
|
|
121
|
-
*/
|
|
122
|
-
get hidden() {
|
|
123
|
-
return this.text() === '';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 后一个可见的兄弟节点
|
|
128
|
-
* @complexity `n`
|
|
129
|
-
*/
|
|
130
|
-
get nextVisibleSibling() {
|
|
131
|
-
let {nextSibling} = this;
|
|
132
|
-
while (nextSibling?.text() === '') {
|
|
133
|
-
({nextSibling} = nextSibling);
|
|
134
|
-
}
|
|
135
|
-
return nextSibling;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 前一个可见的兄弟节点
|
|
140
|
-
* @complexity `n`
|
|
141
|
-
*/
|
|
142
|
-
get previousVisibleSibling() {
|
|
143
|
-
let {previousSibling} = this;
|
|
144
|
-
while (previousSibling?.text() === '') {
|
|
145
|
-
({previousSibling} = previousSibling);
|
|
146
|
-
}
|
|
147
|
-
return previousSibling;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** 内部高度 */
|
|
151
|
-
get clientHeight() {
|
|
152
|
-
const {innerText} = this;
|
|
153
|
-
return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** 内部宽度 */
|
|
157
|
-
get clientWidth() {
|
|
158
|
-
const {innerText} = this;
|
|
159
|
-
return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
constructor() {
|
|
163
|
-
super();
|
|
164
|
-
this.seal('name');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 销毁
|
|
169
|
-
* @complexity `n`
|
|
170
|
-
* @param {boolean} deep 是否从根节点开始销毁
|
|
171
|
-
* @throws `Error` 不能销毁子节点
|
|
172
|
-
*/
|
|
173
|
-
destroy(deep) {
|
|
174
|
-
if (this.parentNode && !deep) {
|
|
175
|
-
throw new Error('不能销毁子节点!');
|
|
176
|
-
}
|
|
177
|
-
this.parentNode?.destroy(deep);
|
|
178
|
-
for (const child of this.childNodes) {
|
|
179
|
-
child.setAttribute('parentNode');
|
|
180
|
-
}
|
|
181
|
-
Object.setPrototypeOf(this, null);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* @override
|
|
186
|
-
* @template {string} T
|
|
187
|
-
* @param {T} key 属性键
|
|
188
|
-
* @returns {TokenAttribute<T>}
|
|
189
|
-
*/
|
|
190
|
-
getAttribute(key) {
|
|
191
|
-
return key === 'matchesAttr' ? this.#matchesAttr : super.getAttribute(key);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
|
|
195
|
-
#isProtected() {
|
|
196
|
-
const /** @type {{parentNode: AstElement & {constructor: {fixed: boolean}}}} */ {parentNode} = this;
|
|
197
|
-
if (!parentNode) {
|
|
198
|
-
return undefined;
|
|
199
|
-
}
|
|
200
|
-
const {childNodes, constructor: {fixed}} = parentNode,
|
|
201
|
-
protectedIndices = parentNode.getAttribute('protectedChildren')?.applyTo(childNodes);
|
|
202
|
-
return fixed || protectedIndices?.includes(childNodes.indexOf(this));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* 检查是否符合解析后的选择器,不含节点关系
|
|
207
|
-
* @this {AstElement & {link: string, constructor: {fixed: boolean}}}
|
|
208
|
-
* @param {SelectorArray} step 解析后的选择器
|
|
209
|
-
* @throws `SyntaxError` 未定义的伪选择器
|
|
210
|
-
*/
|
|
211
|
-
#matches(step) {
|
|
212
|
-
const {parentNode, type, name, childNodes, link, constructor: {fixed, name: tokenName}} = this,
|
|
213
|
-
children = parentNode?.children,
|
|
214
|
-
childrenOfType = children?.filter(({type: t}) => t === type),
|
|
215
|
-
siblingsCount = children?.length ?? 1,
|
|
216
|
-
siblingsCountOfType = childrenOfType?.length ?? 1,
|
|
217
|
-
index = (children?.indexOf(this) ?? 0) + 1,
|
|
218
|
-
indexOfType = (childrenOfType?.indexOf(this) ?? 0) + 1,
|
|
219
|
-
lastIndex = siblingsCount - index + 1,
|
|
220
|
-
lastIndexOfType = siblingsCountOfType - indexOfType + 1;
|
|
221
|
-
return step.every(selector => {
|
|
222
|
-
if (typeof selector === 'string') {
|
|
223
|
-
switch (selector) { // 情形1:简单伪选择器、type和name
|
|
224
|
-
case '*':
|
|
225
|
-
return true;
|
|
226
|
-
case ':root':
|
|
227
|
-
return !parentNode;
|
|
228
|
-
case ':first-child':
|
|
229
|
-
return index === 1;
|
|
230
|
-
case ':first-of-type':
|
|
231
|
-
return indexOfType === 1;
|
|
232
|
-
case ':last-child':
|
|
233
|
-
return lastIndex === 1;
|
|
234
|
-
case ':last-of-type':
|
|
235
|
-
return lastIndexOfType === 1;
|
|
236
|
-
case ':only-child':
|
|
237
|
-
return siblingsCount === 1;
|
|
238
|
-
case ':only-of-type':
|
|
239
|
-
return siblingsCountOfType === 1;
|
|
240
|
-
case ':empty':
|
|
241
|
-
return !childNodes.some(child => child instanceof AstElement || String(child));
|
|
242
|
-
case ':parent':
|
|
243
|
-
return childNodes.some(child => child instanceof AstElement || String(child));
|
|
244
|
-
case ':header':
|
|
245
|
-
return type === 'heading';
|
|
246
|
-
case ':hidden':
|
|
247
|
-
return this.text() === '';
|
|
248
|
-
case ':visible':
|
|
249
|
-
return this.text() !== '';
|
|
250
|
-
case ':only-whitespace':
|
|
251
|
-
return this.text().trim() === '';
|
|
252
|
-
case ':any-link':
|
|
253
|
-
return type === 'link' || type === 'free-ext-link' || type === 'ext-link'
|
|
254
|
-
|| (type === 'file' || type === 'gallery-image' && link);
|
|
255
|
-
case ':local-link':
|
|
256
|
-
return (type === 'link' || type === 'file' || type === 'gallery-image')
|
|
257
|
-
&& link?.[0] === '#';
|
|
258
|
-
case ':read-only':
|
|
259
|
-
return fixed;
|
|
260
|
-
case ':read-write':
|
|
261
|
-
return !fixed;
|
|
262
|
-
case ':invalid':
|
|
263
|
-
return type === 'table-inter' || tokenName === 'HiddenToken';
|
|
264
|
-
case ':required':
|
|
265
|
-
return this.#isProtected() === true;
|
|
266
|
-
case ':optional':
|
|
267
|
-
return this.#isProtected() === false;
|
|
268
|
-
default: {
|
|
269
|
-
const [t, n] = selector.split('#');
|
|
270
|
-
return (!t || t === type || Boolean(Parser.typeAliases[type]?.includes(t)))
|
|
271
|
-
&& (!n || n === name);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
} else if (selector.length === 4) { // 情形2:属性选择器
|
|
275
|
-
return this.getAttribute('matchesAttr')(...selector);
|
|
276
|
-
}
|
|
277
|
-
const [s, pseudo] = selector; // 情形3:复杂伪选择器
|
|
278
|
-
switch (pseudo) {
|
|
279
|
-
case 'is':
|
|
280
|
-
return this.matches(s);
|
|
281
|
-
case 'not':
|
|
282
|
-
return !this.matches(s);
|
|
283
|
-
case 'nth-child':
|
|
284
|
-
return nth(s, index);
|
|
285
|
-
case 'nth-of-type':
|
|
286
|
-
return nth(s, indexOfType);
|
|
287
|
-
case 'nth-last-child':
|
|
288
|
-
return nth(s, lastIndex);
|
|
289
|
-
case 'nth-last-of-type':
|
|
290
|
-
return nth(s, lastIndexOfType);
|
|
291
|
-
case 'contains':
|
|
292
|
-
return this.text().includes(s);
|
|
293
|
-
case 'has':
|
|
294
|
-
return Boolean(this.querySelector(s));
|
|
295
|
-
case 'lang': {
|
|
296
|
-
const regex = new RegExp(`^${s}(?:-|$)`, 'u');
|
|
297
|
-
return matchesLang(this, regex)
|
|
298
|
-
|| this.getAncestors().some(ancestor => matchesLang(ancestor, regex));
|
|
299
|
-
}
|
|
300
|
-
default:
|
|
301
|
-
throw new SyntaxError(`未定义的伪选择器!${pseudo}`);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 检查是否符合选择器
|
|
308
|
-
* @param {string|SelectorArray[]} selector
|
|
309
|
-
* @returns {boolean}
|
|
310
|
-
* @complexity `n`
|
|
311
|
-
*/
|
|
312
|
-
matches(selector) {
|
|
313
|
-
if (selector === undefined) {
|
|
314
|
-
return true;
|
|
315
|
-
} else if (typeof selector === 'string') {
|
|
316
|
-
const stack = parseSelector(selector),
|
|
317
|
-
/** @type {Set<string>} */
|
|
318
|
-
pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step[0] === ':'));
|
|
319
|
-
if (pseudos.size > 0) {
|
|
320
|
-
Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
|
|
321
|
-
}
|
|
322
|
-
return Parser.run(() => stack.some(condition => this.matches(condition)));
|
|
323
|
-
} else if (!Parser.running) {
|
|
324
|
-
this.typeError('matches', 'String');
|
|
325
|
-
}
|
|
326
|
-
selector = [...selector];
|
|
327
|
-
const step = selector.pop();
|
|
328
|
-
if (this.#matches(step)) {
|
|
329
|
-
const {parentNode, previousElementSibling} = this;
|
|
330
|
-
switch (selector.at(-1)?.relation) {
|
|
331
|
-
case undefined:
|
|
332
|
-
return true;
|
|
333
|
-
case '>':
|
|
334
|
-
return parentNode?.matches(selector);
|
|
335
|
-
case '+':
|
|
336
|
-
return previousElementSibling?.matches(selector);
|
|
337
|
-
case '~': {
|
|
338
|
-
if (!parentNode) {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
const {children} = parentNode,
|
|
342
|
-
i = children.indexOf(this);
|
|
343
|
-
return children.slice(0, i).some(child => child.matches(selector));
|
|
344
|
-
}
|
|
345
|
-
default: // ' '
|
|
346
|
-
return this.getAncestors().some(ancestor => ancestor.matches(selector));
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* 符合选择器的第一个后代节点
|
|
354
|
-
* @param {string} selector
|
|
355
|
-
* @returns {this|undefined}
|
|
356
|
-
* @complexity `n`
|
|
357
|
-
*/
|
|
358
|
-
querySelector(selector) {
|
|
359
|
-
for (const child of this.children) {
|
|
360
|
-
if (child.matches(selector)) {
|
|
361
|
-
return child;
|
|
362
|
-
}
|
|
363
|
-
const descendant = child.querySelector(selector);
|
|
364
|
-
if (descendant) {
|
|
365
|
-
return descendant;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* 符合选择器的所有后代节点
|
|
373
|
-
* @param {string} selector
|
|
374
|
-
* @complexity `n`
|
|
375
|
-
*/
|
|
376
|
-
querySelectorAll(selector) {
|
|
377
|
-
const /** @type {this[]} */ descendants = [];
|
|
378
|
-
for (const child of this.children) {
|
|
379
|
-
if (child.matches(selector)) {
|
|
380
|
-
descendants.push(child);
|
|
381
|
-
}
|
|
382
|
-
descendants.push(...child.querySelectorAll(selector));
|
|
383
|
-
}
|
|
384
|
-
return descendants;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* 类选择器
|
|
389
|
-
* @param {string} className 类名之一
|
|
390
|
-
*/
|
|
391
|
-
getElementsByClassName(className) {
|
|
392
|
-
return typeof className === 'string'
|
|
393
|
-
? this.querySelectorAll(`[className~="${className.replace(/(?<!\\)"/gu, '\\"')}"]`)
|
|
394
|
-
: this.typeError('getElementsByClassName', 'String');
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* 标签名选择器
|
|
399
|
-
* @param {string} name 标签名
|
|
400
|
-
*/
|
|
401
|
-
getElementsByTagName(name) {
|
|
402
|
-
if (typeof name === 'string') {
|
|
403
|
-
name = name.replace(/(?<!\\)"/gu, '\\"');
|
|
404
|
-
return this.querySelectorAll(`ext[name="${name}"], html[name="${name}"]`);
|
|
405
|
-
}
|
|
406
|
-
return this.typeError('getElementsByTagName', 'String');
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* 获取某一行的wikitext
|
|
411
|
-
* @param {number} n 行号
|
|
412
|
-
*/
|
|
413
|
-
getLine(n) {
|
|
414
|
-
return String(this).split('\n', n + 1).at(-1);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* 在开头批量插入子节点
|
|
419
|
-
* @param {...this} elements 插入节点
|
|
420
|
-
* @complexity `n`
|
|
421
|
-
*/
|
|
422
|
-
prepend(...elements) {
|
|
423
|
-
for (let i = 0; i < elements.length; i++) {
|
|
424
|
-
this.insertAt(elements[i], i);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
17
|
/**
|
|
429
18
|
* 最近的祖先节点
|
|
430
19
|
* @param {string} selector
|
|
431
20
|
*/
|
|
432
21
|
closest(selector) {
|
|
22
|
+
const types = new Set(selector.split(',').map(type => type.trim()));
|
|
433
23
|
let {parentNode} = this;
|
|
434
24
|
while (parentNode) {
|
|
435
|
-
if (parentNode.
|
|
25
|
+
if (types.has(parentNode.type)) {
|
|
436
26
|
return parentNode;
|
|
437
27
|
}
|
|
438
28
|
({parentNode} = parentNode);
|
|
@@ -457,9 +47,7 @@ class AstElement extends AstNode {
|
|
|
457
47
|
* @complexity `n`
|
|
458
48
|
*/
|
|
459
49
|
replaceChildren(...elements) {
|
|
460
|
-
|
|
461
|
-
this.removeAt(i);
|
|
462
|
-
}
|
|
50
|
+
this.childNodes.length = 0;
|
|
463
51
|
this.append(...elements);
|
|
464
52
|
}
|
|
465
53
|
|
|
@@ -467,29 +55,24 @@ class AstElement extends AstNode {
|
|
|
467
55
|
* 修改文本子节点
|
|
468
56
|
* @param {string} str 新文本
|
|
469
57
|
* @param {number} i 子节点位置
|
|
470
|
-
* @throws `RangeError` 对应位置的子节点不是文本节点
|
|
471
58
|
*/
|
|
472
59
|
setText(str, i = 0) {
|
|
473
|
-
this.
|
|
474
|
-
|
|
475
|
-
{type, data, constructor: {name}} = oldText;
|
|
60
|
+
const /** @type {AstText} */ oldText = this.childNodes[i],
|
|
61
|
+
{type, data} = oldText;
|
|
476
62
|
if (type === 'text') {
|
|
477
63
|
oldText.replaceData(str);
|
|
478
64
|
return data;
|
|
479
65
|
}
|
|
480
|
-
|
|
66
|
+
return undefined;
|
|
481
67
|
}
|
|
482
68
|
|
|
483
69
|
/**
|
|
484
70
|
* 还原为wikitext
|
|
485
|
-
* @param {string} selector
|
|
486
71
|
* @param {string} separator 子节点间的连接符
|
|
487
72
|
* @returns {string}
|
|
488
73
|
*/
|
|
489
74
|
toString(selector, separator = '') {
|
|
490
|
-
return
|
|
491
|
-
? ''
|
|
492
|
-
: this.childNodes.map(child => child.toString(selector)).join(separator);
|
|
75
|
+
return this.childNodes.map(child => child.toString()).join(separator);
|
|
493
76
|
}
|
|
494
77
|
|
|
495
78
|
static lintIgnoredExt = new Set([
|
|
@@ -530,17 +113,6 @@ class AstElement extends AstNode {
|
|
|
530
113
|
return errors;
|
|
531
114
|
}
|
|
532
115
|
|
|
533
|
-
/**
|
|
534
|
-
* 以HTML格式打印
|
|
535
|
-
* @param {printOpt} opt 选项
|
|
536
|
-
* @returns {string}
|
|
537
|
-
*/
|
|
538
|
-
print(opt = {}) {
|
|
539
|
-
return String(this)
|
|
540
|
-
? `<span class="wpb-${opt.class || this.type}">${print(this.childNodes, opt)}</span>`
|
|
541
|
-
: '';
|
|
542
|
-
}
|
|
543
|
-
|
|
544
116
|
/**
|
|
545
117
|
* 保存为JSON
|
|
546
118
|
* @param {string} file 文件名
|
|
@@ -560,52 +132,6 @@ class AstElement extends AstNode {
|
|
|
560
132
|
}
|
|
561
133
|
return json;
|
|
562
134
|
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* 输出AST
|
|
566
|
-
* @param {number} depth 当前深度
|
|
567
|
-
* @returns {void}
|
|
568
|
-
*/
|
|
569
|
-
echo(depth = 0) {
|
|
570
|
-
if (!Number.isInteger(depth) || depth < 0) {
|
|
571
|
-
this.typeError('print', 'Number');
|
|
572
|
-
}
|
|
573
|
-
const indent = ' '.repeat(depth),
|
|
574
|
-
str = String(this),
|
|
575
|
-
{childNodes, type, length} = this;
|
|
576
|
-
if (childNodes.every(child => child.type === 'text' || !String(child))) {
|
|
577
|
-
console.log(`${indent}\x1B[32m<%s>\x1B[0m${noWrap(str)}\x1B[32m</%s>\x1B[0m`, type, type);
|
|
578
|
-
return undefined;
|
|
579
|
-
}
|
|
580
|
-
Parser.info(`${indent}<${type}>`);
|
|
581
|
-
let i = this.getPadding();
|
|
582
|
-
if (i) {
|
|
583
|
-
console.log(`${indent} ${noWrap(str.slice(0, i))}`);
|
|
584
|
-
}
|
|
585
|
-
for (let j = 0; j < length; j++) {
|
|
586
|
-
const child = childNodes[j],
|
|
587
|
-
childStr = String(child),
|
|
588
|
-
gap = j === length - 1 ? 0 : this.getGaps(j);
|
|
589
|
-
if (!childStr) {
|
|
590
|
-
// pass
|
|
591
|
-
} else if (child.type === 'text') {
|
|
592
|
-
console.log(`${indent} ${noWrap(String(child))}`);
|
|
593
|
-
} else {
|
|
594
|
-
child.echo(depth + 1);
|
|
595
|
-
}
|
|
596
|
-
i += childStr.length;
|
|
597
|
-
if (gap) {
|
|
598
|
-
console.log(`${indent} ${noWrap(str.slice(i, i + gap))}`);
|
|
599
|
-
i += gap;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
if (i < str.length) {
|
|
603
|
-
console.log(`${indent} ${noWrap(str.slice(i))}`);
|
|
604
|
-
}
|
|
605
|
-
Parser.info(`${indent}</${type}>`);
|
|
606
|
-
return undefined;
|
|
607
|
-
}
|
|
608
135
|
}
|
|
609
136
|
|
|
610
|
-
Parser.classes.AstElement = __filename;
|
|
611
137
|
module.exports = AstElement;
|