wikilint 2.6.1 → 2.7.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.
Files changed (67) hide show
  1. package/config/.schema.json +9 -0
  2. package/config/default.json +5 -0
  3. package/config/enwiki.json +3 -0
  4. package/config/llwiki.json +4 -0
  5. package/config/minimum.json +3 -0
  6. package/config/moegirl.json +4 -0
  7. package/config/zhwiki.json +5 -0
  8. package/dist/base.d.ts +4 -5
  9. package/dist/bin/cli.js +1 -1
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.js +10 -4
  12. package/dist/internal.d.ts +3 -1
  13. package/dist/lib/element.d.ts +10 -0
  14. package/dist/lib/element.js +60 -1
  15. package/dist/lib/node.d.ts +1 -2
  16. package/dist/lib/node.js +19 -18
  17. package/dist/lib/text.d.ts +1 -0
  18. package/dist/lib/text.js +7 -4
  19. package/dist/lib/title.d.ts +10 -3
  20. package/dist/lib/title.js +56 -20
  21. package/dist/mixin/attributesParent.d.ts +0 -1
  22. package/dist/mixin/hidden.d.ts +0 -1
  23. package/dist/parser/braces.js +1 -2
  24. package/dist/parser/commentAndExt.js +2 -8
  25. package/dist/parser/converter.js +1 -4
  26. package/dist/parser/externalLinks.js +17 -7
  27. package/dist/parser/hrAndDoubleUnderscore.js +1 -2
  28. package/dist/parser/html.js +7 -8
  29. package/dist/parser/links.js +9 -5
  30. package/dist/parser/list.js +1 -2
  31. package/dist/parser/magicLinks.js +4 -7
  32. package/dist/parser/quotes.js +1 -2
  33. package/dist/parser/redirect.js +22 -0
  34. package/dist/parser/table.js +6 -7
  35. package/dist/src/attribute.js +2 -2
  36. package/dist/src/attributes.js +0 -2
  37. package/dist/src/extLink.js +5 -2
  38. package/dist/src/heading.js +2 -2
  39. package/dist/src/html.js +1 -1
  40. package/dist/src/imageParameter.js +3 -5
  41. package/dist/src/imagemap.js +1 -1
  42. package/dist/src/index.d.ts +1 -3
  43. package/dist/src/index.js +38 -26
  44. package/dist/src/link/base.d.ts +1 -1
  45. package/dist/src/link/base.js +3 -3
  46. package/dist/src/link/category.d.ts +6 -1
  47. package/dist/src/link/category.js +4 -1
  48. package/dist/src/link/galleryImage.js +2 -1
  49. package/dist/src/link/redirectTarget.d.ts +25 -0
  50. package/dist/src/link/redirectTarget.js +50 -0
  51. package/dist/src/magicLink.js +1 -1
  52. package/dist/src/paramTag/inputbox.js +2 -2
  53. package/dist/src/parameter.js +0 -2
  54. package/dist/src/pre.js +1 -1
  55. package/dist/src/redirect.d.ts +26 -0
  56. package/dist/src/redirect.js +46 -0
  57. package/dist/src/syntax.d.ts +2 -3
  58. package/dist/src/table/index.js +7 -4
  59. package/dist/src/table/td.js +2 -2
  60. package/dist/src/table/trBase.js +1 -1
  61. package/dist/src/transclude.js +5 -5
  62. package/dist/util/debug.js +11 -2
  63. package/dist/util/diff.js +2 -2
  64. package/dist/util/string.js +4 -1
  65. package/i18n/zh-hans.json +2 -1
  66. package/i18n/zh-hant.json +2 -1
  67. package/package.json +11 -9
@@ -121,6 +121,14 @@
121
121
  "pattern": "^[-a-z]+$"
122
122
  }
123
123
  },
124
+ "redirection": {
125
+ "description": "magic words for redirection",
126
+ "type": "array",
127
+ "items": {
128
+ "type": "string",
129
+ "pattern": "^#.+$"
130
+ }
131
+ },
124
132
  "variants": {
125
133
  "description": "variants for language conversion",
126
134
  "type": "array",
@@ -166,6 +174,7 @@
166
174
  "protocol",
167
175
  "interwiki",
168
176
  "img",
177
+ "redirection",
169
178
  "variants"
170
179
  ],
