wikiparser-node 1.12.7 → 1.13.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/config/default.json +8 -2
- package/config/enwiki.json +8 -1
- package/config/zhwiki.json +14 -2
- package/dist/addon/token.js +1 -1
- package/dist/base.d.ts +28 -1
- package/dist/base.js +28 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -4
- package/dist/lib/element.js +6 -19
- package/dist/lib/title.js +2 -4
- package/dist/parser/selector.js +69 -19
- package/dist/src/attributes.js +15 -12
- package/dist/src/link/file.js +1 -1
- package/dist/src/transclude.js +2 -2
- package/package.json +5 -5
package/config/default.json
CHANGED
|
@@ -704,6 +704,8 @@
|
|
|
704
704
|
"PAGELANGUAGE",
|
|
705
705
|
"=",
|
|
706
706
|
"#FORMAL",
|
|
707
|
+
"#bcp47",
|
|
708
|
+
"#dir",
|
|
707
709
|
"#timef",
|
|
708
710
|
"#timefl"
|
|
709
711
|
],
|
|
@@ -740,7 +742,9 @@
|
|
|
740
742
|
"静态重定向",
|
|
741
743
|
"靜態重新導向",
|
|
742
744
|
"NOGLOBAL",
|
|
743
|
-
"
|
|
745
|
+
"禁用全域用户页",
|
|
746
|
+
"EXPECTED_UNCONNECTED_PAGE",
|
|
747
|
+
"EXPECTUNUSEDTEMPLATE"
|
|
744
748
|
],
|
|
745
749
|
{
|
|
746
750
|
"forcetoc": "forcetoc",
|
|
@@ -768,7 +772,9 @@
|
|
|
768
772
|
"目录": "toc",
|
|
769
773
|
"目錄": "toc",
|
|
770
774
|
"archivedtalk": "archivedtalk",
|
|
771
|
-
"
|
|
775
|
+
"已存档讨论": "archivedtalk",
|
|
776
|
+
"notalk": "notalk",
|
|
777
|
+
"禁用讨论": "notalk"
|
|
772
778
|
}
|
|
773
779
|
],
|
|
774
780
|
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
package/config/enwiki.json
CHANGED
|
@@ -120,6 +120,8 @@
|
|
|
120
120
|
"101": "Portal talk",
|
|
121
121
|
"118": "Draft",
|
|
122
122
|
"119": "Draft talk",
|
|
123
|
+
"126": "MOS",
|
|
124
|
+
"127": "MOS talk",
|
|
123
125
|
"710": "TimedText",
|
|
124
126
|
"711": "TimedText talk",
|
|
125
127
|
"828": "Module",
|
|
@@ -158,6 +160,8 @@
|
|
|
158
160
|
"portal talk": 101,
|
|
159
161
|
"draft": 118,
|
|
160
162
|
"draft talk": 119,
|
|
163
|
+
"mos": 126,
|
|
164
|
+
"mos talk": 127,
|
|
161
165
|
"timedtext": 710,
|
|
162
166
|
"timedtext talk": 711,
|
|
163
167
|
"module": 828,
|
|
@@ -333,6 +337,8 @@
|
|
|
333
337
|
"PAGELANGUAGE",
|
|
334
338
|
"=",
|
|
335
339
|
"#FORMAL",
|
|
340
|
+
"#bcp47",
|
|
341
|
+
"#dir",
|
|
336
342
|
"#timef",
|
|
337
343
|
"#timefl"
|
|
338
344
|
],
|
|
@@ -357,7 +363,8 @@
|
|
|
357
363
|
"NONEWSECTIONLINK",
|
|
358
364
|
"STATICREDIRECT",
|
|
359
365
|
"NOGLOBAL",
|
|
360
|
-
"EXPECTED_UNCONNECTED_PAGE"
|
|
366
|
+
"EXPECTED_UNCONNECTED_PAGE",
|
|
367
|
+
"EXPECTUNUSEDTEMPLATE"
|
|
361
368
|
],
|
|
362
369
|
{
|
|
363
370
|
"forcetoc": "forcetoc",
|
package/config/zhwiki.json
CHANGED
|
@@ -122,6 +122,8 @@
|
|
|
122
122
|
"103": "WikiProject talk",
|
|
123
123
|
"118": "Draft",
|
|
124
124
|
"119": "Draft talk",
|
|
125
|
+
"126": "MOS",
|
|
126
|
+
"127": "MOS talk",
|
|
125
127
|
"710": "TimedText",
|
|
126
128
|
"711": "TimedText talk",
|
|
127
129
|
"828": "Module",
|
|
@@ -260,6 +262,8 @@
|
|
|
260
262
|
"draft talk": 119,
|
|
261
263
|
"草稿讨论": 119,
|
|
262
264
|
"草稿討論": 119,
|
|
265
|
+
"mos": 126,
|
|
266
|
+
"mos talk": 127,
|
|
263
267
|
"timedtext": 710,
|
|
264
268
|
"timedtext talk": 711,
|
|
265
269
|
"module": 828,
|
|
@@ -646,6 +650,8 @@
|
|
|
646
650
|
"PAGELANGUAGE",
|
|
647
651
|
"=",
|
|
648
652
|
"#FORMAL",
|
|
653
|
+
"#bcp47",
|
|
654
|
+
"#dir",
|
|
649
655
|
"#timef",
|
|
650
656
|
"#timefl"
|
|
651
657
|
],
|
|
@@ -682,7 +688,9 @@
|
|
|
682
688
|
"静态重定向",
|
|
683
689
|
"靜態重新導向",
|
|
684
690
|
"NOGLOBAL",
|
|
685
|
-
"
|
|
691
|
+
"禁用全域用户页",
|
|
692
|
+
"EXPECTED_UNCONNECTED_PAGE",
|
|
693
|
+
"EXPECTUNUSEDTEMPLATE"
|
|
686
694
|
],
|
|
687
695
|
{
|
|
688
696
|
"forcetoc": "forcetoc",
|
|
@@ -708,7 +716,11 @@
|
|
|
708
716
|
"無目錄": "notoc",
|
|
709
717
|
"toc": "toc",
|
|
710
718
|
"目录": "toc",
|
|
711
|
-
"目錄": "toc"
|
|
719
|
+
"目錄": "toc",
|
|
720
|
+
"archivedtalk": "archivedtalk",
|
|
721
|
+
"已存档讨论": "archivedtalk",
|
|
722
|
+
"notalk": "notalk",
|
|
723
|
+
"禁用讨论": "notalk"
|
|
712
724
|
}
|
|
713
725
|
],
|
|
714
726
|
"protocol": "bitcoin:|ftp://|ftps://|geo:|git://|gopher://|http://|https://|irc://|ircs://|magnet:|mailto:|matrix:|mms://|news:|nntp://|redis://|sftp://|sip:|sips:|sms:|ssh://|svn://|tel:|telnet://|urn:|worldwind://|xmpp:",
|
package/dist/addon/token.js
CHANGED
|
@@ -220,7 +220,7 @@ const expand = (wikitext, config, include, context, accum = [], stack = []) => {
|
|
|
220
220
|
else if (!path.isAbsolute(index_1.default.templateDir)) {
|
|
221
221
|
index_1.default.templateDir = path.join(__dirname, '..', '..', index_1.default.templateDir);
|
|
222
222
|
}
|
|
223
|
-
const titles = [title, title.replaceAll('_', ' ')], file = ['.wiki', '.txt', '']
|
|
223
|
+
const titles = [title, title.replaceAll('_', ' ')].flatMap(tt => [tt, tt.replaceAll(':', '꞉')]), file = ['.wiki', '.txt', '']
|
|
224
224
|
.flatMap(ext => titles.map(tt => path.join(index_1.default.templateDir, tt + ext)))
|
|
225
225
|
.find(fs.existsSync);
|
|
226
226
|
if (!file) {
|
package/dist/base.d.ts
CHANGED
|
@@ -16,6 +16,33 @@ export interface Config {
|
|
|
16
16
|
readonly articlePath?: string;
|
|
17
17
|
}
|
|
18
18
|
export type TokenTypes = 'root' | 'plain' | 'redirect' | 'redirect-syntax' | 'redirect-target' | 'onlyinclude' | 'noinclude' | 'include' | 'comment' | 'ext' | 'ext-attrs' | 'ext-attr-dirty' | 'ext-attr' | 'attr-key' | 'attr-value' | 'ext-inner' | 'arg' | 'arg-name' | 'arg-default' | 'hidden' | 'magic-word' | 'magic-word-name' | 'invoke-function' | 'invoke-module' | 'template' | 'template-name' | 'parameter' | 'parameter-key' | 'parameter-value' | 'heading' | 'heading-title' | 'heading-trail' | 'html' | 'html-attrs' | 'html-attr-dirty' | 'html-attr' | 'table' | 'tr' | 'td' | 'table-syntax' | 'table-attrs' | 'table-attr-dirty' | 'table-attr' | 'table-inter' | 'td-inner' | 'hr' | 'double-underscore' | 'link' | 'link-target' | 'link-text' | 'category' | 'file' | 'gallery-image' | 'imagemap-image' | 'image-parameter' | 'quote' | 'ext-link' | 'ext-link-text' | 'ext-link-url' | 'free-ext-link' | 'magic-link' | 'list' | 'dd' | 'list-range' | 'converter' | 'converter-flags' | 'converter-flag' | 'converter-rule' | 'converter-rule-variant' | 'converter-rule-to' | 'converter-rule-from' | 'param-line' | 'imagemap-link';
|
|
19
|
+
export declare const stages: {
|
|
20
|
+
redirect: number;
|
|
21
|
+
onlyinclude: number;
|
|
22
|
+
noinclude: number;
|
|
23
|
+
include: number;
|
|
24
|
+
comment: number;
|
|
25
|
+
ext: number;
|
|
26
|
+
arg: number;
|
|
27
|
+
'magic-word': number;
|
|
28
|
+
template: number;
|
|
29
|
+
heading: number;
|
|
30
|
+
html: number;
|
|
31
|
+
table: number;
|
|
32
|
+
hr: number;
|
|
33
|
+
'double-underscore': number;
|
|
34
|
+
link: number;
|
|
35
|
+
category: number;
|
|
36
|
+
file: number;
|
|
37
|
+
quote: number;
|
|
38
|
+
'ext-link': number;
|
|
39
|
+
'free-ext-link': number;
|
|
40
|
+
'magic-link': number;
|
|
41
|
+
list: number;
|
|
42
|
+
dd: number;
|
|
43
|
+
converter: number;
|
|
44
|
+
};
|
|
45
|
+
export type Stage = keyof typeof stages;
|
|
19
46
|
export declare const rules: readonly ["bold-header", "format-leakage", "fostered-content", "h1", "illegal-attr", "insecure-style", "invalid-gallery", "invalid-imagemap", "invalid-invoke", "invalid-isbn", "lonely-apos", "lonely-bracket", "lonely-http", "nested-link", "no-arg", "no-duplicate", "no-ignored", "obsolete-attr", "obsolete-tag", "parsing-order", "pipe-like", "table-layout", "tag-like", "unbalanced-header", "unclosed-comment", "unclosed-quote", "unclosed-table", "unescaped", "unknown-page", "unmatched-tag", "unterminated-url", "url-encoding", "var-anchor", "void-ext"];
|
|
20
47
|
export declare namespace LintError {
|
|
21
48
|
type Severity = 'error' | 'warning';
|
|
@@ -77,6 +104,6 @@ export interface Parser {
|
|
|
77
104
|
* @param include 是否嵌入
|
|
78
105
|
* @param maxStage 最大解析层级
|
|
79
106
|
*/
|
|
80
|
-
parse(wikitext: string, include?: boolean, maxStage?: number, config?: Config): Token;
|
|
107
|
+
parse(wikitext: string, include?: boolean, maxStage?: number | Stage | Stage[], config?: Config): Token;
|
|
81
108
|
}
|
|
82
109
|
export {};
|
package/dist/base.js
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.rules = void 0;
|
|
3
|
+
exports.rules = exports.stages = void 0;
|
|
4
|
+
exports.stages = {
|
|
5
|
+
redirect: 1,
|
|
6
|
+
onlyinclude: 1,
|
|
7
|
+
noinclude: 1,
|
|
8
|
+
include: 1,
|
|
9
|
+
comment: 1,
|
|
10
|
+
ext: 1,
|
|
11
|
+
arg: 2,
|
|
12
|
+
'magic-word': 2,
|
|
13
|
+
template: 2,
|
|
14
|
+
heading: 2,
|
|
15
|
+
html: 3,
|
|
16
|
+
table: 4,
|
|
17
|
+
hr: 5,
|
|
18
|
+
'double-underscore': 5,
|
|
19
|
+
link: 6,
|
|
20
|
+
category: 6,
|
|
21
|
+
file: 6,
|
|
22
|
+
quote: 7,
|
|
23
|
+
'ext-link': 8,
|
|
24
|
+
'free-ext-link': 9,
|
|
25
|
+
'magic-link': 9,
|
|
26
|
+
list: 10,
|
|
27
|
+
dd: 10,
|
|
28
|
+
converter: 11,
|
|
29
|
+
};
|
|
30
|
+
Object.setPrototypeOf(exports.stages, null);
|
|
4
31
|
exports.rules = [
|
|
5
32
|
'bold-header',
|
|
6
33
|
'format-leakage',
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Config, LintError, TokenTypes, Parser as ParserBase } from './base';
|
|
1
|
+
import type { Config, LintError, TokenTypes, Parser as ParserBase, Stage } from './base';
|
|
2
2
|
import type { Title } from './lib/title';
|
|
3
3
|
import type { Token } from './internal';
|
|
4
4
|
declare interface Parser extends ParserBase {
|
|
@@ -17,7 +17,7 @@ declare interface Parser extends ParserBase {
|
|
|
17
17
|
* @param include 是否嵌入
|
|
18
18
|
*/
|
|
19
19
|
normalizeTitle(title: string, defaultNs?: number, include?: boolean, config?: Config): Title;
|
|
20
|
-
parse(wikitext: string, include?: boolean, maxStage?: number, config?: Config): Token;
|
|
20
|
+
parse(wikitext: string, include?: boolean, maxStage?: number | Stage | Stage[], config?: Config): Token;
|
|
21
21
|
/**
|
|
22
22
|
* 是否是跨维基链接
|
|
23
23
|
* @param title 链接标题
|
package/dist/index.js
CHANGED
|
@@ -70,6 +70,9 @@ const Parser = {
|
|
|
70
70
|
getConfig() {
|
|
71
71
|
if (typeof this.config === 'string') {
|
|
72
72
|
this.config = rootRequire(this.config, 'config');
|
|
73
|
+
if (this.config.doubleUnderscore.length < 3) {
|
|
74
|
+
(0, diff_1.error)(`The schema (${path.resolve(__dirname, '..', 'config', '.schema.json')}) of parser configuration is updated.`);
|
|
75
|
+
}
|
|
73
76
|
/* NOT FOR BROWSER */
|
|
74
77
|
const { config: { conversionTable, redirects } } = this;
|
|
75
78
|
if (conversionTable) {
|
|
@@ -82,10 +85,7 @@ const Parser = {
|
|
|
82
85
|
return this.getConfig();
|
|
83
86
|
}
|
|
84
87
|
const { doubleUnderscore } = this.config;
|
|
85
|
-
if (doubleUnderscore.length ===
|
|
86
|
-
(0, diff_1.error)(`The schema (${path.resolve(__dirname, '..', 'config', '.schema.json')}) of parser configuration is updated.`);
|
|
87
|
-
}
|
|
88
|
-
else if (doubleUnderscore[0].length === 0) {
|
|
88
|
+
if (doubleUnderscore.length > 2 && doubleUnderscore[0].length === 0) {
|
|
89
89
|
doubleUnderscore[0] = Object.keys(doubleUnderscore[2]);
|
|
90
90
|
}
|
|
91
91
|
return {
|
|
@@ -139,6 +139,10 @@ const Parser = {
|
|
|
139
139
|
/** @implements */
|
|
140
140
|
parse(wikitext, include, maxStage = constants_1.MAX_STAGE, config = Parser.getConfig()) {
|
|
141
141
|
wikitext = (0, string_1.tidy)(wikitext);
|
|
142
|
+
if (typeof maxStage !== 'number') {
|
|
143
|
+
const types = Array.isArray(maxStage) ? maxStage : [maxStage];
|
|
144
|
+
maxStage = Math.max(...types.map(t => base_1.stages[t] || constants_1.MAX_STAGE));
|
|
145
|
+
}
|
|
142
146
|
const { Token } = require('./src/index');
|
|
143
147
|
const root = debug_1.Shadow.run(() => {
|
|
144
148
|
const token = new Token(wikitext, config);
|
package/dist/lib/element.js
CHANGED
|
@@ -3,26 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AstElement = void 0;
|
|
4
4
|
const string_1 = require("../util/string");
|
|
5
5
|
const debug_1 = require("../util/debug");
|
|
6
|
+
const selector_1 = require("../parser/selector");
|
|
6
7
|
const node_1 = require("./node");
|
|
7
8
|
/* NOT FOR BROWSER */
|
|
8
9
|
const fs = require("fs");
|
|
9
10
|
const path = require("path");
|
|
10
11
|
const constants_1 = require("../util/constants");
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 将选择器转化为类型谓词
|
|
14
|
-
* @param selector 选择器
|
|
15
|
-
*/
|
|
16
|
-
const getCondition = (selector) => (
|
|
17
|
-
/* eslint-disable @stylistic/operator-linebreak */
|
|
18
|
-
/[^a-z\-,#]/u.test(selector) ?
|
|
19
|
-
(0, selector_1.checkToken)(selector) :
|
|
20
|
-
({ type, name }) => selector.split(',').some(str => {
|
|
21
|
-
const [t, ...ns] = str.trim().split('#');
|
|
22
|
-
return (!t || t === type) && ns.every(n => n === name);
|
|
23
|
-
})
|
|
24
|
-
/* eslint-enable @stylistic/operator-linebreak */
|
|
25
|
-
);
|
|
12
|
+
/* NOT FOR BROWSER END */
|
|
26
13
|
/** 类似HTMLElement */
|
|
27
14
|
class AstElement extends node_1.AstNode {
|
|
28
15
|
/** 子节点总数 */
|
|
@@ -154,7 +141,7 @@ class AstElement extends node_1.AstNode {
|
|
|
154
141
|
* @param selector 选择器
|
|
155
142
|
*/
|
|
156
143
|
closest(selector) {
|
|
157
|
-
const condition = getCondition(selector);
|
|
144
|
+
const condition = (0, selector_1.getCondition)(selector, this);
|
|
158
145
|
let { parentNode } = this;
|
|
159
146
|
while (parentNode) {
|
|
160
147
|
if (condition(parentNode)) {
|
|
@@ -188,7 +175,7 @@ class AstElement extends node_1.AstNode {
|
|
|
188
175
|
* @param selector 选择器
|
|
189
176
|
*/
|
|
190
177
|
querySelector(selector) {
|
|
191
|
-
const condition = getCondition(selector);
|
|
178
|
+
const condition = (0, selector_1.getCondition)(selector, this);
|
|
192
179
|
return this.#getElementBy(condition);
|
|
193
180
|
}
|
|
194
181
|
/**
|
|
@@ -213,7 +200,7 @@ class AstElement extends node_1.AstNode {
|
|
|
213
200
|
* @param selector 选择器
|
|
214
201
|
*/
|
|
215
202
|
querySelectorAll(selector) {
|
|
216
|
-
const condition = getCondition(selector);
|
|
203
|
+
const condition = (0, selector_1.getCondition)(selector, this);
|
|
217
204
|
return this.#getElementsBy(condition);
|
|
218
205
|
}
|
|
219
206
|
/**
|
|
@@ -308,7 +295,7 @@ class AstElement extends node_1.AstNode {
|
|
|
308
295
|
* @param selector 选择器
|
|
309
296
|
*/
|
|
310
297
|
matches(selector) {
|
|
311
|
-
return selector === undefined || getCondition(selector)(this);
|
|
298
|
+
return selector === undefined || (0, selector_1.getCondition)(selector, this)(this);
|
|
312
299
|
}
|
|
313
300
|
/**
|
|
314
301
|
* 类型选择器
|
package/dist/lib/title.js
CHANGED
|
@@ -72,14 +72,13 @@ class Title {
|
|
|
72
72
|
this.#fragment = undefined;
|
|
73
73
|
}
|
|
74
74
|
else {
|
|
75
|
-
fragment = (0, string_1.decodeHtml)(fragment);
|
|
76
75
|
if (fragment.includes('%')) {
|
|
77
76
|
try {
|
|
78
77
|
fragment = (0, string_1.rawurldecode)(fragment);
|
|
79
78
|
}
|
|
80
79
|
catch { }
|
|
81
80
|
}
|
|
82
|
-
this.#fragment = fragment.replace(/[_ ]+/gu, ' ').trimEnd().replaceAll(' ', '_');
|
|
81
|
+
this.#fragment = (0, string_1.decodeHtml)(fragment).replace(/[_ ]+/gu, ' ').trimEnd().replaceAll(' ', '_');
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
/* NOT FOR BROWSER END */
|
|
@@ -93,7 +92,6 @@ class Title {
|
|
|
93
92
|
*/
|
|
94
93
|
constructor(title, defaultNs, config, decode, selfLink) {
|
|
95
94
|
const subpage = title.trim().startsWith('../');
|
|
96
|
-
title = (0, string_1.decodeHtml)(title);
|
|
97
95
|
if (decode && title.includes('%')) {
|
|
98
96
|
try {
|
|
99
97
|
const encoded = /%(?!21|3[ce]|5[bd]|7[b-d])[\da-f]{2}/iu.test(title);
|
|
@@ -102,7 +100,7 @@ class Title {
|
|
|
102
100
|
}
|
|
103
101
|
catch { }
|
|
104
102
|
}
|
|
105
|
-
title = title.replace(/[_ ]+/gu, ' ').trim();
|
|
103
|
+
title = (0, string_1.decodeHtml)(title).replace(/[_ ]+/gu, ' ').trim();
|
|
106
104
|
if (subpage) {
|
|
107
105
|
this.#ns = 0;
|
|
108
106
|
}
|
package/dist/parser/selector.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getCondition = void 0;
|
|
4
4
|
const constants_1 = require("../util/constants");
|
|
5
5
|
const ranges_1 = require("../lib/ranges");
|
|
6
6
|
const title_1 = require("../lib/title");
|
|
7
|
+
/* NOT FOR BROWSER */
|
|
7
8
|
const simplePseudos = new Set([
|
|
8
9
|
'root',
|
|
9
10
|
'first-child',
|
|
@@ -21,8 +22,10 @@ const simplePseudos = new Set([
|
|
|
21
22
|
'any-link',
|
|
22
23
|
'local-link',
|
|
23
24
|
'invalid',
|
|
25
|
+
'valid',
|
|
24
26
|
'required',
|
|
25
27
|
'optional',
|
|
28
|
+
'scope',
|
|
26
29
|
]), complexPseudos = new Set([
|
|
27
30
|
'is',
|
|
28
31
|
'not',
|
|
@@ -89,14 +92,18 @@ const getAttr = (token, key) => {
|
|
|
89
92
|
* 检查是否符合解析后的选择器,不含节点关系
|
|
90
93
|
* @param token 节点
|
|
91
94
|
* @param step 解析后的选择器
|
|
95
|
+
* @param scope 作用对象
|
|
96
|
+
* @param has `:has()`伪选择器
|
|
92
97
|
* @throws `SyntaxError` 错误的正则伪选择器
|
|
93
98
|
* @throws `SyntaxError` 未定义的伪选择器
|
|
94
99
|
*/
|
|
95
|
-
const matches = (token, step) => {
|
|
96
|
-
const { parentNode, type, name, childNodes, link } = token, children = parentNode?.children, childrenOfType = children?.filter(({ type: t }) => t === type), siblingsCount = children?.length ?? 1, siblingsCountOfType = childrenOfType?.length ?? 1, index = (children?.indexOf(token) ?? 0) + 1, indexOfType = (childrenOfType?.indexOf(token) ?? 0) + 1, lastIndex = siblingsCount - index + 1, lastIndexOfType = siblingsCountOfType - indexOfType + 1;
|
|
100
|
+
const matches = (token, step, scope, has) => {
|
|
101
|
+
const { parentNode, type, name, childNodes, link } = token, invalid = type === 'table-inter' || type === 'image-parameter' && name === 'invalid', children = parentNode?.children, childrenOfType = children?.filter(({ type: t }) => t === type), siblingsCount = children?.length ?? 1, siblingsCountOfType = childrenOfType?.length ?? 1, index = (children?.indexOf(token) ?? 0) + 1, indexOfType = (childrenOfType?.indexOf(token) ?? 0) + 1, lastIndex = siblingsCount - index + 1, lastIndexOfType = siblingsCountOfType - indexOfType + 1;
|
|
97
102
|
return step.every(selector => {
|
|
98
103
|
if (typeof selector === 'string') {
|
|
99
104
|
switch (selector) { // 情形1:简单伪选择器、type和name
|
|
105
|
+
case '':
|
|
106
|
+
return token === has;
|
|
100
107
|
case '*':
|
|
101
108
|
return true;
|
|
102
109
|
case ':root':
|
|
@@ -137,11 +144,15 @@ const matches = (token, step) => {
|
|
|
137
144
|
&& link instanceof title_1.Title
|
|
138
145
|
&& link.title === '';
|
|
139
146
|
case ':invalid':
|
|
140
|
-
return
|
|
147
|
+
return invalid;
|
|
148
|
+
case ':valid':
|
|
149
|
+
return !invalid;
|
|
141
150
|
case ':required':
|
|
142
151
|
return isProtected(token) === true;
|
|
143
152
|
case ':optional':
|
|
144
153
|
return isProtected(token) === false;
|
|
154
|
+
case ':scope':
|
|
155
|
+
return token === scope;
|
|
145
156
|
default: {
|
|
146
157
|
const [t, n] = selector.split('#');
|
|
147
158
|
return (!t || t === type) && (!n || n === name);
|
|
@@ -184,9 +195,9 @@ const matches = (token, step) => {
|
|
|
184
195
|
const [s, pseudo] = selector; // 情形3:复杂伪选择器
|
|
185
196
|
switch (pseudo) {
|
|
186
197
|
case 'is':
|
|
187
|
-
return
|
|
198
|
+
return (0, exports.getCondition)(s, scope)(token);
|
|
188
199
|
case 'not':
|
|
189
|
-
return !
|
|
200
|
+
return !(0, exports.getCondition)(s, scope)(token);
|
|
190
201
|
case 'nth-child':
|
|
191
202
|
return nth(s, index);
|
|
192
203
|
case 'nth-of-type':
|
|
@@ -197,8 +208,20 @@ const matches = (token, step) => {
|
|
|
197
208
|
return nth(s, lastIndexOfType);
|
|
198
209
|
case 'contains':
|
|
199
210
|
return token.text().includes(s);
|
|
200
|
-
case 'has':
|
|
201
|
-
|
|
211
|
+
case 'has': {
|
|
212
|
+
if (has) {
|
|
213
|
+
throw new SyntaxError('The :has() pseudo-selector cannot be nested.');
|
|
214
|
+
}
|
|
215
|
+
const condition = (0, exports.getCondition)(s, scope, token), childOrSibling = children && /(?:^|,)\s*[+~]/u.test(s)
|
|
216
|
+
? [...token.childNodes, ...children.slice(children.indexOf(token))]
|
|
217
|
+
: token.childNodes;
|
|
218
|
+
/**
|
|
219
|
+
* 递归查找元素
|
|
220
|
+
* @param child 子节点
|
|
221
|
+
*/
|
|
222
|
+
const hasElement = (child) => child.type !== 'text' && (condition(child) || child.childNodes.some(hasElement));
|
|
223
|
+
return childOrSibling.some(hasElement);
|
|
224
|
+
}
|
|
202
225
|
case 'lang': {
|
|
203
226
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
204
227
|
/^zh(?:-|$)/iu;
|
|
@@ -233,27 +256,30 @@ const matches = (token, step) => {
|
|
|
233
256
|
* 检查是否符合解析后的选择器
|
|
234
257
|
* @param token 节点
|
|
235
258
|
* @param copy 解析后的选择器
|
|
259
|
+
* @param scope 作用对象
|
|
260
|
+
* @param has `:has()`伪选择器
|
|
236
261
|
*/
|
|
237
|
-
const matchesArray = (token, copy) => {
|
|
262
|
+
const matchesArray = (token, copy, scope, has) => {
|
|
238
263
|
const condition = [...copy];
|
|
239
|
-
if (matches(token, condition.pop())) {
|
|
264
|
+
if (matches(token, condition.pop(), scope, has)) {
|
|
240
265
|
const { parentNode, previousElementSibling } = token;
|
|
241
266
|
switch (condition.at(-1)?.relation) {
|
|
242
267
|
case undefined:
|
|
243
268
|
return true;
|
|
244
269
|
case '>':
|
|
245
|
-
return Boolean(parentNode && matchesArray(parentNode, condition));
|
|
270
|
+
return Boolean(parentNode && matchesArray(parentNode, condition, scope, has));
|
|
246
271
|
case '+':
|
|
247
|
-
return Boolean(previousElementSibling && matchesArray(previousElementSibling, condition));
|
|
272
|
+
return Boolean(previousElementSibling && matchesArray(previousElementSibling, condition, scope, has));
|
|
248
273
|
case '~': {
|
|
249
274
|
if (!parentNode) {
|
|
250
275
|
return false;
|
|
251
276
|
}
|
|
252
277
|
const { children } = parentNode;
|
|
253
|
-
return children.slice(0, children.indexOf(token))
|
|
278
|
+
return children.slice(0, children.indexOf(token))
|
|
279
|
+
.some(child => matchesArray(child, condition, scope, has));
|
|
254
280
|
}
|
|
255
281
|
default: // ' '
|
|
256
|
-
return token.getAncestors().some(ancestor => matchesArray(ancestor, condition));
|
|
282
|
+
return token.getAncestors().some(ancestor => matchesArray(ancestor, condition, scope, has));
|
|
257
283
|
}
|
|
258
284
|
}
|
|
259
285
|
return false;
|
|
@@ -276,8 +302,10 @@ const deQuote = (val) => /^(["']).*\1$/u.test(val) ? val.slice(1, -1) : val.trim
|
|
|
276
302
|
/**
|
|
277
303
|
* 检查节点是否符合选择器
|
|
278
304
|
* @param selector
|
|
305
|
+
* @param scope 作用对象
|
|
306
|
+
* @param has `:has()`伪选择器
|
|
279
307
|
*/
|
|
280
|
-
const checkToken = (selector) => (token) => {
|
|
308
|
+
const checkToken = (selector, scope, has) => (token) => {
|
|
281
309
|
let sanitized = selector.trim();
|
|
282
310
|
for (const [c, entity] of specialChars) {
|
|
283
311
|
sanitized = sanitized.replaceAll(`\\${c}`, entity);
|
|
@@ -327,7 +355,7 @@ const checkToken = (selector) => (token) => {
|
|
|
327
355
|
* @throws `SyntaxError` 非法的选择器
|
|
328
356
|
*/
|
|
329
357
|
const needUniversal = () => {
|
|
330
|
-
if (step.length === 0) {
|
|
358
|
+
if (step.length === 0 && (condition.length > 1 || !has)) {
|
|
331
359
|
throw new SyntaxError(`Invalid selector!\n${selector}\nYou may need the universal selector '*'.`);
|
|
332
360
|
}
|
|
333
361
|
};
|
|
@@ -346,7 +374,12 @@ const checkToken = (selector) => (token) => {
|
|
|
346
374
|
stack.push(condition);
|
|
347
375
|
}
|
|
348
376
|
else if (combinator.has(syntax)) { // 情形2:关系
|
|
349
|
-
|
|
377
|
+
if (has && syntax && condition.length === 1 && step.length === 0 && !sanitized.slice(0, index).trim()) {
|
|
378
|
+
step.push('');
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
pushSimple(index);
|
|
382
|
+
}
|
|
350
383
|
needUniversal();
|
|
351
384
|
step.relation = syntax;
|
|
352
385
|
step = [];
|
|
@@ -385,9 +418,26 @@ const checkToken = (selector) => (token) => {
|
|
|
385
418
|
if (regex === regularRegex) {
|
|
386
419
|
pushSimple();
|
|
387
420
|
needUniversal();
|
|
388
|
-
return stack.some(copy => matchesArray(token, copy));
|
|
421
|
+
return stack.some(copy => matchesArray(token, copy, scope, has));
|
|
389
422
|
}
|
|
390
423
|
throw new SyntaxError(`Unclosed '${regex === attributeRegex ? '[' : '('}' in the selector!\n${desanitize(sanitized)}`);
|
|
391
424
|
};
|
|
392
|
-
|
|
425
|
+
/* NOT FOR BROWSER END */
|
|
426
|
+
/**
|
|
427
|
+
* 将选择器转化为类型谓词
|
|
428
|
+
* @param selector 选择器
|
|
429
|
+
* @param scope 作用对象
|
|
430
|
+
* @param has `:has()`伪选择器
|
|
431
|
+
*/
|
|
432
|
+
const getCondition = (selector, scope, has) => (
|
|
433
|
+
/* eslint-disable @stylistic/operator-linebreak */
|
|
434
|
+
/[^a-z\-,#]/u.test(selector) ?
|
|
435
|
+
checkToken(selector, scope, has) :
|
|
436
|
+
({ type, name }) => selector.split(',').some(str => {
|
|
437
|
+
const [t, ...ns] = str.trim().split('#');
|
|
438
|
+
return (!t || t === type) && ns.every(n => n === name);
|
|
439
|
+
})
|
|
440
|
+
/* eslint-enable @stylistic/operator-linebreak */
|
|
441
|
+
);
|
|
442
|
+
exports.getCondition = getCondition;
|
|
393
443
|
constants_1.parsers['parseSelector'] = __filename;
|
package/dist/src/attributes.js
CHANGED
|
@@ -173,18 +173,7 @@ class AttributesToken extends index_2.Token {
|
|
|
173
173
|
errors.push(e);
|
|
174
174
|
}
|
|
175
175
|
for (const attr of childNodes) {
|
|
176
|
-
if (attr instanceof
|
|
177
|
-
const e = (0, lint_1.generateForChild)(attr, rect, 'no-ignored', 'containing invalid attribute');
|
|
178
|
-
e.suggestions = [
|
|
179
|
-
{
|
|
180
|
-
desc: 'remove',
|
|
181
|
-
range: [e.startIndex, e.endIndex],
|
|
182
|
-
text: ' ',
|
|
183
|
-
},
|
|
184
|
-
];
|
|
185
|
-
errors.push(e);
|
|
186
|
-
}
|
|
187
|
-
else if (attr instanceof attribute_1.AttributeToken) {
|
|
176
|
+
if (attr instanceof attribute_1.AttributeToken) {
|
|
188
177
|
const { name } = attr;
|
|
189
178
|
if (attrs.has(name)) {
|
|
190
179
|
duplicated.add(name);
|
|
@@ -194,6 +183,20 @@ class AttributesToken extends index_2.Token {
|
|
|
194
183
|
attrs.set(name, [attr]);
|
|
195
184
|
}
|
|
196
185
|
}
|
|
186
|
+
else {
|
|
187
|
+
const str = attr.text().trim();
|
|
188
|
+
if (str) {
|
|
189
|
+
const e = (0, lint_1.generateForChild)(attr, rect, 'no-ignored', 'containing invalid attribute', /[\p{L}\d]/u.test(str) ? 'error' : 'warning');
|
|
190
|
+
e.suggestions = [
|
|
191
|
+
{
|
|
192
|
+
desc: 'remove',
|
|
193
|
+
range: [e.startIndex, e.endIndex],
|
|
194
|
+
text: ' ',
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
errors.push(e);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
197
200
|
}
|
|
198
201
|
if (duplicated.size > 0) {
|
|
199
202
|
for (const key of duplicated) {
|
package/dist/src/link/file.js
CHANGED
|
@@ -309,7 +309,7 @@ class FileToken extends base_1.LinkBaseToken {
|
|
|
309
309
|
/** @private */
|
|
310
310
|
toHtmlInternal(_, nocc) {
|
|
311
311
|
/** @ignore */
|
|
312
|
-
const isInteger = (n) => Boolean(n &&
|
|
312
|
+
const isInteger = (n) => Boolean(n && !/\D/u.test(n));
|
|
313
313
|
const { link, width, height, type } = this, file = this.getAttribute('title'), fr = this.getFrame(), manual = fr instanceof title_1.Title, visibleCaption = manual || fr === 'thumbnail' || fr === 'framed' || type === 'gallery-image', caption = this.getArg('caption')?.toHtmlInternal(true, nocc) ?? '', titleFromCaption = visibleCaption && type !== 'gallery-image' ? '' : (0, string_1.sanitizeAlt)(caption), hasLink = manual || link !== file, title = titleFromCaption || (hasLink && typeof link !== 'string' ? link.getTitleAttr() : ''), titleAttr = title && ` title="${title}"`, alt = (0, string_1.sanitizeAlt)(this.getArg('alt')?.toHtmlInternal(true)) ?? titleFromCaption, horiz = this.getHorizAlign() ?? '', vert = this.getVertAlign() ?? '', className = `${horiz ? `mw-halign-${horiz}` : vert && `mw-valign-${vert}`}${this.getValue('border') ? ' mw-image-border' : ''} ${(0, string_1.sanitizeAlt)(this.getValue('class')) ?? ''}`.trim(), classAttr = className && ` class="${className}"`, img = `<img${alt && ` alt="${alt}"`} src="${(manual ? fr : file).getUrl()}" class="mw-file-element"${isInteger(width) ? ` width="${width}"` : ''}${isInteger(height) ? ` height="${height}"` : ''}>`;
|
|
314
314
|
let href = '';
|
|
315
315
|
if (link) {
|
package/dist/src/transclude.js
CHANGED
|
@@ -355,7 +355,7 @@ class TranscludeToken extends index_2.Token {
|
|
|
355
355
|
getPossibleValues() {
|
|
356
356
|
const { type, name, childNodes } = this;
|
|
357
357
|
if (type === 'template') {
|
|
358
|
-
throw new Error(
|
|
358
|
+
throw new Error('TranscludeToken.getPossibleValues method is only for specific magic words!');
|
|
359
359
|
}
|
|
360
360
|
let start;
|
|
361
361
|
switch (name) {
|
|
@@ -369,7 +369,7 @@ class TranscludeToken extends index_2.Token {
|
|
|
369
369
|
start = 3;
|
|
370
370
|
break;
|
|
371
371
|
default:
|
|
372
|
-
throw new Error(
|
|
372
|
+
throw new Error('TranscludeToken.getPossibleValues method is only for specific magic words!');
|
|
373
373
|
}
|
|
374
374
|
const queue = childNodes.slice(start, start + 2).map(({ childNodes: [, value] }) => value);
|
|
375
375
|
for (let i = 0; i < queue.length;) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wikiparser-node",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A Node.js parser for MediaWiki markup with AST",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mediawiki",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"chalk": "^4.1.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@bhsd/common": "^0.
|
|
51
|
+
"@bhsd/common": "^0.1.1",
|
|
52
52
|
"@codemirror/lint": "^6.8.0",
|
|
53
53
|
"@types/node": "^20.11.6",
|
|
54
|
-
"codejar-async": "^4.2.
|
|
55
|
-
"monaco-editor": "^0.
|
|
54
|
+
"codejar-async": "^4.2.3",
|
|
55
|
+
"monaco-editor": "^0.52.0",
|
|
56
56
|
"v8r": "^3.0.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
|
-
"node": "
|
|
59
|
+
"node": ">=20.9.0"
|
|
60
60
|
}
|
|
61
61
|
}
|