wikiparser-node 0.3.0 → 0.4.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 (81) hide show
  1. package/.eslintrc.json +472 -34
  2. package/README.md +1 -1
  3. package/config/default.json +58 -30
  4. package/config/llwiki.json +22 -90
  5. package/config/moegirl.json +51 -13
  6. package/config/zhwiki.json +1269 -0
  7. package/index.js +114 -104
  8. package/lib/element.js +448 -440
  9. package/lib/node.js +335 -115
  10. package/lib/ranges.js +27 -18
  11. package/lib/text.js +146 -0
  12. package/lib/title.js +13 -5
  13. package/mixin/attributeParent.js +70 -24
  14. package/mixin/fixedToken.js +14 -6
  15. package/mixin/hidden.js +6 -4
  16. package/mixin/sol.js +27 -10
  17. package/package.json +9 -3
  18. package/parser/brackets.js +22 -17
  19. package/parser/commentAndExt.js +18 -16
  20. package/parser/converter.js +14 -13
  21. package/parser/externalLinks.js +12 -11
  22. package/parser/hrAndDoubleUnderscore.js +23 -14
  23. package/parser/html.js +10 -9
  24. package/parser/links.js +15 -14
  25. package/parser/list.js +12 -11
  26. package/parser/magicLinks.js +12 -11
  27. package/parser/quotes.js +6 -5
  28. package/parser/selector.js +175 -0
  29. package/parser/table.js +25 -18
  30. package/printed/example.json +120 -0
  31. package/src/arg.js +56 -32
  32. package/src/atom/hidden.js +5 -2
  33. package/src/atom/index.js +17 -9
  34. package/src/attribute.js +182 -100
  35. package/src/converter.js +68 -41
  36. package/src/converterFlags.js +67 -45
  37. package/src/converterRule.js +117 -65
  38. package/src/extLink.js +66 -18
  39. package/src/gallery.js +42 -15
  40. package/src/heading.js +34 -15
  41. package/src/html.js +97 -35
  42. package/src/imageParameter.js +83 -54
  43. package/src/index.js +299 -178
  44. package/src/link/category.js +20 -52
  45. package/src/link/file.js +59 -28
  46. package/src/link/galleryImage.js +21 -7
  47. package/src/link/index.js +146 -60
  48. package/src/magicLink.js +34 -12
  49. package/src/nowiki/comment.js +22 -10
  50. package/src/nowiki/dd.js +37 -22
  51. package/src/nowiki/doubleUnderscore.js +16 -7
  52. package/src/nowiki/hr.js +11 -7
  53. package/src/nowiki/index.js +16 -9
  54. package/src/nowiki/list.js +2 -2
  55. package/src/nowiki/noinclude.js +8 -4
  56. package/src/nowiki/quote.js +11 -7
  57. package/src/onlyinclude.js +19 -7
  58. package/src/parameter.js +65 -38
  59. package/src/syntax.js +26 -20
  60. package/src/table/index.js +260 -165
  61. package/src/table/td.js +98 -52
  62. package/src/table/tr.js +102 -58
  63. package/src/tagPair/ext.js +27 -19
  64. package/src/tagPair/include.js +16 -11
  65. package/src/tagPair/index.js +64 -29
  66. package/src/transclude.js +170 -93
  67. package/test/api.js +83 -0
  68. package/test/real.js +133 -0
  69. package/test/test.js +28 -0
  70. package/test/util.js +80 -0
  71. package/tool/index.js +41 -31
  72. package/typings/api.d.ts +13 -0
  73. package/typings/array.d.ts +28 -0
  74. package/typings/event.d.ts +24 -0
  75. package/typings/index.d.ts +46 -4
  76. package/typings/node.d.ts +15 -9
  77. package/typings/parser.d.ts +7 -0
  78. package/typings/tool.d.ts +3 -2
  79. package/util/debug.js +21 -18
  80. package/util/string.js +40 -27
  81. package/typings/element.d.ts +0 -28