171
180
  "additionalProperties": false
@@ -832,6 +832,11 @@
832
832
  "页数=$1": "page",
833
833
  "$1页": "page"
834
834
  },
835
+ "redirection": [
836
+ "#redirect",
837
+ "#重定向",
838
+ "#重新導向"
839
+ ],
835
840
  "variants": [
836
841
  "zh",
837
842
  "zh-hans",
@@ -406,5 +406,8 @@
406
406
  "page=$1": "page",
407
407
  "page $1": "page"
408
408
  },
409
+ "redirection": [
410
+ "#redirect"
411
+ ],
409
412
  "variants": []
410
413
  }
@@ -583,6 +583,10 @@
583
583
  "页数=$1": "page",
584
584
  "$1页": "page"
585
585
  },
586
+ "redirection": [
587
+ "#redirect",
588
+ "#重定向"
589
+ ],
586
590
  "variants": [
587
591
  "zh",
588
592
  "zh-hans",
@@ -132,5 +132,8 @@
132
132
  "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:",
133
133
  "interwiki": [],
134
134
  "img": {},
135
+ "redirection": [
136
+ "#redirect"
137
+ ],
135
138
  "variants": []
136
139
  }
@@ -676,6 +676,10 @@
676
676
  "页数=$1": "page",
677
677
  "$1页": "page"
678
678
  },
679
+ "redirection": [
680
+ "#redirect",
681
+ "#重定向"
682
+ ],
679
683
  "variants": [
680
684
  "zh",
681
685
  "zh-hans",
@@ -790,6 +790,11 @@
790
790
  "頁=$1": "page",
791
791
  "$1頁": "page"
792
792
  },
