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.
@@ -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
- "EXPECTED_UNCONNECTED_PAGE"
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
- "notalk": "notalk"
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:",
@@ -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",
@@ -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
- "EXPECTED_UNCONNECTED_PAGE"
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:",
@@ -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 === 2) {
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);
@@ -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
- const selector_1 = require("../parser/selector");
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
  }
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkToken = void 0;
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 type === 'table-inter' || type === 'image-parameter' && name === 'invalid';
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 token.matches(s);
198
+ return (0, exports.getCondition)(s, scope)(token);
188
199
  case 'not':
189
- return !token.matches(s);
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
- return Boolean(token.querySelector(s));
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)).some(child => matchesArray(child, condition));
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
- pushSimple(index);
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
- exports.checkToken = checkToken;
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;
@@ -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 atom_1.AtomToken && attr.text().trim()) {
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) {
@@ -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 && /^\d+$/u.test(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) {
@@ -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(`TranscludeToken.getPossibleValues method is only for specific magic words!`);
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(`TranscludeToken.getPossibleValues method is only for specific magic words!`);
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.12.7",
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.0.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.0",
55
- "monaco-editor": "^0.51.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": "^20.9.0"
59
+ "node": ">=20.9.0"
60
60
  }
61
61
  }