package/lib/ranges.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..');
3
+ const Parser = require('..');
4
4
 
5
5
  /** 模拟Python的Range对象。除`step`至少为`1`外,允许负数、小数或`end < start`的情形。 */
6
6
  class Range {
@@ -8,7 +8,12 @@ class Range {
8
8
  end;
9
9
  step;
10
10
 
11
- /** @param {string|Range} str */
11
+ /**
12
+ * @param {string|Range} str 表达式
13
+ * @throws `RangeError` 起点、终点和步长均应为整数
14
+ * @throws `RangeError` n的系数不能为0
15
+ * @throws `RangeError` 应使用CSS选择器或Python切片的格式
16
+ */
12
17
  constructor(str) {
13
18
  if (str instanceof Range) {
14
19
  Object.assign(this, str);
@@ -32,7 +37,7 @@ class Range {
32
37
  throw new RangeError(`步长 ${this.step} 应为整数!`);
33
38
  }
34
39
  } else {
35
- const mt = str.match(/^([+-])?(\d+)?n(?:([+-])(\d+))?$/);
40
+ const mt = /^([+-])?(\d+)?n(?:([+-])(\d+))?$/u.exec(str);
36
41
  if (mt) {
37
42
  const [, sgnA = '+', a = 1, sgnB = '+'] = mt,
38
43
  b = Number(mt[4] ?? 0);
@@ -56,19 +61,20 @@ class Range {
56
61
  }
57
62
 
58
63
  /**
59
- * 将Range转换为针对特定Array的下标集
60
- * @param {number|any[]} arr
64
+ * 将Range转换为针对特定数组的下标集
65
+ * @param {number|any[]} arr 参考数组
61
66
  * @complexity `n`
62
67
  */
63
68
  applyTo(arr) {
64
69
  return new Array(typeof arr === 'number' ? arr : arr.length).fill().map((_, i) => i)
65
- .slice(this.start, this.end).filter((_, j) => j % this.step === 0);
70
+ .slice(this.start, this.end)
71
+ .filter((_, j) => j % this.step === 0);
66
72
  }
67
73
  }
68
74
 
69
75
  /** @extends {Array<number|Range>} */
70
76
  class Ranges extends Array {
71
- /** @param {number|string|Range|(number|string|Range)[]} arr */
77
+ /** @param {number|string|Range|(number|string|Range)[]} arr 表达式数组 */
72
78
  constructor(arr) {
73
79
  super();
74
80
  if (arr === undefined) {
@@ -94,24 +100,27 @@ class Ranges extends Array {
94
100
 
95
101
  /**
96
102
  * 将Ranges转换为针对特定Array的下标集
97
- * @param {number|any[]} arr
103
+ * @param {number|any[]} arr 参考数组
98
104
  * @complexity `n`
99
105
  */
100
106
  applyTo(arr) {
101
107
  const length = typeof arr === 'number' ? arr : arr.length;
102
- return [...new Set(
103
- Array.from(this).flatMap(ele => {
104
- if (typeof ele === 'number') {
105
- return ele < 0 ? ele + length : ele;
106
- }
107
- return ele.applyTo(length);
108
- }),
109
- )].filter(i => i >= 0 && i < length).sort();
108
+ return [
109
+ ...new Set(
110
+ [...this].flatMap(ele => {
111
+ if (typeof ele === 'number') {
112
+ return ele < 0 ? ele + length : ele;
113
+ }
114
+ return ele.applyTo(length);
115
+ }),
116
+ ),
117
+ ].filter(i => i >= 0 && i < length).sort();
110
118
  }
111
119
 
112
120
  /**
113
- * @param {string} str
114
- * @param {number} i
121
+ * 检查某个下标是否符合表达式
122
+ * @param {string} str 表达式
123
+ * @param {number} i 待检查的下标
115
124
  */
116
125
  static nth(str, i) {
117
126
  return new Ranges(str.split(',')).applyTo(i + 1).includes(i);
package/lib/text.js ADDED
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ const AstNode = require('./node'),
4
+ Parser = require('..');
5
+
6
+ /** 文本节点 */
7
+ class AstText extends AstNode {
8
+ type = 'text';
9
+ /** @type {string} */ data;
10
+
11
+ /** 文本长度 */
12
+ get length() {
13
+ return this.data.length;
14
+ }
15
+
16
+ /** @param {string} text 包含文本 */
17
+ constructor(text = '') {
18
+ super();
19
+ Object.defineProperties(this, {
20
+ data: {value: text, writable: false},
21
+ childNodes: {enumerable: false, configurable: false},
22
+ type: {enumerable: false, writable: false, configurable: false},
23
+ });
24
+ }
25
+
26
+ /** 复制 */
27
+ cloneNode() {
28
+ return new AstText(this.data);
29
+ }
30
+
31
+ /**
32
+ * @override
33
+ * @template {string} T
34
+ * @param {T} key 属性键
35
+ * @returns {TokenAttribute<T>}
36
+ */
37
+ getAttribute(key) {
38
+ return key === 'verifyChild'
39
+ ? () => {
40
+ throw new Error('文本节点没有子节点!');
41
+ }
42
+ : super.getAttribute(key);
43
+ }
44
+
45
+ /** 输出字符串 */
46
+ toString() {
47
+ return this.data;
48
+ }
49
+
50
+ /** @override */
51
+ text() {
52
+ return this.data;
53
+ }
54
+
55
+ /**
56
+ * 修改内容
57
+ * @param {string} text 新内容
58
+ */
59
+ #setData(text) {
60
+ text = String(text);
61
+ const {data} = this,
62
+ e = new Event('text', {bubbles: true});
63
+ this.setAttribute('data', text);
64
+ if (data !== text) {
65
+ this.dispatchEvent(e, {oldText: data, newText: text});
66
+ }
67
+ return this;
68
+ }
69
+
70
+ /**
71
+ * 在后方添加字符串
72
+ * @param {string} text 添加的字符串
73
+ * @throws `Error` 禁止外部调用
74
+ */
75
+ appendData(text) {
76
+ this.#setData(this.data + text);
77
+ }
78
+
79
+ /**
80
+ * 删减字符串
81
+ * @param {number} offset 起始位置
82
+ * @param {number} count 删减字符数
83
+ * @throws `RangeError` 错误的删减位置
84
+ * @throws `Error` 禁止外部调用
85
+ */
86
+ deleteData(offset, count) {
87
+ this.#setData(this.data.slice(0, offset) + this.data.slice(offset + count));
88
+ }
89
+
90
+ /**
91
+ * 插入字符串
92
+ * @param {number} offset 插入位置
93
+ * @param {string} text 待插入的字符串
94
+ * @throws `RangeError` 错误的插入位置
95
+ * @throws `Error` 禁止外部调用
96
+ */
97
+ insertData(offset, text) {
98
+ this.#setData(this.data.slice(0, offset) + text + this.data.slice(offset));
99
+ }
100
+
101
+ /**
102
+ * 替换字符串
103
+ * @param {string} text 替换的字符串
104
+ * @throws `Error` 禁止外部调用
105
+ */
106
+ replaceData(text = '') {
107
+ this.#setData(text);
108
+ }
109
+
110
+ /**
111
+ * 提取子串
112
+ * @param {number} offset 起始位置
113
+ * @param {number} count 字符数
114
+ */
115
+ substringData(offset, count) {
116
+ return this.data.slice(offset, offset + count);
117
+ }
118
+
119
+ /**
120
+ * 将文本子节点分裂为两部分
121
+ * @param {number} offset 分裂位置
122
+ * @throws `RangeError` 错误的断开位置
123
+ * @throws `Error` 没有父节点
124
+ */
125
+ splitText(offset) {
126
+ if (typeof offset !== 'number') {
127
+ this.typeError('splitText', 'Number');
128
+ } else if (offset > this.length || offset < -this.length || !Number.isInteger(offset)) {
129
+ throw new RangeError(`错误的断开位置!${offset}`);
130
+ }
131
+ const {parentNode, data} = this;
132
+ if (!parentNode) {
133
+ throw new Error('待分裂的文本节点没有父节点!');
134
+ }
135
+ const newText = new AstText(data.slice(offset)),
136
+ childNodes = [...parentNode.childNodes];
137
+ this.setAttribute('data', data.slice(0, offset));
138
+ childNodes.splice(childNodes.indexOf(this) + 1, 0, newText);
139
+ newText.setAttribute('parentNode', parentNode);
140
+ parentNode.setAttribute('childNodes', childNodes);
141
+ return newText;
142
+ }
143
+ }
144
+
145
+ Parser.classes.AstText = __filename;
146
+ module.exports = AstText;
package/lib/title.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const {ucfirst} = require('../util/string'),
4
- /** @type {Parser} */ Parser = require('..');
3
+ const Parser = require('..');
5
4
 
5
+ /** MediaWiki页面标题对象 */
6
6
  class Title {
7
7
  title = '';
8
8
  main = '';
@@ -12,7 +12,10 @@ class Title {
12
12
  fragment = '';
13
13
  valid = true;
14
14
 
15
- /** @param {string} title */
15
+ /**
16
+ * @param {string} title 标题(含或不含命名空间前缀)
17
+ * @param {number} defaultNs 命名空间
18
+ */
16
19
  constructor(title, defaultNs = 0, config = Parser.getConfig()) {
17
20
  const {namespaces, nsid} = config;
18
21
  let namespace = namespaces[defaultNs];
@@ -50,10 +53,15 @@ class Title {
50
53
  this.fragment ||= fragment;
51
54
  title = title.slice(0, i).trim();
52
55
  }
53
- this.main = ucfirst(title);
56
+ this.main = title && `${title[0].toUpperCase()}${title.slice(1)}`;
54
57
  this.prefix = `${namespace}${namespace && ':'}`;
55
58
  this.title = `${iw ? `${this.interwiki}:` : ''}${this.prefix}${this.main}`;
56
- this.valid = Boolean(this.main) && !/\x00\d+[eh!+-]\x7f|[<>[\]{}|]/.test(this.title);
59
+ this.valid = Boolean(this.main || this.fragment) && !/\0\d+[eh!+-]\x7F|[<>[\]{}|]/u.test(this.title);
60
+ }
61
+
62
+ /** @override */
63
+ toString() {
64
+ return `${this.title}${this.fragment && '#'}${this.fragment}`;
57
65
  }
58
66
  }
59
67
 
@@ -1,65 +1,111 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..'),
3
+ const Parser = require('..'),
4
4
  AttributeToken = require('../src/attribute');
5
5
 
6
6
  /**
7
+ * 子节点含有AttributeToken的类
7
8
  * @template T
8
- * @param {T} constructor
9
+ * @param {T} Constructor 基类
10
+ * @param {number} i AttributeToken子节点的位置
9
11
  * @returns {T}
10
12
  */
11
- const attributeParent = (ct, i = 0) => class extends ct {
13
+ const attributeParent = (Constructor, i = 0) => class extends Constructor {
12
14
  /**
13
- * @this {{children: AttributeToken[]}}
14
- * @param {string} key
15
+ * getAttr()方法的getter写法
16
+ * @returns {Record<string, string|true>}
17
+ */
18
+ get attributes() {
19
+ return this.getAttr();
20
+ }
21
+
22
+ /** 以字符串表示的class属性 */
23
+ get className() {
24
+ const attr = this.getAttr('class');
25
+ return typeof attr === 'string' ? attr : '';
26
+ }
27
+
28
+ set className(className) {
29
+ this.setAttr('class', className);
30
+ }
31
+
32
+ /** 以Set表示的class属性 */
33
+ get classList() {
34
+ return new Set(this.className.split(/\s/u));
35
+ }
36
+
37
+ /** id属性 */
38
+ get id() {
39
+ const attr = this.getAttr('id');
40
+ return typeof attr === 'string' ? attr : '';
41
+ }
42
+
43
+ set id(id) {
44
+ this.setAttr('id', id);
45
+ }
46
+
47
+ /**
48
+ * AttributeToken子节点是否具有某属性
49
+ * @this {{childNodes: AttributeToken[]}}
50
+ * @param {string} key 属性键
15
51
  */
16
52
  hasAttr(key) {
17
- return this.children.at(i).hasAttr(key);
53
+ return this.childNodes.at(i).hasAttr(key);
18
54
  }
19
55
 
20
56
  /**
21
- * @this {{children: AttributeToken[]}}
57
+ * 获取AttributeToken子节点的属性
58
+ * @this {{childNodes: AttributeToken[]}}
22
59
  * @template {string|undefined} T
23
- * @param {T} key
60
+ * @param {T} key 属性键
24
61
  */
25
62
  getAttr(key) {
26
- return this.children.at(i).getAttr(key);
63
+ return this.childNodes.at(i).getAttr(key);
27
64
  }
28
65
 
29
- /** @this {{children: AttributeToken[]}} */
66
+ /**
67
+ * 列举AttributeToken子节点的属性键
68
+ * @this {{childNodes: AttributeToken[]}}
69
+ */
30
70
  getAttrNames() {
31
- return this.children.at(i).getAttrNames();
71
+ return this.childNodes.at(i).getAttrNames();
32
72
  }
33
73
 
34
- /** @this {{children: AttributeToken[]}} */
74
+ /**
75
+ * AttributeToken子节点是否具有任意属性
76
+ * @this {{childNodes: AttributeToken[]}}
77
+ */
35
78
  hasAttrs() {
36
- return this.children.at(i).hasAttrs();
79
+ return this.childNodes.at(i).hasAttrs();
37
80
  }
38
81
 
39
82
  /**
40
- * @this {{children: AttributeToken[]}}
41
- * @param {string} key
42
- * @param {string|boolean} value
83
+ * AttributeToken子节点设置属性
84
+ * @this {{childNodes: AttributeToken[]}}
85
+ * @param {string} key 属性键
86
+ * @param {string|boolean} value 属性值
43
87
  */
44
88
  setAttr(key, value) {
45
- return this.children.at(i).setAttr(key, value);
89
+ return this.childNodes.at(i).setAttr(key, value);
46
90
  }
47
91
 
48
92
  /**
49
- * @this {{children: AttributeToken[]}}
50
- * @param {string} key
93
+ * 移除AttributeToken子节点的某属性
94
+ * @this {{childNodes: AttributeToken[]}}
95
+ * @param {string} key 属性键
51
96
  */
52
97
  removeAttr(key) {
53
- this.children.at(i).removeAttr(key);
98
+ this.childNodes.at(i).removeAttr(key);
54
99
  }
55
100
 
56
101
  /**
57
- * @this {{children: AttributeToken[]}}
58
- * @param {string} key
59
- * @param {boolean|undefined} force
102
+ * 开关AttributeToken子节点的某属性
103
+ * @this {{childNodes: AttributeToken[]}}
104
+ * @param {string} key 属性键
105
+ * @param {boolean|undefined} force 强制开启或关闭
60
106
  */
61
107
  toggleAttr(key, force) {
62
- this.children.at(i).toggleAttr(key, force);
108
+ this.childNodes.at(i).toggleAttr(key, force);
63
109
  }
64
110
  };
65
111
 
@@ -1,23 +1,31 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..');
3
+ const Parser = require('..'),
4
+ Token = require('../src');
4
5
 
5
6
  /**
7
+ * 不可增删子节点的类
6
8
  * @template T
7
- * @param {T} constructor
9
+ * @param {T} Constructor 基类
8
10
  * @returns {T}
9
11
  */
10
- const fixedToken = ct => class extends ct {
12
+ const fixedToken = Constructor => class extends Constructor {
11
13
  static fixed = true;
12
14
 
15
+ /**
16
+ * 移除子节点
17
+ * @throws `Error`
18
+ */
13
19
  removeAt() {
14
20
  throw new Error(`${this.constructor.name} 不可删除元素!`);
15
21
  }
16
22
 
17
23
  /**
18
- * @template {string|Token} T
19
- * @param {T} token
20
- * @param {number} i
24
+ * 插入子节点
25
+ * @template {Token} T
26
+ * @param {T} token 待插入的子节点
27
+ * @param {number} i 插入位置
28
+ * @throws `Error`
21
29
  */
22
30
  insertAt(token, i = this.childNodes.length) {
23
31
  if (!Parser.running) {
package/mixin/hidden.js CHANGED
@@ -1,14 +1,16 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..');
3
+ const Parser = require('..');
4
4
 
5
5
  /**
6
+ * 解析后不可见的类
6
7
  * @template T
7
- * @param {T} constructor
8
+ * @param {T} Constructor 基类
8
9
  * @returns {T}
9
10
  */
10
- const hidden = ct => class extends ct {
11
- text() {
11
+ const hidden = Constructor => class extends Constructor {
12
+ /** 没有可见部分 */
13
+ text() { // eslint-disable-line class-methods-use-this
12
14
  return '';
13
15
  }
14
16
  };
package/mixin/sol.js CHANGED
@@ -1,39 +1,56 @@
1
1
  'use strict';
2
2
 
3
- const /** @type {Parser} */ Parser = require('..'),
3
+ const Parser = require('..'),
4
4
  Token = require('../src');
5
5
 
6
6
  /**
7
+ * 只能位于行首的类
7
8
  * @template T
8
- * @param {T} constructor
9
+ * @param {T} Constructor 基类
9
10
  * @returns {T}
10
11
  */
11
- const sol = ct => class extends ct {
12
- /** @this {Token} */
12
+ const sol = Constructor => class extends Constructor {
13
+ /**
14
+ * 在前方插入newline
15
+ * @this {Token}
16
+ */
13
17
  prependNewLine() {
14
18
  const {previousVisibleSibling = '', parentNode} = this;
15
- return (previousVisibleSibling || parentNode?.type !== 'root') && !String(previousVisibleSibling).endsWith('\n')
19
+ return (previousVisibleSibling || parentNode?.type !== 'root') && String(previousVisibleSibling).at(-1) !== '\n'
16
20
  ? '\n'
17
21
  : '';
18
22
  }
19
23
 
20
- /** @this {Token} */
24
+ /**
25
+ * 在后方插入newline
26
+ * @this {Token}
27
+ */
21
28
  appendNewLine() {
22
29
  const {nextVisibleSibling = '', parentNode} = this;
23
- return (nextVisibleSibling || parentNode?.type !== 'root') && !String(nextVisibleSibling ?? '').startsWith('\n')
30
+ return (nextVisibleSibling || parentNode?.type !== 'root') && String(nextVisibleSibling ?? '')[0] !== '\n'
24
31
  ? '\n'
25
32
  : '';
26
33
  }
27
34
 
28
- toString(ownLine = false) {
29
- return `${this.prependNewLine()}${super.toString()}${ownLine ? this.appendNewLine() : ''}`;
35
+ /**
36
+ * 还原为wikitext
37
+ * @param {string} selector
38
+ * @param {boolean} ownLine 是否独占一行
39
+ */
40
+ toString(selector, ownLine) {
41
+ return `${this.prependNewLine()}${super.toString(selector)}${ownLine ? this.appendNewLine() : ''}`;
30
42
  }
31
43
 
44
+ /** 获取padding */
32
45
  getPadding() {
33
46
  return this.prependNewLine().length;
34
47
  }
35
48
 
36
- text(ownLine = false) {
49
+ /**
50
+ * 可见部分
51
+ * @param {booean} ownLine 是否独占一行
52
+ */
53
+ text(ownLine) {
37
54
  return `${this.prependNewLine()}${super.text()}${ownLine ? this.appendNewLine() : ''}`;
38
55
  }
39
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikiparser-node",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A Node.js parser for MediaWiki markup with AST",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -18,11 +18,17 @@
18
18
  "url": "git+https://github.com/bhsd-harry/wikiparser-node.git"
19
19
  },
20
20
  "scripts": {
21
- "test": "echo 'Error: no test specified' && exit 1"
21
+ "test": "node test/test.js",
22
+ "real": "node test/real.js"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^17.0.23",
25
- "eslint": "^8.30.0"
26
+ "eslint": "^8.30.0",
27
+ "eslint-plugin-n": "^15.6.0",
28
+ "eslint-plugin-regexp": "^1.11.0",
29
+ "eslint-plugin-unicorn": "^45.0.2",
30
+ "eslint-plugin-jsdoc": "^1.0.0",
31
+ "request": "^2.88.2"
26
32
  },
27
33
  "engines": {
28
34
  "node": "^18.4.0"
@@ -1,35 +1,42 @@
1
1
  'use strict';
2
2
 
3
3
  const {removeComment} = require('../util/string'),
4
- /** @type {Parser} */ Parser = require('..');
4
+ Parser = require('..');
5
5
 
6
6
  /**
7
- * @param {string} text
7
+ * 解析花括号
8
+ * @param {string} text wikitext
8
9
  * @param {accum} accum
10
+ * @throws TranscludeToken.constructor()
9
11
  */
10
12
  const parseBrackets = (text, config = Parser.getConfig(), accum = []) => {
11
- const source = '(?<=^(?:\x00\\d+c\x7f)*)={1,6}|\\[\\[|{{2,}|-{(?!{)',
13
+ const source = '^(\0\\d+c\x7F)*={1,6}|\\[\\[|\\{{2,}|-\\{(?!\\{)',
12
14
  /** @type {BracketExecArray[]} */ stack = [],
13
- closes = {'=': '\n', '{': '}{2,}|\\|', '-': '}-', '[': ']]'},
15
+ closes = {'=': '\n', '{': '\\}{2,}|\\|', '-': '\\}-', '[': '\\]\\]'},
14
16
  /** @type {Record<string, string>} */ marks = {'!': '!', '!!': '+', '(!': '{', '!)': '}', '!-': '-', '=': '~'};
15
- let regex = new RegExp(source, 'gm'),
17
+ let regex = new RegExp(source, 'gmu'),
16
18
  /** @type {BracketExecArray} */ mt = regex.exec(text),
17
19
  moreBraces = text.includes('}}'),
18
20
  lastIndex;
19
21
  while (mt || lastIndex <= text.length && stack.at(-1)?.[0]?.[0] === '=') {
22
+ if (mt?.[1]) {
23
+ const [, {length}] = mt;
24
+ mt[0] = mt[0].slice(length);
25
+ mt.index += length;
26
+ }
20
27
  const {0: syntax, index: curIndex} = mt ?? {0: '\n', index: text.length},
21
28
  /** @type {BracketExecArray} */ top = stack.pop() ?? {},
22
- {0: open, index, parts} = top,
23
- innerEqual = syntax === '=' && top.findEqual;
24
- if ([']]', '}-'].includes(syntax)) { // 情形1:闭合内链或转换
29
+ {0: open, index, parts, findEqual: topFindEqual, pos: topPos} = top,
30
+ innerEqual = syntax === '=' && topFindEqual;
31
+ if (syntax === ']]' || syntax === '}-') { // 情形1:闭合内链或转换
25
32
  lastIndex = curIndex + 2;
26
33
  } else if (syntax === '\n') { // 情形2:闭合标题
27
34
  lastIndex = curIndex + 1;
28
35
  const {pos, findEqual} = stack.at(-1) ?? {};
29
36
  if (!pos || findEqual || removeComment(text.slice(pos, index)) !== '') {
30
- const rmt = text.slice(index, curIndex).match(/^(={1,6})(.+)\1((?:\s|\x00\d+c\x7f)*)$/);
37
+ const rmt = /^(={1,6})(.+)\1((?:\s|\0\d+c\x7F)*)$/u.exec(text.slice(index, curIndex));
31
38
  if (rmt) {
32
- text = `${text.slice(0, index)}\x00${accum.length}h\x7f${text.slice(curIndex)}`;
39
+ text = `${text.slice(0, index)}\0${accum.length}h\x7F${text.slice(curIndex)}`;
33
40
  lastIndex = index + 4 + String(accum.length).length;
34
41
  const HeadingToken = require('../src/heading');
35
42
  new HeadingToken(rmt[1].length, rmt.slice(2), config, accum);
@@ -37,7 +44,7 @@ const parseBrackets = (text, config = Parser.getConfig(), accum = []) => {
37
44
  }
38
45
  } else if (syntax === '|' || innerEqual) { // 情形3:模板内部,含行首单个'='
39
46
  lastIndex = curIndex + 1;
40
- parts.at(-1).push(text.slice(top.pos, curIndex));
47
+ parts.at(-1).push(text.slice(topPos, curIndex));
41
48
  if (syntax === '|') {
42
49
  parts.push([]);
43
50
  }
@@ -49,9 +56,8 @@ const parseBrackets = (text, config = Parser.getConfig(), accum = []) => {
49
56
  rest = open.length - close.length,
50
57
  {length} = accum;
51
58
  lastIndex = curIndex + close.length; // 这不是最终的lastIndex
52
- parts.at(-1).push(text.slice(top.pos, curIndex));
53
- /* 标记{{!}}等 */
54
- const ch = close.length === 2 ? marks[removeComment(parts[0][0])] ?? 't' : 't';
59
+ parts.at(-1).push(text.slice(topPos, curIndex));
60
+ const ch = close.length === 2 ? marks[removeComment(parts[0][0])] ?? 't' : 't'; // 标记{{!}}等
55
61
  let skip = false;
56
62
  if (close.length === 3) {
57
63
  const ArgToken = require('../src/arg');
@@ -70,8 +76,7 @@ const parseBrackets = (text, config = Parser.getConfig(), accum = []) => {
70
76
  }
71
77
  }
72
78
  if (!skip) {
73
- /* 标记{{!}}结束 */
74
- text = `${text.slice(0, index + rest)}\x00${length}${ch}\x7f${text.slice(lastIndex)}`;
79
+ text = `${text.slice(0, index + rest)}\0${length}${ch}\x7F${text.slice(lastIndex)}`;
75
80
  lastIndex = index + rest + 3 + String(length).length;
76
81
  if (rest > 1) {
77
82
  stack.push({0: open.slice(0, rest), index, pos: index + rest, parts: [[]]});
@@ -96,7 +101,7 @@ const parseBrackets = (text, config = Parser.getConfig(), accum = []) => {
96
101
  regex = new RegExp(source + (curTop
97
102
  ? `|${closes[curTop[0][0]]}${curTop.findEqual ? '|=' : ''}`
98
103
  : ''
99
- ), 'gm');
104
+ ), 'gmu');
100
105
  regex.lastIndex = lastIndex;
101
106
  mt = regex.exec(text);
102
107
  }