793
+ "redirection": [
794
+ "#redirect",
795
+ "#重定向",
796
+ "#重新導向"
797
+ ],
793
798
  "variants": [
794
799
  "zh",
795
800
  "zh-hans",
package/dist/base.d.ts CHANGED
@@ -7,9 +7,11 @@ export interface Config {
7
7
  readonly doubleUnderscore: [string[], string[]];
8
8
  readonly protocol: string;
9
9
  readonly img: Record<string, string>;
10
+ readonly redirection: string[];
10
11
  readonly variants: string[];
11
12
  readonly excludes?: string[];
12
13
  }
14
+ 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' | 'list' | 'dd' | 'converter' | 'converter-flags' | 'converter-flag' | 'converter-rule' | 'converter-rule-variant' | 'converter-rule-to' | 'converter-rule-from' | 'param-line' | 'imagemap-link';
13
15
  export declare const rules: readonly ["bold-header", "format-leakage", "fostered-content", "h1", "illegal-attr", "insecure-style", "invalid-gallery", "invalid-imagemap", "invalid-invoke", "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"];
14
16
  export declare namespace LintError {
15
17
  type Severity = 'error' | 'warning';
@@ -45,11 +47,8 @@ export interface AstNode {
45
47
  interface AstElement extends AstNode {
46
48
  }
47
49
  export interface Parser {
48
- config: string | Config;
49
- i18n: string | Record<string, string> | undefined;
50
- rules: readonly LintError.Rule[];
51
- /** 获取解析设置 */
52
- getConfig(): Config;
50
+ config: Config | string;
51
+ i18n: Record<string, string> | string | undefined;
53
52
  /**
54
53
  * 解析wikitext
55
54
  * @param include 是否嵌入
package/dist/bin/cli.js CHANGED
@@ -118,7 +118,7 @@ for (const file of files) {
118
118
  let start = Infinity;
119
119
  for (const { range: [from, to], text } of fixable) {
120
120
  if (to <= start) {
121
- wikitext = `${wikitext.slice(0, from)}${text}${wikitext.slice(to)}`;
121
+ wikitext = wikitext.slice(0, from) + text + wikitext.slice(to);
122
122
  start = from;
123
123
  }
124
124
  }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Config, LintError, Parser as ParserBase } from './base';
2
2
  import type { Title } from './lib/title';
3
3
  import type { Token } from './internal';
4
4
  declare interface Parser extends ParserBase {
5
+ rules: readonly LintError.Rule[];
5
6
  /**
6
7
  * 规范化页面标题
7
8
  * @param title 标题(含或不含命名空间前缀)
@@ -18,3 +19,6 @@ declare const Parser: Parser;
18
19
  export = Parser;
19
20
  export type { Config, LintError };
20
21
  export type * from './internal';
22
+ declare global {
23
+ type Acceptable = unknown;
24
+ }
package/dist/index.js CHANGED
@@ -37,13 +37,21 @@ const Parser = {
37
37
  return msg && (this.i18n?.[msg] ?? msg).replace('$1', this.msg(arg));
38
38
  },
39
39
  /** @implements */
40
- normalizeTitle(title, defaultNs = 0, include = false, config = Parser.getConfig(), halfParsed = false, decode = false, selfLink = false) {
40
+ normalizeTitle(title, defaultNs = 0, include, config = Parser.getConfig(), halfParsed, decode = false, selfLink = false) {
41
41
  const { Title } = require('./lib/title');
42
42
  if (halfParsed) {
43
43
  return new Title(title, defaultNs, config, decode, selfLink);
44
44
  }
45
45
  const { Token } = require('./src/index');
46
46
  const token = debug_1.Shadow.run(() => new Token(title, config).parseOnce(0, include).parseOnce()), titleObj = new Title(String(token), defaultNs, config, decode, selfLink);
47
+ debug_1.Shadow.run(() => {
48
+ for (const key of ['main', 'fragment']) {
49
+ const str = titleObj[key];
50
+ if (str?.includes('\0')) {
51
+ titleObj[key] = token.buildFromStr(str, constants_1.BuildMethod.Text);
52
+ }
53
+ }
54
+ });
47
55
  return titleObj;
48
56
  },
49
57
  /** @implements */
@@ -60,9 +68,7 @@ const Parser = {
60
68
  const file = path.join(__dirname, '..', 'errors', new Date().toISOString()), stage = token.getAttribute('stage');
61
69
  fs.writeFileSync(file, stage === constants_1.MAX_STAGE ? wikitext : String(token));
62
70
  fs.writeFileSync(`${file}.err`, e.stack);
63
- fs.writeFileSync(`${file}.json`, JSON.stringify({
64
- stage, include: token.getAttribute('include'), config: this.config,
65
- }, null, '\t'));
71
+ fs.writeFileSync(`${file}.json`, JSON.stringify({ stage, include, config }, null, '\t'));
66
72
  }
67
73
  throw e;
68
74
  }
@@ -1,6 +1,8 @@
1
1
  export type { AstNodes, } from './lib/node';
2
2
  export type * from './lib/text';
3
- export type * from './src/index';
3
+ export type { Token } from './src/index';
4
+ export type * from './src/redirect';
5
+ export type * from './src/link/redirectTarget';
4
6
  export type * from './src/onlyinclude';
5
7
  export type * from './src/nowiki/noinclude';
6
8
  export type * from './src/tagPair/include';
@@ -31,6 +31,16 @@ export declare abstract class AstElement extends AstNode {
31
31
  * @param selector 选择器
32
32
  */
33
33
  closest<T = Token>(selector: string): T | undefined;
34
+ /**
35
+ * 符合选择器的第一个后代节点
36
+ * @param selector 选择器
37
+ */
38
+ querySelector<T = Token>(selector: string): T | undefined;
39
+ /**
40
+ * 符合选择器的所有后代节点
41
+ * @param selector 选择器
42
+ */
43
+ querySelectorAll<T = Token>(selector: string): T[];
34
44
  /**
35
45
  * 在末尾批量插入子节点
36
46
  * @param elements 插入节点
@@ -20,13 +20,20 @@ class AstElement extends node_1.AstNode {
20
20
  /** 合并相邻的文本子节点 */
21
21
  normalize() {
22
22
  const childNodes = [...this.childNodes];
23
+ /**
24
+ * 移除子节点
25
+ * @param i 移除位置
26
+ */
27
+ const remove = (i) => {
28
+ childNodes.splice(i, 1);
29
+ };
23
30
  for (let i = childNodes.length - 1; i >= 0; i--) {
24
31
  const { type, data } = childNodes[i];
25
32
  if (type !== 'text' || this.getGaps(i - 1)) {
26
33
  //
27
34
  }
28
35
  else if (data === '') {
29
- childNodes.splice(i, 1);
36
+ remove(i);
30
37
  }
31
38
  }
32
39
  this.setAttribute('childNodes', childNodes);
@@ -70,6 +77,58 @@ class AstElement extends node_1.AstNode {
70
77
  }
71
78
  return undefined;
72
79
  }
80
+ /**
81
+ * 符合条件的第一个后代节点
82
+ * @param condition 条件
83
+ */
84
+ #getElementBy(condition) {
85
+ for (const child of this.childNodes) {
86
+ if (child.type === 'text') {
87
+ continue;
88
+ }
89
+ else if (condition(child)) {
90
+ return child;
91
+ }
92
+ const descendant = child.#getElementBy(condition);
93
+ if (descendant) {
94
+ return descendant;
95
+ }
96
+ }
97
+ return undefined;
98
+ }
99
+ /**
100
+ * 符合选择器的第一个后代节点
101
+ * @param selector 选择器
102
+ */
103
+ querySelector(selector) {
104
+ const condition = this.#getCondition(selector);
105
+ return this.#getElementBy(condition);
106
+ }
107
+ /**
108
+ * 符合条件的所有后代节点
109
+ * @param condition 条件
110
+ */
111
+ #getElementsBy(condition) {
112
+ const descendants = [];
113
+ for (const child of this.childNodes) {
114
+ if (child.type === 'text') {
115
+ continue;
116
+ }
117
+ else if (condition(child)) {
118
+ descendants.push(child);
119
+ }
120
+ descendants.push(...child.#getElementsBy(condition));
121
+ }
122
+ return descendants;
123
+ }
124
+ /**
125
+ * 符合选择器的所有后代节点
126
+ * @param selector 选择器
127
+ */
128
+ querySelectorAll(selector) {
129
+ const condition = this.#getCondition(selector);
130
+ return this.#getElementsBy(condition);
131
+ }
73
132
  /**
74
133
  * 在末尾批量插入子节点
75
134
  * @param elements 插入节点
@@ -1,6 +1,5 @@
1
- import type { LintError, AstNode as AstNodeBase } from '../base';
1
+ import type { LintError, AstNode as AstNodeBase, TokenTypes } from '../base';
2
2
  import type { AstText, Token } from '../internal';
3
- import type { TokenTypes } from '../util/constants';
4
3
  export type AstNodes = AstText | Token;
5
4
  export interface Dimension {
6
5
  readonly height: number;
package/dist/lib/node.js CHANGED
@@ -1,6 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AstNode = void 0;
4
+ /**
5
+ * 计算字符串的行列数
6
+ * @param str 字符串
7
+ */
8
+ const getDimension = (str) => {
9
+ const lines = str.split('\n'), height = lines.length;
10
+ return { height, width: lines[height - 1].length };
11
+ };
12
+ /**
13
+ * 获取子节点相对于父节点的字符位置
14
+ * @param j 子节点序号
15
+ * @param parent 父节点
16
+ */
17
+ const getIndex = (j, parent) => parent.childNodes.slice(0, j).reduce((acc, cur, i) => acc + String(cur).length + parent.getGaps(i), 0)
18
+ + parent.getAttribute('padding');
4
19
  /** 类似Node */
5
20
  class AstNode {
6
21
  childNodes = [];
@@ -66,15 +81,14 @@ class AstNode {
66
81
  posFromIndex(index) {
67
82
  const str = String(this);
68
83
  if (index >= -str.length && index <= str.length) {
69
- const lines = str.slice(0, index).split('\n'), top = lines.length - 1;
70
- return { top, left: lines[top].length };
84
+ const { height, width } = getDimension(str.slice(0, index));
85
+ return { top: height - 1, left: width };
71
86
  }
72
87
  return undefined;
73
88
  }
74
89
  /** 获取行数和最后一行的列数 */
75
90
  #getDimension() {
76
- const lines = String(this).split('\n'), height = lines.length;
77
- return { height, width: lines[height - 1].length };
91
+ return getDimension(String(this));
78
92
  }
79
93
  /** @private */
80
94
  getGaps(i) {
@@ -85,23 +99,10 @@ class AstNode {
85
99
  * @param j 子节点序号
86
100
  */
87
101
  getRelativeIndex(j) {
88
- let childNodes;
89
- /**
90
- * 获取子节点相对于父节点的字符位置,使用前需要先给`childNodes`赋值
91
- * @param end 子节点序号
92
- * @param parent 父节点
93
- */
94
- const getIndex = (end, parent) => childNodes.slice(0, end).reduce((acc, cur, i) => acc + String(cur).length + parent.getGaps(i), 0)
95
- + parent.getAttribute('padding');
96
102
  if (j === undefined) {
97
103
  const { parentNode } = this;
98
- if (parentNode) {
99
- ({ childNodes } = parentNode);
100
- return getIndex(childNodes.indexOf(this), parentNode);
101
- }
102
- return 0;
104
+ return parentNode ? getIndex(parentNode.childNodes.indexOf(this), parentNode) : 0;
103
105
  }
104
- ({ childNodes } = this);
105
106
  return getIndex(j, this);
106
107
  }
107
108
  /** 获取当前节点的绝对位置 */
@@ -13,6 +13,7 @@ export declare class AstText extends AstNode {
13
13
  /**
14
14
  * @override
15
15
  * @param start
16
+ * @throws `Error` 孤立文本节点
16
17
  */
17
18
  lint(start?: number, errorRegex?: RegExp): LintError[];
18
19
  /**
package/dist/lib/text.js CHANGED
@@ -1,10 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AstText = void 0;
4
+ const string_1 = require("../util/string");
4
5
  const index_1 = require("../index");
5
6
  const node_1 = require("./node");
6
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
7
- /<\s*(?:\/\s*)?([a-z]\w*)|\{+|\}+|\[{2,}|\[(?![^[]*?\])|((?:^|\])[^[]*?)\]+|https?[:/]\/+/giu;
8
7
  const source = '<\\s*(?:\\/\\s*)?([a-z]\\w*)' // 疑似标签
9
8
  + '|'
10
9
  + '\\{+|\\}+' // `{`、`}`
@@ -12,7 +11,7 @@ const source = '<\\s*(?:\\/\\s*)?([a-z]\\w*)' // 疑似标签
12
11
  + '\\[{2,}|\\[(?![^[]*?\\])' // `[`
13
12
  + '|'
14
13
  + '((?:^|\\])[^[]*?)\\]+', // `]`
15
- errorSyntax = new RegExp(`${source}|https?[:/]\\/+`, 'giu'), errorSyntaxUrl = new RegExp(source, 'giu'), regexes = {
14
+ errorSyntax = new RegExp(`${source}|https?[:/]\\/+`, 'giu'), errorSyntaxUrl = new RegExp(source, 'giu'), extImage = new RegExp(`^https?:\\/\\/${string_1.extUrlCharFirst}${string_1.extUrlChar}\\.(?:gif|png|jpg|jpeg)$`, 'iu'), regexes = {
16
15
  '[': /[[\]]/u,
17
16
  '{': /[{}]/u,
18
17
  ']': /[[\]](?=[^[\]]*$)/u,
@@ -77,11 +76,12 @@ class AstText extends node_1.AstNode {
77
76
  /**
78
77
  * @override
79
78
  * @param start
79
+ * @throws `Error` 孤立文本节点
80
80
  */
81
81
  lint(start = this.getAbsoluteIndex(), errorRegex) {
82
82
  const { data, parentNode, nextSibling, previousSibling } = this;
83
83
  if (!parentNode) {
84
- return [];
84
+ throw new Error('无法对孤立文本节点进行语法分析!');
85
85
  }
86
86
  const { type, name, parentNode: grandparent } = parentNode;
87
87
  let isHtmlAttrVal = false;
@@ -126,6 +126,9 @@ class AstText extends node_1.AstNode {
126
126
  else if (char === ']' && (index || length > 1)) {
127
127
  errorRegex.lastIndex--;
128
128
  }
129
+ else if (char === 'h' && index === 0 && type === 'ext-link-text' && extImage.test(data)) {
130
+ continue;
131
+ }
129
132
  const startIndex = start + index, endIndex = startIndex + length, rootStr = String(root), nextChar = rootStr[endIndex], previousChar = rootStr[startIndex - 1], severity = length > 1 && !(char === '<' && !/[\s/>]/u.test(nextChar ?? '')
130
133
  || isHtmlAttrVal && (char === '[' || char === ']'))
131
134
  || char === '{' && (nextChar === char || previousChar === '-')
@@ -1,20 +1,27 @@
1
- import Parser from '../index';
1
+ import type { Config } from '../base';
2
2
  /** MediaWiki页面标题对象 */
3
3
  export declare class Title {
4
4
  #private;
5
- readonly valid: boolean;
6
5
  ns: number;
7
6
  fragment: string | undefined;
7
+ interwiki: string;
8
+ readonly valid: boolean;
8
9
  /** 不含命名空间的标题主体部分 */
9
10
  get main(): string;
10
11
  set main(title: string);
12
+ /** 命名空间前缀 */
13
+ get prefix(): string;
14
+ /** 完整标题 */
15
+ get title(): string;
11
16
  /** 扩展名 */
12
17
  get extension(): string | undefined;
13
18
  /**
19
+ * @see MediaWikiTitleCodec::splitTitleString
20
+ *
14
21
  * @param title 标题(含或不含命名空间前缀)
15
22
  * @param defaultNs 命名空间
16
23
  * @param decode 是否需要解码
17
24
  * @param selfLink 是否允许selfLink
18
25
  */
19
- constructor(title: string, defaultNs?: number, config?: Parser.Config, decode?: boolean, selfLink?: boolean);
26
+ constructor(title: string, defaultNs: number, config: Config, decode: boolean, selfLink: boolean);
20
27
  }
package/dist/lib/title.js CHANGED
@@ -2,22 +2,40 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Title = void 0;
4
4
  const string_1 = require("../util/string");
5
- const index_1 = require("../index");
5
+ /**
6
+ * PHP的`rawurldecode`函数的JavaScript实现
7
+ * @param str 要解码的字符串
8
+ */
9
+ const rawurldecode = (str) => decodeURIComponent(str.replace(/%(?![\da-f]{2})/giu, '%25'));
6
10
  /** MediaWiki页面标题对象 */
7
11
  class Title {
8
- valid;
12
+ #main;
13
+ #namespaces;
9
14
  ns;
10
15
  fragment;
16
+ interwiki = '';
17
+ valid;
11
18
  /** @private */
12
19
  encoded = false;
13
- #main;
14
20
  /** 不含命名空间的标题主体部分 */
15
21
  get main() {
16
22
  return this.#main;
17
23
  }
18
24
  set main(title) {
19
25
  title = title.replace(/_/gu, ' ').trim();
20
- this.#main = title && `${title[0].toUpperCase()}${title.slice(1)}`;
26
+ this.#main = title && title[0].toUpperCase() + title.slice(1);
27
+ }
28
+ /** 命名空间前缀 */
29
+ get prefix() {
30
+ const namespace = this.#namespaces[this.ns];
31
+ return namespace + (namespace && ':');
32
+ }
33
+ /** 完整标题 */
34
+ get title() {
35
+ const prefix = this.interwiki + (this.interwiki && ':') + this.prefix;
36
+ // eslint-disable-next-line prefer-const
37
+ let title = (prefix + this.main).replace(/ /gu, '_');
38
+ return title;
21
39
  }
22
40
  /** 扩展名 */
23
41
  get extension() {
@@ -25,51 +43,69 @@ class Title {
25
43
  return i === -1 ? undefined : main.slice(i + 1).toLowerCase();
26
44
  }
27
45
  /**
46
+ * @see MediaWikiTitleCodec::splitTitleString
47
+ *
28
48
  * @param title 标题(含或不含命名空间前缀)
29
49
  * @param defaultNs 命名空间
30
50
  * @param decode 是否需要解码
31
51
  * @param selfLink 是否允许selfLink
32
52
  */
33
- constructor(title, defaultNs = 0, config = index_1.default.getConfig(), decode = false, selfLink = false) {
53
+ constructor(title, defaultNs, config, decode, selfLink) {
54
+ const subpage = title.trim().startsWith('../');
34
55
  title = (0, string_1.decodeHtml)(title);
35
56
  if (decode && title.includes('%')) {
36
57
  try {
37
58
  const encoded = /%(?!21|3[ce]|5[bd]|7[b-d])[\da-f]{2}/iu.test(title);
38
- title = decodeURIComponent(title);
59
+ title = rawurldecode(title);
39
60
  this.encoded = encoded;
40
61
  }
41
62
  catch { }
42
63
  }
43
64
  title = title.replace(/_/gu, ' ').trim();
44
- let ns = defaultNs;
45
- if (title.startsWith(':')) {
46
- ns = 0;
47
- title = title.slice(1).trim();
65
+ if (subpage) {
66
+ this.ns = 0;
48
67
  }
49
- const m = title.split(':');
50
- if (m.length > 1) {
51
- const id = config.nsid[m[0].trim().toLowerCase()];
52
- if (id) {
53
- ns = id;
54
- title = m.slice(1).join(':').trim();
68
+ else {
69
+ let ns = defaultNs;
70
+ if (title.startsWith(':')) {
71
+ ns = 0;
72
+ title = title.slice(1).trim();
73
+ }
74
+ const m = title.split(':');
75
+ if (m.length > 1) {
76
+ const id = config.nsid[m[0].trim().toLowerCase()];
77
+ if (id) {
78
+ ns = id;
79
+ title = m.slice(1).join(':').trim();
80
+ }
55
81
  }
82
+ this.ns = ns;
56
83
  }
57
- this.ns = ns;
58
84
  const i = title.indexOf('#');
59
85
  if (i !== -1) {
60
86
  let fragment = title.slice(i + 1).trimEnd();
61
87
  if (fragment.includes('%')) {
62
88
  try {
63
- fragment = decodeURIComponent(fragment);
89
+ fragment = rawurldecode(fragment);
64
90
  }
65
91
  catch { }
66
92
  }
67
93
  this.fragment = fragment;
68
94
  title = title.slice(0, i).trim();
69
95
  }
70
- this.valid = Boolean(title || this.interwiki || selfLink && this.fragment !== undefined)
71
- && !/^:|\0\d+[eh!+-]\x7F|[<>[\]{}|]|%[\da-f]{2}/iu.test(title);
96
+ this.valid = Boolean(title || this.interwiki || selfLink && this.ns === 0 && this.fragment !== undefined)
97
+ && !/^:|\0\d+[eh!+-]\x7F|[<>[\]{}|\n]|%[\da-f]{2}|(?:^|\/)\.{1,2}(?:$|\/)/iu.test(subpage ? /^(?:\.\.\/)+(.*)/u.exec(title)[1] : title);
72
98
  this.main = title;
99
+ Object.defineProperties(this, {
100
+ encoded: { enumerable: false, writable: false },
101
+ });
102
+ this.#namespaces = config.namespaces;
103
+ }
104
+ /** @private */
105
+ toString() {
106
+ return `${this.title}${this.fragment === undefined
107
+ ? ''
108
+ : `#${this.fragment}`}`;
73
109
  }
74
110
  }
75
111
  exports.Title = Title;
@@ -11,4 +11,3 @@ export interface AttributesParentBase {
11
11
  * @param constructor 基类
12
12
  * @param _ context
13
13
  */
14
- export declare const attributesParent: (i?: number) => <T extends AstConstructor>(constructor: T, _?: unknown) => T;
@@ -2,4 +2,3 @@
2
2
  * 解析后不可见的类
3
3
  * @param constructor 基类
4
4
  */
5
- export declare const hiddenToken: <T extends AstConstructor>(constructor: T) => T;
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseBraces = void 0;
4
4
  const string_1 = require("../util/string");
5
- const index_1 = require("../index");
6
5
  const heading_1 = require("../src/heading");
7
6
  const transclude_1 = require("../src/transclude");
8
7
  const arg_1 = require("../src/arg");
@@ -13,7 +12,7 @@ const arg_1 = require("../src/arg");
13
12
  * @param accum
14
13
  * @throws TranscludeToken.constructor()
15
14
  */
16
- const parseBraces = (wikitext, config = index_1.default.getConfig(), accum = []) => {
15
+ const parseBraces = (wikitext, config, accum) => {
17
16
  const source = `${config.excludes?.includes('heading') ? '' : '^(\0\\d+c\x7F)*={1,6}|'}\\[\\[|\\{{2,}|-\\{(?!\\{)`, { parserFunction: [, , , subst] } = config, stack = [], closes = { '=': '\n', '{': '\\}{2,}|\\|', '-': '\\}-', '[': '\\]\\]' }, marks = new Map([['!', '!'], ['!!', '+'], ['(!', '{'], ['!)', '}'], ['!-', '-'], ['=', '~']]);
18
17
  let regex = new RegExp(source, 'gmu'), mt = regex.exec(wikitext), moreBraces = wikitext.includes('}}'), lastIndex;
19
18
  while (mt
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseCommentAndExt = void 0;
4
- const index_1 = require("../index");
5
4
  const onlyinclude_1 = require("../src/onlyinclude");
6
5
  const noinclude_1 = require("../src/nowiki/noinclude");
7
6
  const include_1 = require("../src/tagPair/include");
@@ -14,7 +13,7 @@ const comment_1 = require("../src/nowiki/comment");
14
13
  * @param accum
15
14
  * @param includeOnly 是否嵌入
16
15
  */
17
- const parseCommentAndExt = (wikitext, config = index_1.default.getConfig(), accum = [], includeOnly = false) => {
16
+ const parseCommentAndExt = (wikitext, config, accum, includeOnly) => {
18
17
  const onlyincludeLeft = '<onlyinclude>', onlyincludeRight = '</onlyinclude>', { length } = onlyincludeLeft;
19
18
  /** 更新`<onlyinclude>`和`</onlyinclude>`的位置 */
20
19
  const update = () => {
@@ -50,14 +49,9 @@ const parseCommentAndExt = (wikitext, config = index_1.default.getConfig(), accu
50
49
  return str;
51
50
  }
52
51
  }
53
- /* eslint-disable @typescript-eslint/no-unused-expressions */
54
- /<foo(?:\s[^>]*)?>|<\/foo\s*>/giu;
55
- /<(bar)(\s[^>]*?)?(?:\/>|>(.*?)<\/(\1\s*)>)/gisu;
56
- /<(baz)(\s[^>]*?)?(?:\/>|>(.*?)(?:<\/(baz\s*)>|$))/gisu;
57
- /* eslint-enable @typescript-eslint/no-unused-expressions */
58
52
  const ext = config.ext.join('|'), noincludeRegex = includeOnly ? 'includeonly' : '(?:no|only)include', includeRegex = includeOnly ? 'noinclude' : 'includeonly', regex = new RegExp('<!--.*?(?:-->|$)' // comment
59
53
  + '|'
60
- + `<${noincludeRegex}(?:\\s[^>]*)?>|</${noincludeRegex}\\s*>` // <noinclude>
54
+ + `<${noincludeRegex}(?:\\s[^>]*)?/?>|</${noincludeRegex}\\s*>` // <noinclude>
61
55
  + '|'
62
56
  + `<(${ext})(\\s[^>]*?)?(?:/>|>(.*?)</(\\1\\s*)>)` // 扩展标签
63
57
  + '|'