wikiparser-node 1.4.2-b → 1.4.2
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/.schema.json +172 -0
- package/config/enwiki.json +814 -1
- package/config/llwiki.json +35 -1
- package/config/moegirl.json +44 -1
- package/config/zhwiki.json +466 -1
- package/dist/addon/table.d.ts +6 -0
- package/dist/addon/table.js +560 -0
- package/dist/base.d.ts +53 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +211 -0
- package/dist/internal.d.ts +44 -0
- package/dist/lib/element.d.ts +154 -0
- package/dist/lib/element.js +643 -0
- package/dist/lib/node.d.ts +146 -0
- package/dist/lib/node.js +425 -0
- package/dist/lib/range.d.ts +104 -0
- package/dist/lib/range.js +385 -0
- package/dist/lib/ranges.d.ts +26 -0
- package/dist/lib/ranges.js +118 -0
- package/dist/lib/text.d.ts +62 -0
- package/dist/lib/text.js +306 -0
- package/dist/lib/title.d.ts +38 -0
- package/dist/lib/title.js +172 -0
- package/dist/mixin/attributesParent.d.ts +20 -0
- package/dist/mixin/attributesParent.js +89 -0
- package/dist/mixin/fixed.d.ts +5 -0
- package/dist/mixin/fixed.js +28 -0
- package/dist/mixin/flagsParent.d.ts +61 -0
- package/dist/mixin/flagsParent.js +72 -0
- package/dist/mixin/hidden.d.ts +5 -0
- package/dist/mixin/hidden.js +24 -0
- package/dist/mixin/magicLinkParent.d.ts +34 -0
- package/dist/mixin/magicLinkParent.js +43 -0
- package/dist/mixin/singleLine.d.ts +5 -0
- package/dist/mixin/singleLine.js +25 -0
- package/dist/mixin/sol.d.ts +8 -0
- package/dist/mixin/sol.js +41 -0
- package/dist/mixin/syntax.d.ts +6 -0
- package/dist/mixin/syntax.js +54 -0
- package/dist/parser/braces.js +131 -0
- package/dist/parser/commentAndExt.js +90 -0
- package/dist/parser/converter.js +41 -0
- package/dist/parser/externalLinks.js +32 -0
- package/dist/parser/hrAndDoubleUnderscore.js +41 -0
- package/dist/parser/html.js +39 -0
- package/dist/parser/links.js +97 -0
- package/dist/parser/list.js +68 -0
- package/dist/parser/magicLinks.js +43 -0
- package/dist/parser/quotes.js +68 -0
- package/dist/parser/selector.js +162 -0
- package/dist/parser/table.js +124 -0
- package/dist/src/arg.d.ts +58 -0
- package/dist/src/arg.js +196 -0
- package/dist/src/atom.d.ts +12 -0
- package/dist/src/atom.js +27 -0
- package/dist/src/attribute.d.ts +67 -0
- package/dist/src/attribute.js +422 -0
- package/dist/src/attributes.d.ts +109 -0
- package/dist/src/attributes.js +347 -0
- package/dist/src/converter.d.ts +28 -0
- package/dist/src/converter.js +85 -0
- package/dist/src/converterFlags.d.ts +87 -0
- package/dist/src/converterFlags.js +227 -0
- package/dist/src/converterRule.d.ts +77 -0
- package/dist/src/converterRule.js +210 -0
- package/dist/src/extLink.d.ts +38 -0
- package/dist/src/extLink.js +137 -0
- package/dist/src/gallery.d.ts +54 -0
- package/dist/src/gallery.js +127 -0
- package/dist/src/heading.d.ts +47 -0
- package/dist/src/heading.js +143 -0
- package/dist/src/hidden.d.ts +7 -0
- package/dist/src/hidden.js +23 -0
- package/dist/src/html.d.ts +62 -0
- package/dist/src/html.js +286 -0
- package/dist/src/imageParameter.d.ts +65 -0
- package/dist/src/imageParameter.js +247 -0
- package/dist/src/imagemap.d.ts +56 -0
- package/dist/src/imagemap.js +156 -0
- package/dist/src/imagemapLink.d.ts +28 -0
- package/dist/src/imagemapLink.js +44 -0
- package/dist/src/index.d.ts +143 -0
- package/dist/src/index.js +804 -0
- package/dist/src/link/base.d.ts +52 -0
- package/dist/src/link/base.js +212 -0
- package/dist/src/link/category.d.ts +13 -0
- package/dist/src/link/category.js +28 -0
- package/dist/src/link/file.d.ts +96 -0
- package/dist/src/link/file.js +266 -0
- package/dist/src/link/galleryImage.d.ts +31 -0
- package/dist/src/link/galleryImage.js +109 -0
- package/dist/src/link/index.d.ts +56 -0
- package/dist/src/link/index.js +134 -0
- package/dist/src/magicLink.d.ts +51 -0
- package/dist/src/magicLink.js +149 -0
- package/dist/src/nested.d.ts +43 -0
- package/dist/src/nested.js +91 -0
- package/dist/src/nowiki/base.d.ts +27 -0
- package/dist/src/nowiki/base.js +42 -0
- package/dist/src/nowiki/comment.d.ts +27 -0
- package/dist/src/nowiki/comment.js +77 -0
- package/dist/src/nowiki/dd.d.ts +8 -0
- package/dist/src/nowiki/dd.js +24 -0
- package/dist/src/nowiki/doubleUnderscore.d.ts +18 -0
- package/dist/src/nowiki/doubleUnderscore.js +51 -0
- package/dist/src/nowiki/hr.d.ts +8 -0
- package/dist/src/nowiki/hr.js +14 -0
- package/dist/src/nowiki/index.d.ts +16 -0
- package/dist/src/nowiki/index.js +20 -0
- package/dist/src/nowiki/list.d.ts +19 -0
- package/dist/src/nowiki/list.js +48 -0
- package/dist/src/nowiki/listBase.d.ts +5 -0
- package/dist/src/nowiki/listBase.js +11 -0
- package/dist/src/nowiki/noinclude.d.ts +10 -0
- package/dist/src/nowiki/noinclude.js +23 -0
- package/dist/src/nowiki/quote.d.ts +14 -0
- package/dist/src/nowiki/quote.js +68 -0
- package/dist/src/onlyinclude.d.ts +16 -0
- package/dist/src/onlyinclude.js +57 -0
- package/dist/src/paramTag/index.d.ts +37 -0
- package/dist/src/paramTag/index.js +68 -0
- package/dist/src/paramTag/inputbox.d.ts +8 -0
- package/dist/src/paramTag/inputbox.js +22 -0
- package/dist/src/parameter.d.ts +67 -0
- package/dist/src/parameter.js +213 -0
- package/dist/src/pre.d.ts +28 -0
- package/dist/src/pre.js +54 -0
- package/dist/src/syntax.d.ts +14 -0
- package/dist/src/syntax.js +35 -0
- package/dist/src/table/base.d.ts +27 -0
- package/dist/src/table/base.js +81 -0
- package/dist/src/table/index.d.ts +230 -0
- package/dist/src/table/index.js +390 -0
- package/dist/src/table/td.d.ts +88 -0
- package/dist/src/table/td.js +284 -0
- package/dist/src/table/tr.d.ts +32 -0
- package/dist/src/table/tr.js +57 -0
- package/dist/src/table/trBase.d.ts +53 -0
- package/dist/src/table/trBase.js +154 -0
- package/dist/src/tagPair/ext.d.ts +29 -0
- package/dist/src/tagPair/ext.js +153 -0
- package/dist/src/tagPair/include.d.ts +37 -0
- package/dist/src/tagPair/include.js +71 -0
- package/dist/src/tagPair/index.d.ts +27 -0
- package/dist/src/tagPair/index.js +79 -0
- package/dist/src/transclude.d.ts +167 -0
- package/dist/src/transclude.js +715 -0
- package/dist/util/constants.js +113 -0
- package/dist/util/debug.js +90 -0
- package/dist/util/diff.js +82 -0
- package/dist/util/lint.js +30 -0
- package/dist/util/string.js +53 -0
- package/errors/README +1 -0
- package/package.json +15 -30
- package/printed/README +1 -0
- package/bundle/bundle.min.js +0 -36
- package/extensions/dist/base.js +0 -64
- package/extensions/dist/editor.js +0 -159
- package/extensions/dist/highlight.js +0 -59
- package/extensions/dist/lint.js +0 -48
- package/extensions/editor.css +0 -63
- package/extensions/ui.css +0 -141
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AstElement = void 0;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const string_1 = require("../util/string");
|
|
7
|
+
const debug_1 = require("../util/debug");
|
|
8
|
+
const constants_1 = require("../util/constants");
|
|
9
|
+
const selector_1 = require("../parser/selector");
|
|
10
|
+
const ranges_1 = require("./ranges");
|
|
11
|
+
const title_1 = require("./title");
|
|
12
|
+
const index_1 = require("../index");
|
|
13
|
+
const node_1 = require("./node");
|
|
14
|
+
/**
|
|
15
|
+
* optionally convert to lower cases
|
|
16
|
+
* @param val 属性值
|
|
17
|
+
* @param i 是否对大小写不敏感
|
|
18
|
+
*/
|
|
19
|
+
const toCase = (val, i) => i ? val.toLowerCase() : val;
|
|
20
|
+
/**
|
|
21
|
+
* 检查某个下标是否符合表达式
|
|
22
|
+
* @param str 表达式
|
|
23
|
+
* @param i 待检查的下标
|
|
24
|
+
*/
|
|
25
|
+
const nth = (str, i) => new ranges_1.Ranges(str.split(',')).applyTo(i + 1).includes(i);
|
|
26
|
+
/**
|
|
27
|
+
* 检测:lang()伪选择器
|
|
28
|
+
* @param node 节点
|
|
29
|
+
* @param node.attributes 节点属性
|
|
30
|
+
* @param regex 语言正则
|
|
31
|
+
*/
|
|
32
|
+
const matchesLang = ({ attributes }, regex) => {
|
|
33
|
+
const lang = attributes?.['lang'];
|
|
34
|
+
return typeof lang === 'string' && regex.test(lang);
|
|
35
|
+
};
|
|
36
|
+
/* NOT FOR BROWSER END */
|
|
37
|
+
/** 类似HTMLElement */
|
|
38
|
+
class AstElement extends node_1.AstNode {
|
|
39
|
+
/** 子节点总数 */
|
|
40
|
+
get length() {
|
|
41
|
+
return this.childNodes.length;
|
|
42
|
+
}
|
|
43
|
+
/* NOT FOR BROWSER */
|
|
44
|
+
/** 全部非文本子节点 */
|
|
45
|
+
get children() {
|
|
46
|
+
return this.childNodes.filter((child) => child.type !== 'text');
|
|
47
|
+
}
|
|
48
|
+
/** 首位非文本子节点 */
|
|
49
|
+
get firstElementChild() {
|
|
50
|
+
return this.childNodes.find((child) => child.type !== 'text');
|
|
51
|
+
}
|
|
52
|
+
/** 末位非文本子节点 */
|
|
53
|
+
get lastElementChild() {
|
|
54
|
+
return this.children[this.childElementCount - 1];
|
|
55
|
+
}
|
|
56
|
+
/** 非文本子节点总数 */
|
|
57
|
+
get childElementCount() {
|
|
58
|
+
return this.children.length;
|
|
59
|
+
}
|
|
60
|
+
/** 父节点 */
|
|
61
|
+
get parentElement() {
|
|
62
|
+
return this.parentNode;
|
|
63
|
+
}
|
|
64
|
+
/** AstElement.prototype.text()的getter写法 */
|
|
65
|
+
get outerText() {
|
|
66
|
+
return this.text();
|
|
67
|
+
}
|
|
68
|
+
/** 不可见 */
|
|
69
|
+
get hidden() {
|
|
70
|
+
return this.text() === '';
|
|
71
|
+
}
|
|
72
|
+
/** 后一个可见的兄弟节点 */
|
|
73
|
+
get nextVisibleSibling() {
|
|
74
|
+
let { nextSibling } = this;
|
|
75
|
+
while (nextSibling?.text() === '') {
|
|
76
|
+
({ nextSibling } = nextSibling);
|
|
77
|
+
}
|
|
78
|
+
return nextSibling;
|
|
79
|
+
}
|
|
80
|
+
/** 前一个可见的兄弟节点 */
|
|
81
|
+
get previousVisibleSibling() {
|
|
82
|
+
let { previousSibling } = this;
|
|
83
|
+
while (previousSibling?.text() === '') {
|
|
84
|
+
({ previousSibling } = previousSibling);
|
|
85
|
+
}
|
|
86
|
+
return previousSibling;
|
|
87
|
+
}
|
|
88
|
+
/** 内部高度 */
|
|
89
|
+
get clientHeight() {
|
|
90
|
+
const { innerText } = this;
|
|
91
|
+
return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
|
|
92
|
+
}
|
|
93
|
+
/** 内部宽度 */
|
|
94
|
+
get clientWidth() {
|
|
95
|
+
const { innerText } = this;
|
|
96
|
+
if (typeof innerText === 'string') {
|
|
97
|
+
const lines = innerText.split('\n');
|
|
98
|
+
return lines[lines.length - 1].length;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
constructor() {
|
|
103
|
+
super();
|
|
104
|
+
this.seal('name');
|
|
105
|
+
}
|
|
106
|
+
/* NOT FOR BROWSER END */
|
|
107
|
+
/**
|
|
108
|
+
* 可见部分
|
|
109
|
+
* @param separator 子节点间的连接符
|
|
110
|
+
*/
|
|
111
|
+
text(separator) {
|
|
112
|
+
return (0, string_1.text)(this.childNodes, separator);
|
|
113
|
+
}
|
|
114
|
+
/** 合并相邻的文本子节点 */
|
|
115
|
+
normalize() {
|
|
116
|
+
const childNodes = [...this.childNodes];
|
|
117
|
+
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
118
|
+
const { type, data } = childNodes[i], prev = childNodes[i - 1];
|
|
119
|
+
if (type !== 'text' || this.getGaps(i - 1)) {
|
|
120
|
+
//
|
|
121
|
+
}
|
|
122
|
+
else if (data === '') {
|
|
123
|
+
childNodes.splice(i, 1);
|
|
124
|
+
}
|
|
125
|
+
else if (prev?.type === 'text') {
|
|
126
|
+
prev.setAttribute('data', prev.data + data);
|
|
127
|
+
childNodes.splice(i, 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.setAttribute('childNodes', childNodes);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 移除子节点
|
|
134
|
+
* @param i 移除位置
|
|
135
|
+
*/
|
|
136
|
+
removeAt(i) {
|
|
137
|
+
/* NOT FOR BROWSER */
|
|
138
|
+
this.verifyChild(i);
|
|
139
|
+
/* NOT FOR BROWSER END */
|
|
140
|
+
return (0, debug_1.setChildNodes)(this, i, 1)[0];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 插入子节点
|
|
144
|
+
* @param node 待插入的子节点
|
|
145
|
+
* @param i 插入位置
|
|
146
|
+
* @throws `RangeError` 不能插入祖先或子节点
|
|
147
|
+
*/
|
|
148
|
+
insertAt(node, i = this.length) {
|
|
149
|
+
/* NOT FOR BROWSER */
|
|
150
|
+
if (node.contains(this)) {
|
|
151
|
+
throw new RangeError('不能插入祖先节点!');
|
|
152
|
+
}
|
|
153
|
+
if (this.childNodes.includes(node)) {
|
|
154
|
+
throw new RangeError('不能插入子节点!');
|
|
155
|
+
}
|
|
156
|
+
this.verifyChild(i, 1);
|
|
157
|
+
node.parentNode?.removeChild(node);
|
|
158
|
+
/* NOT FOR BROWSER END */
|
|
159
|
+
(0, debug_1.setChildNodes)(this, i, 0, [node]);
|
|
160
|
+
return node;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 将选择器转化为类型谓词
|
|
164
|
+
* @param selector 选择器
|
|
165
|
+
*/
|
|
166
|
+
#getCondition(selector) {
|
|
167
|
+
let condition;
|
|
168
|
+
/* NOT FOR BROWSER */
|
|
169
|
+
if (/[^a-z\-,\s]/u.test(selector)) {
|
|
170
|
+
const stack = (0, selector_1.parseSelector)(selector);
|
|
171
|
+
condition = (token => stack.some(copy => token.#matchesArray(copy)));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
/* NOT FOR BROWSER END */
|
|
175
|
+
const types = new Set(selector.split(',').map(str => str.trim()));
|
|
176
|
+
condition = (token => types.has(token.type));
|
|
177
|
+
}
|
|
178
|
+
return condition;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 最近的祖先节点
|
|
182
|
+
* @param selector 选择器
|
|
183
|
+
*/
|
|
184
|
+
closest(selector) {
|
|
185
|
+
const condition = this.#getCondition(selector);
|
|
186
|
+
let { parentNode } = this;
|
|
187
|
+
while (parentNode) {
|
|
188
|
+
if (condition(parentNode)) {
|
|
189
|
+
return parentNode;
|
|
190
|
+
}
|
|
191
|
+
({ parentNode } = parentNode);
|
|
192
|
+
}
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 在末尾批量插入子节点
|
|
197
|
+
* @param elements 插入节点
|
|
198
|
+
*/
|
|
199
|
+
append(...elements) {
|
|
200
|
+
for (const element of elements) {
|
|
201
|
+
this.insertAt(element);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 批量替换子节点
|
|
206
|
+
* @param elements 新的子节点
|
|
207
|
+
*/
|
|
208
|
+
replaceChildren(...elements) {
|
|
209
|
+
for (let i = this.length - 1; i >= 0; i--) {
|
|
210
|
+
this.removeAt(i);
|
|
211
|
+
}
|
|
212
|
+
this.append(...elements);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 修改文本子节点
|
|
216
|
+
* @param str 新文本
|
|
217
|
+
* @param i 子节点位置
|
|
218
|
+
* @throws `RangeError` 对应位置的子节点不是文本节点
|
|
219
|
+
*/
|
|
220
|
+
setText(str, i = 0) {
|
|
221
|
+
i += i < 0 ? this.length : 0;
|
|
222
|
+
/* NOT FOR BROWSER */
|
|
223
|
+
this.verifyChild(i);
|
|
224
|
+
/* NOT FOR BROWSER END */
|
|
225
|
+
const oldText = this.childNodes[i];
|
|
226
|
+
/* NOT FOR BROWSER */
|
|
227
|
+
if (oldText.type === 'text') {
|
|
228
|
+
/* NOT FOR BROWSER END */
|
|
229
|
+
const { data } = oldText;
|
|
230
|
+
oldText.replaceData(str);
|
|
231
|
+
return data;
|
|
232
|
+
}
|
|
233
|
+
/* NOT FOR BROWSER */
|
|
234
|
+
throw new RangeError(`第 ${i} 个子节点是 ${oldText.constructor.name}!`);
|
|
235
|
+
}
|
|
236
|
+
/** @private */
|
|
237
|
+
toString(separator = '') {
|
|
238
|
+
return this.childNodes.map(String).join(separator);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* @override
|
|
242
|
+
* @param start
|
|
243
|
+
*/
|
|
244
|
+
lint(start = this.getAbsoluteIndex()) {
|
|
245
|
+
const errors = [];
|
|
246
|
+
for (let i = 0, cur = start + this.getAttribute('padding'); i < this.length; i++) {
|
|
247
|
+
const child = this.childNodes[i];
|
|
248
|
+
errors.push(...child.lint(cur));
|
|
249
|
+
cur += String(child).length + this.getGaps(i);
|
|
250
|
+
}
|
|
251
|
+
return errors;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* @override
|
|
255
|
+
* @param opt 选项
|
|
256
|
+
*/
|
|
257
|
+
print(opt = {}) {
|
|
258
|
+
return String(this) ? `<span class="wpb-${opt.class ?? this.type}">${(0, string_1.print)(this.childNodes, opt)}</span>` : '';
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 保存为JSON
|
|
262
|
+
* @param file 文件名
|
|
263
|
+
*/
|
|
264
|
+
json(file) {
|
|
265
|
+
const json = {
|
|
266
|
+
...this,
|
|
267
|
+
childNodes: this.childNodes.map(child => child.type === 'text' ? { data: child.data } : child.json()),
|
|
268
|
+
};
|
|
269
|
+
/* NOT FOR BROWSER */
|
|
270
|
+
if (typeof file === 'string') {
|
|
271
|
+
fs.writeFileSync(path.join(__dirname.slice(0, -4), '..', 'printed', `${file}${file.endsWith('.json') ? '' : '.json'}`), JSON.stringify(json, null, 2));
|
|
272
|
+
}
|
|
273
|
+
/* NOT FOR BROWSER END */
|
|
274
|
+
return json;
|
|
275
|
+
}
|
|
276
|
+
/* NOT FOR BROWSER */
|
|
277
|
+
/** @private */
|
|
278
|
+
matchesTypes(types) {
|
|
279
|
+
return types.has(this.type);
|
|
280
|
+
}
|
|
281
|
+
/** 销毁 */
|
|
282
|
+
destroy() {
|
|
283
|
+
this.parentNode?.destroy();
|
|
284
|
+
for (const child of this.childNodes) {
|
|
285
|
+
child.setAttribute('parentNode', undefined);
|
|
286
|
+
}
|
|
287
|
+
Object.setPrototypeOf(this, null);
|
|
288
|
+
}
|
|
289
|
+
/** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
|
|
290
|
+
#isProtected() {
|
|
291
|
+
const { parentNode } = this;
|
|
292
|
+
if (!parentNode) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
const { childNodes, fixed } = parentNode, protectedIndices = parentNode.getAttribute('protectedChildren').applyTo(childNodes);
|
|
296
|
+
return fixed || protectedIndices.includes(childNodes.indexOf(this));
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 检查是否符合属性选择器
|
|
300
|
+
* @param key 属性键
|
|
301
|
+
* @param equal 比较符
|
|
302
|
+
* @param val 属性值
|
|
303
|
+
* @param i 是否对大小写不敏感
|
|
304
|
+
* @throws `RangeError` 复杂属性不能用于选择器
|
|
305
|
+
*/
|
|
306
|
+
#matchesAttr(key, equal, val = '', i) {
|
|
307
|
+
const isAttr = typeof this.hasAttr === 'function' && typeof this.getAttr === 'function';
|
|
308
|
+
if (!(key in this) && (!isAttr || !this.hasAttr(key))) {
|
|
309
|
+
return equal === '!=';
|
|
310
|
+
}
|
|
311
|
+
else if (!equal) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
const v = toCase(val, i);
|
|
315
|
+
let thisVal = this.getAttribute(key);
|
|
316
|
+
if (isAttr) {
|
|
317
|
+
const attr = this.getAttr(key);
|
|
318
|
+
if (attr !== undefined) {
|
|
319
|
+
thisVal = attr === true ? '' : attr;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (thisVal instanceof RegExp) {
|
|
323
|
+
thisVal = thisVal.source;
|
|
324
|
+
}
|
|
325
|
+
if (equal === '~=') {
|
|
326
|
+
const thisVals = typeof thisVal === 'string' ? thisVal.split(/\s/u) : thisVal;
|
|
327
|
+
return Boolean(thisVals?.[Symbol.iterator])
|
|
328
|
+
&& [...thisVals].some(w => typeof w === 'string' && toCase(w, i) === v);
|
|
329
|
+
}
|
|
330
|
+
else if (typeof thisVal !== 'string') {
|
|
331
|
+
throw new RangeError(`复杂属性 ${key} 不能用于选择器!`);
|
|
332
|
+
}
|
|
333
|
+
const stringVal = toCase(thisVal, i);
|
|
334
|
+
switch (equal) {
|
|
335
|
+
case '|=':
|
|
336
|
+
return stringVal === v || stringVal.startsWith(`${v}-`);
|
|
337
|
+
case '^=':
|
|
338
|
+
return stringVal.startsWith(v);
|
|
339
|
+
case '$=':
|
|
340
|
+
return stringVal.endsWith(v);
|
|
341
|
+
case '*=':
|
|
342
|
+
return stringVal.includes(v);
|
|
343
|
+
case '!=':
|
|
344
|
+
return stringVal !== v;
|
|
345
|
+
default: // `=`
|
|
346
|
+
return stringVal === v;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* 检查是否符合解析后的选择器,不含节点关系
|
|
351
|
+
* @param step 解析后的选择器
|
|
352
|
+
* @throws `SyntaxError` 错误的正则伪选择器
|
|
353
|
+
* @throws `SyntaxError` 未定义的伪选择器
|
|
354
|
+
*/
|
|
355
|
+
#matches(step) {
|
|
356
|
+
const { parentNode, type, name, childNodes, link } = this, children = parentNode?.children, childrenOfType = children?.filter(({ type: t }) => t === type), siblingsCount = children?.length ?? 1, siblingsCountOfType = childrenOfType?.length ?? 1, index = (children?.indexOf(this) ?? 0) + 1, indexOfType = (childrenOfType?.indexOf(this) ?? 0) + 1, lastIndex = siblingsCount - index + 1, lastIndexOfType = siblingsCountOfType - indexOfType + 1;
|
|
357
|
+
return step.every(selector => {
|
|
358
|
+
if (typeof selector === 'string') {
|
|
359
|
+
switch (selector) { // 情形1:简单伪选择器、type和name
|
|
360
|
+
case '*':
|
|
361
|
+
return true;
|
|
362
|
+
case ':root':
|
|
363
|
+
return !parentNode;
|
|
364
|
+
case ':first-child':
|
|
365
|
+
return index === 1;
|
|
366
|
+
case ':first-of-type':
|
|
367
|
+
return indexOfType === 1;
|
|
368
|
+
case ':last-child':
|
|
369
|
+
return lastIndex === 1;
|
|
370
|
+
case ':last-of-type':
|
|
371
|
+
return lastIndexOfType === 1;
|
|
372
|
+
case ':only-child':
|
|
373
|
+
return siblingsCount === 1;
|
|
374
|
+
case ':only-of-type':
|
|
375
|
+
return siblingsCountOfType === 1;
|
|
376
|
+
case ':empty':
|
|
377
|
+
return !childNodes.some(({ type: t, data }) => t !== 'text' || data);
|
|
378
|
+
case ':parent':
|
|
379
|
+
return childNodes.some(({ type: t, data }) => t !== 'text' || data);
|
|
380
|
+
case ':header':
|
|
381
|
+
return type === 'heading';
|
|
382
|
+
case ':hidden':
|
|
383
|
+
return this.text() === '';
|
|
384
|
+
case ':visible':
|
|
385
|
+
return this.text() !== '';
|
|
386
|
+
case ':only-whitespace':
|
|
387
|
+
return this.text().trim() === '';
|
|
388
|
+
case ':any-link':
|
|
389
|
+
return type === 'link'
|
|
390
|
+
|| type === 'free-ext-link'
|
|
391
|
+
|| type === 'ext-link'
|
|
392
|
+
|| (type === 'file' || type === 'gallery-image' && link);
|
|
393
|
+
case ':local-link':
|
|
394
|
+
return (type === 'link' || type === 'file' || type === 'gallery-image')
|
|
395
|
+
&& link instanceof title_1.Title
|
|
396
|
+
&& link.title === '';
|
|
397
|
+
case ':invalid':
|
|
398
|
+
return type === 'table-inter' || type === 'image-parameter' && name === 'invalid';
|
|
399
|
+
case ':required':
|
|
400
|
+
return this.#isProtected() === true;
|
|
401
|
+
case ':optional':
|
|
402
|
+
return this.#isProtected() === false;
|
|
403
|
+
default: {
|
|
404
|
+
const [t, n] = selector.split('#');
|
|
405
|
+
return (!t || t === type || Boolean(constants_1.typeAliases[type]?.includes(t))) && (!n || n === name);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else if (selector.length === 4) { // 情形2:属性选择器
|
|
410
|
+
return this.#matchesAttr(...selector);
|
|
411
|
+
}
|
|
412
|
+
const [s, pseudo] = selector; // 情形3:复杂伪选择器
|
|
413
|
+
switch (pseudo) {
|
|
414
|
+
case 'is':
|
|
415
|
+
return this.matches(s);
|
|
416
|
+
case 'not':
|
|
417
|
+
return !this.matches(s);
|
|
418
|
+
case 'nth-child':
|
|
419
|
+
return nth(s, index);
|
|
420
|
+
case 'nth-of-type':
|
|
421
|
+
return nth(s, indexOfType);
|
|
422
|
+
case 'nth-last-child':
|
|
423
|
+
return nth(s, lastIndex);
|
|
424
|
+
case 'nth-last-of-type':
|
|
425
|
+
return nth(s, lastIndexOfType);
|
|
426
|
+
case 'contains':
|
|
427
|
+
return this.text().includes(s);
|
|
428
|
+
case 'has':
|
|
429
|
+
return Boolean(this.querySelector(s));
|
|
430
|
+
case 'lang': {
|
|
431
|
+
const regex = new RegExp(`^${s}(?:-|$)`, 'u');
|
|
432
|
+
return matchesLang(this, regex)
|
|
433
|
+
|| this.getAncestors().some(ancestor => matchesLang(ancestor, regex));
|
|
434
|
+
}
|
|
435
|
+
case 'regex': {
|
|
436
|
+
const mt = /^([^,]+),\s*\/(.+)\/([a-z]*)$/u.exec(s);
|
|
437
|
+
if (!mt) {
|
|
438
|
+
throw new SyntaxError('错误的伪选择器用法。请使用形如 ":regex(\'attr, /re/i\')" 的格式。');
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
return new RegExp(mt[2], mt[3]).test(String(this.getAttribute(mt[1].trim())));
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
throw new SyntaxError(`错误的正则表达式:/${mt[2]}/${mt[3]}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
default:
|
|
448
|
+
throw new SyntaxError(`未定义的伪选择器:${pseudo}`);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* 检查是否符合解析后的选择器
|
|
454
|
+
* @param copy 解析后的选择器
|
|
455
|
+
*/
|
|
456
|
+
#matchesArray(copy) {
|
|
457
|
+
const condition = [...copy], step = condition.pop();
|
|
458
|
+
if (this.#matches(step)) {
|
|
459
|
+
const { parentNode, previousElementSibling } = this;
|
|
460
|
+
switch (condition[condition.length - 1]?.relation) {
|
|
461
|
+
case undefined:
|
|
462
|
+
return true;
|
|
463
|
+
case '>':
|
|
464
|
+
return Boolean(parentNode && parentNode.#matchesArray(condition));
|
|
465
|
+
case '+':
|
|
466
|
+
return Boolean(previousElementSibling && previousElementSibling.#matchesArray(condition));
|
|
467
|
+
case '~': {
|
|
468
|
+
if (!parentNode) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
const { children } = parentNode;
|
|
472
|
+
return children.slice(0, children.indexOf(this))
|
|
473
|
+
.some(child => child.#matchesArray(condition));
|
|
474
|
+
}
|
|
475
|
+
default: // ' '
|
|
476
|
+
return this.getAncestors().some(ancestor => ancestor.#matchesArray(condition));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 检查是否符合选择器
|
|
483
|
+
* @param selector 选择器
|
|
484
|
+
*/
|
|
485
|
+
matches(selector) {
|
|
486
|
+
return selector === undefined || this.#getCondition(selector)(this);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* 符合条件的第一个后代节点
|
|
490
|
+
* @param condition 条件
|
|
491
|
+
*/
|
|
492
|
+
#getElementBy(condition) {
|
|
493
|
+
for (const child of this.children) {
|
|
494
|
+
if (condition(child)) {
|
|
495
|
+
return child;
|
|
496
|
+
}
|
|
497
|
+
const descendant = child.#getElementBy(condition);
|
|
498
|
+
if (descendant) {
|
|
499
|
+
return descendant;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 符合选择器的第一个后代节点
|
|
506
|
+
* @param selector 选择器
|
|
507
|
+
*/
|
|
508
|
+
querySelector(selector) {
|
|
509
|
+
const condition = this.#getCondition(selector);
|
|
510
|
+
return this.#getElementBy(condition);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* 类型选择器
|
|
514
|
+
* @param types
|
|
515
|
+
*/
|
|
516
|
+
getElementByTypes(types) {
|
|
517
|
+
const typeSet = new Set(types.split(',').map(str => str.trim()));
|
|
518
|
+
return this.#getElementBy((({ type }) => typeSet.has(type)));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* id选择器
|
|
522
|
+
* @param id id名
|
|
523
|
+
*/
|
|
524
|
+
getElementById(id) {
|
|
525
|
+
return this.#getElementBy((token => 'id' in token && token.id === id));
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* 符合条件的所有后代节点
|
|
529
|
+
* @param condition 条件
|
|
530
|
+
*/
|
|
531
|
+
#getElementsBy(condition) {
|
|
532
|
+
const descendants = [];
|
|
533
|
+
for (const child of this.children) {
|
|
534
|
+
if (condition(child)) {
|
|
535
|
+
descendants.push(child);
|
|
536
|
+
}
|
|
537
|
+
descendants.push(...child.#getElementsBy(condition));
|
|
538
|
+
}
|
|
539
|
+
return descendants;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* 符合选择器的所有后代节点
|
|
543
|
+
* @param selector 选择器
|
|
544
|
+
*/
|
|
545
|
+
querySelectorAll(selector) {
|
|
546
|
+
const condition = this.#getCondition(selector);
|
|
547
|
+
return this.#getElementsBy(condition);
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* 类选择器
|
|
551
|
+
* @param className 类名之一
|
|
552
|
+
*/
|
|
553
|
+
getElementsByClassName(className) {
|
|
554
|
+
return this.#getElementsBy((token => 'classList' in token && token.classList.has(className)));
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* 标签名选择器
|
|
558
|
+
* @param tag 标签名
|
|
559
|
+
*/
|
|
560
|
+
getElementsByTagName(tag) {
|
|
561
|
+
return this.#getElementsBy((({ type, name }) => name === tag && (type === 'html' || type === 'ext')));
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* 获取某一行的wikitext
|
|
565
|
+
* @param n 行号
|
|
566
|
+
*/
|
|
567
|
+
getLine(n) {
|
|
568
|
+
return String(this).split('\n', n + 1)[n];
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 在开头批量插入子节点
|
|
572
|
+
* @param elements 插入节点
|
|
573
|
+
*/
|
|
574
|
+
prepend(...elements) {
|
|
575
|
+
for (let i = 0; i < elements.length; i++) {
|
|
576
|
+
this.insertAt(elements[i], i);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* 获取子节点的位置
|
|
581
|
+
* @param node 子节点
|
|
582
|
+
* @throws `RangeError` 找不到子节点
|
|
583
|
+
*/
|
|
584
|
+
#getChildIndex(node) {
|
|
585
|
+
const i = this.childNodes.indexOf(node);
|
|
586
|
+
if (i === -1) {
|
|
587
|
+
throw new RangeError('找不到子节点!');
|
|
588
|
+
}
|
|
589
|
+
return i;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* 移除子节点
|
|
593
|
+
* @param node 子节点
|
|
594
|
+
*/
|
|
595
|
+
removeChild(node) {
|
|
596
|
+
this.removeAt(this.#getChildIndex(node));
|
|
597
|
+
return node;
|
|
598
|
+
}
|
|
599
|
+
insertBefore(child, reference) {
|
|
600
|
+
return reference === undefined
|
|
601
|
+
? this.insertAt(child)
|
|
602
|
+
: this.insertAt(child, this.#getChildIndex(reference));
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* 输出AST
|
|
606
|
+
* @param depth 当前深度
|
|
607
|
+
*/
|
|
608
|
+
echo(depth = 0) {
|
|
609
|
+
const indent = ' '.repeat(depth), str = String(this), { childNodes, type, length } = this;
|
|
610
|
+
if (childNodes.every(child => child.type === 'text' || !String(child))) {
|
|
611
|
+
console.log(`${indent}\x1B[32m<%s>\x1B[0m${(0, string_1.noWrap)(str)}\x1B[32m</%s>\x1B[0m`, type, type);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
index_1.default.info(`${indent}<${type}>`);
|
|
615
|
+
let i = this.getAttribute('padding');
|
|
616
|
+
if (i) {
|
|
617
|
+
console.log(`${indent} ${(0, string_1.noWrap)(str.slice(0, i))}`);
|
|
618
|
+
}
|
|
619
|
+
for (let j = 0; j < length; j++) {
|
|
620
|
+
const child = childNodes[j], childStr = String(child), gap = j === length - 1 ? 0 : this.getGaps(j);
|
|
621
|
+
if (!childStr) {
|
|
622
|
+
//
|
|
623
|
+
}
|
|
624
|
+
else if (child.type === 'text') {
|
|
625
|
+
console.log(`${indent} ${(0, string_1.noWrap)(child.data)}`);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
child.echo(depth + 1);
|
|
629
|
+
}
|
|
630
|
+
i += childStr.length;
|
|
631
|
+
if (gap) {
|
|
632
|
+
console.log(`${indent} ${(0, string_1.noWrap)(str.slice(i, i + gap))}`);
|
|
633
|
+
i += gap;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (i < str.length) {
|
|
637
|
+
console.log(`${indent} ${(0, string_1.noWrap)(str.slice(i))}`);
|
|
638
|
+
}
|
|
639
|
+
index_1.default.info(`${indent}</${type}>`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
exports.AstElement = AstElement;
|
|
643
|
+
constants_1.classes['AstElement'] = __filename;
|