wikiparser-node 0.3.1 → 0.5.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 (80) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +13 -17
  3. package/config/llwiki.json +11 -79
  4. package/config/moegirl.json +7 -1
  5. package/config/zhwiki.json +1269 -0
  6. package/index.js +130 -97
  7. package/lib/element.js +410 -518
  8. package/lib/node.js +493 -115
  9. package/lib/ranges.js +27 -19
  10. package/lib/text.js +175 -0
  11. package/lib/title.js +14 -6
  12. package/mixin/attributeParent.js +70 -24
  13. package/mixin/fixedToken.js +18 -10
  14. package/mixin/hidden.js +6 -4
  15. package/mixin/sol.js +39 -12
  16. package/package.json +17 -4
  17. package/parser/brackets.js +18 -18
  18. package/parser/commentAndExt.js +16 -14
  19. package/parser/converter.js +14 -13
  20. package/parser/externalLinks.js +12 -11
  21. package/parser/hrAndDoubleUnderscore.js +24 -14
  22. package/parser/html.js +8 -7
  23. package/parser/links.js +13 -13
  24. package/parser/list.js +12 -11
  25. package/parser/magicLinks.js +11 -10
  26. package/parser/quotes.js +6 -5
  27. package/parser/selector.js +175 -0
  28. package/parser/table.js +31 -24
  29. package/src/arg.js +91 -43
  30. package/src/atom/hidden.js +5 -2
  31. package/src/atom/index.js +17 -9
  32. package/src/attribute.js +210 -101
  33. package/src/converter.js +78 -43
  34. package/src/converterFlags.js +104 -45
  35. package/src/converterRule.js +136 -78
  36. package/src/extLink.js +81 -27
  37. package/src/gallery.js +63 -20
  38. package/src/heading.js +58 -20
  39. package/src/html.js +138 -48
  40. package/src/imageParameter.js +93 -58
  41. package/src/index.js +314 -186
  42. package/src/link/category.js +22 -54
  43. package/src/link/file.js +83 -32
  44. package/src/link/galleryImage.js +21 -7
  45. package/src/link/index.js +170 -81
  46. package/src/magicLink.js +64 -14
  47. package/src/nowiki/comment.js +36 -10
  48. package/src/nowiki/dd.js +37 -22
  49. package/src/nowiki/doubleUnderscore.js +21 -7
  50. package/src/nowiki/hr.js +11 -7
  51. package/src/nowiki/index.js +16 -9
  52. package/src/nowiki/list.js +2 -2
  53. package/src/nowiki/noinclude.js +8 -4
  54. package/src/nowiki/quote.js +38 -7
  55. package/src/onlyinclude.js +24 -7
  56. package/src/parameter.js +102 -62
  57. package/src/syntax.js +23 -20
  58. package/src/table/index.js +282 -174
  59. package/src/table/td.js +112 -61
  60. package/src/table/tr.js +135 -74
  61. package/src/tagPair/ext.js +30 -23
  62. package/src/tagPair/include.js +26 -11
  63. package/src/tagPair/index.js +72 -29
  64. package/src/transclude.js +235 -127
  65. package/tool/index.js +42 -32
  66. package/util/debug.js +21 -18
  67. package/util/diff.js +76 -0
  68. package/util/lint.js +40 -0
  69. package/util/string.js +56 -26
  70. package/.eslintrc.json +0 -319
  71. package/errors/README +0 -1
  72. package/jsconfig.json +0 -7
  73. package/printed/README +0 -1
  74. package/typings/element.d.ts +0 -28
  75. package/typings/index.d.ts +0 -52
  76. package/typings/node.d.ts +0 -23
  77. package/typings/parser.d.ts +0 -9
  78. package/typings/table.d.ts +0 -14
  79. package/typings/token.d.ts +0 -22
  80. package/typings/tool.d.ts +0 -10
package/src/heading.js CHANGED
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fixedToken = require('../mixin/fixedToken'),
3
+ const {generateForSelf} = require('../util/lint'),
4
+ fixedToken = require('../mixin/fixedToken'),
4
5
  sol = require('../mixin/sol'),
5
- /** @type {Parser} */ Parser = require('..'),
6
+ Parser = require('..'),
6
7
  Token = require('.');
7
8
 
8
9
  /**
@@ -12,9 +13,14 @@ const fixedToken = require('../mixin/fixedToken'),
12
13
  class HeadingToken extends fixedToken(sol(Token)) {
13
14
  type = 'heading';
14
15
 
16
+ /** 内部wikitext */
17
+ get innerText() {
18
+ return this.firstChild.text();
19
+ }
20
+
15
21
  /**
16
- * @param {number} level
17
- * @param {string[]} input
22
+ * @param {number} level 标题层级
23
+ * @param {string[]} input 标题文字
18
24
  * @param {accum} accum
19
25
  */
20
26
  constructor(level, input, config = Parser.getConfig(), accum = []) {
@@ -23,57 +29,89 @@ class HeadingToken extends fixedToken(sol(Token)) {
23
29
  const token = new Token(input[0], config, true, accum);
24
30
  token.type = 'heading-title';
25
31
  token.setAttribute('name', this.name).setAttribute('stage', 2);
26
- const SyntaxToken = require('./syntax'),
27
- trail = new SyntaxToken(input[1], /^[^\S\n]*$/, 'heading-trail', config, accum, {
28
- 'Stage-1': ':', '!ExtToken': '',
29
- });
32
+ const SyntaxToken = require('./syntax');
33
+ const trail = new SyntaxToken(input[1], /^[^\S\n]*$/u, 'heading-trail', config, accum, {
34
+ 'Stage-1': ':', '!ExtToken': '',
35
+ });
30
36
  this.append(token, trail);
31
37
  }
32
38
 
39
+ /** @override */
33
40
  cloneNode() {
34
- const [title, trail] = this.cloneChildren(),
41
+ const [title, trail] = this.cloneChildNodes(),
35
42
  token = Parser.run(() => new HeadingToken(Number(this.name), [], this.getAttribute('config')));
36
- token.firstElementChild.safeReplaceWith(title);
37
- token.lastElementChild.safeReplaceWith(trail);
43
+ token.firsthild.safeReplaceWith(title);
44
+ token.lastChild.safeReplaceWith(trail);
38
45
  return token;
39
46
  }
40
47
 
41
- /** @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}} */
42
- toString() {
48
+ /**
49
+ * @override
50
+ * @this {{prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'} & HeadingToken}
51
+ * @param {string} selector
52
+ */
53
+ toString(selector) {
43
54
  const equals = '='.repeat(Number(this.name));
44
- return `${this.prependNewLine()}${equals}${
45
- this.firstElementChild.toString()
46
- }${equals}${this.lastElementChild.toString()}${this.appendNewLine()}`;
55
+ return selector && this.matches(selector)
56
+ ? ''
57
+ : `${this.prependNewLine()}${equals}${
58
+ this.firstChild.toString(selector)
59
+ }${equals}${this.lastChild.toString(selector)}${this.appendNewLine()}`;
47
60
  }
48
61
 
62
+ /** @override */
49
63
  getPadding() {
50
64
  return super.getPadding() + Number(this.name);
51
65
  }
52
66
 
67
+ /** @override */
53
68
  getGaps() {
54
69
  return Number(this.name);
55
70
  }
56
71
 
72
+ /** @override */
73
+ print() {
74
+ const equals = '='.repeat(Number(this.name));
75
+ return super.print({pre: equals, sep: equals});
76
+ }
77
+
57
78
  /**
79
+ * @override
80
+ * @param {number} start 起始位置
81
+ */
82
+ lint(start = 0) {
83
+ const errors = super.lint(start);
84
+ if (this.name === '1') {
85
+ errors.push(generateForSelf(this, this.getRootNode().posFromIndex(start), '<h1>'));
86
+ }
87
+ return errors;
88
+ }
89
+
90
+ /**
91
+ * @override
58
92
  * @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}}
59
93
  * @returns {string}
60
94
  */
61
95
  text() {
62
96
  const equals = '='.repeat(Number(this.name));
63
- return `${this.prependNewLine()}${equals}${this.firstElementChild.text()}${equals}${this.appendNewLine()}`;
97
+ return `${this.prependNewLine()}${equals}${this.firstChild.text()}${equals}${this.appendNewLine()}`;
64
98
  }
65
99
 
66
- /** @param {number} n */
100
+ /**
101
+ * 设置标题层级
102
+ * @param {number} n 标题层级
103
+ */
67
104
  setLevel(n) {
68
105
  if (typeof n !== 'number') {
69
106
  this.typeError('setLevel', 'Number');
70
107
  }
71
108
  n = Math.min(Math.max(n, 1), 6);
72
- this.setAttribute('name', String(n)).firstElementChild.setAttribute('name', this.name);
109
+ this.setAttribute('name', String(n)).firstChild.setAttribute('name', this.name);
73
110
  }
74
111
 
112
+ /** 移除标题后的不可见内容 */
75
113
  removeTrail() {
76
- this.lastElementChild.replaceChildren();
114
+ this.lastChild.replaceChildren();
77
115
  }
78
116
  }
79
117
 
package/src/html.js CHANGED
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const {noWrap} = require('../util/string'),
4
+ {generateForSelf} = require('../util/lint'),
4
5
  fixedToken = require('../mixin/fixedToken'),
5
6
  attributeParent = require('../mixin/attributeParent'),
6
- /** @type {Parser} */ Parser = require('..'),
7
+ Parser = require('..'),
7
8
  Token = require('.');
8
9
 
9
10
  /**
@@ -12,57 +13,136 @@ const {noWrap} = require('../util/string'),
12
13
  */
13
14
  class HtmlToken extends attributeParent(fixedToken(Token)) {
14
15
  type = 'html';
15
- closing;
16
- selfClosing;
16
+ #closing;
17
+ #selfClosing;
17
18
  #tag;
18
19
 
20
+ /** getter */
21
+ get closing() {
22
+ return this.#closing;
23
+ }
24
+
25
+ /** @throws `Error` 自闭合标签或空标签 */
26
+ set closing(value) {
27
+ if (!value) {
28
+ this.#closing = false;
29
+ return;
30
+ } else if (this.#selfClosing) {
31
+ throw new Error(`这是一个自闭合标签!`);
32
+ }
33
+ const {html: [,, tags]} = this.getAttribute('config');
34
+ if (tags.includes(this.name)) {
35
+ throw new Error(`这是一个空标签!`);
36
+ }
37
+ this.#closing = true;
38
+ }
39
+
40
+ /** getter */
41
+ get selfClosing() {
42
+ return this.#selfClosing;
43
+ }
44
+
45
+ /** @throws `Error` 闭合标签或无效自闭合标签 */
46
+ set selfClosing(value) {
47
+ if (!value) {
48
+ this.#selfClosing = false;
49
+ return;
50
+ } else if (this.#closing) {
51
+ throw new Error('这是一个闭合标签!');
52
+ }
53
+ const {html: [tags]} = this.getAttribute('config');
54
+ if (tags.includes(this.name)) {
55
+ throw new Error(`<${this.name}>标签自闭合无效!`);
56
+ }
57
+ this.#selfClosing = true;
58
+ }
59
+
19
60
  /**
20
- * @param {string} name
21
- * @param {AttributeToken} attr
22
- * @param {boolean} closing
23
- * @param {boolean} selfClosing
61
+ * @param {string} name 标签名
62
+ * @param {AttributeToken} attr 标签属性
63
+ * @param {boolean} closing 是否闭合
64
+ * @param {boolean} selfClosing 是否自封闭
24
65
  * @param {accum} accum
25
66
  */
26
67
  constructor(name, attr, closing, selfClosing, config = Parser.getConfig(), accum = []) {
27
68
  super(undefined, config, true, accum);
28
69
  this.appendChild(attr);
29
70
  this.setAttribute('name', name.toLowerCase());
30
- this.closing = closing;
31
- this.selfClosing = selfClosing;
71
+ this.#closing = closing;
72
+ this.#selfClosing = selfClosing;
32
73
  this.#tag = name;
33
74
  }
34
75
 
76
+ /** @override */
35
77
  cloneNode() {
36
- const [attr] = this.cloneChildren(),
78
+ const [attr] = this.cloneChildNodes(),
37
79
  config = this.getAttribute('config');
38
- return Parser.run(() => new HtmlToken(this.#tag, attr, this.closing, this.selfClosing, config));
80
+ return Parser.run(() => new HtmlToken(this.#tag, attr, this.#closing, this.#selfClosing, config));
39
81
  }
40
82
 
41
83
  /**
84
+ * @override
42
85
  * @template {string} T
43
- * @param {T} key
86
+ * @param {T} key 属性键
44
87
  * @returns {TokenAttribute<T>}
45
88
  */
46
89
  getAttribute(key) {
47
- if (key === 'tag') {
48
- return this.#tag;
49
- }
50
- return super.getAttribute(key);
90
+ return key === 'tag' ? this.#tag : super.getAttribute(key);
51
91
  }
52
92
 
53
- toString() {
54
- return `<${this.closing ? '/' : ''}${this.#tag}${super.toString()}${this.selfClosing ? '/' : ''}>`;
93
+ /**
94
+ * @override
95
+ * @param {string} selector
96
+ */
97
+ toString(selector) {
98
+ return selector && this.matches(selector)
99
+ ? ''
100
+ : `<${this.#closing ? '/' : ''}${this.#tag}${super.toString(selector)}${this.#selfClosing ? '/' : ''}>`;
55
101
  }
56
102
 
103
+ /** @override */
57
104
  getPadding() {
58
- return this.#tag.length + (this.closing ? 2 : 1);
105
+ return this.#tag.length + (this.#closing ? 2 : 1);
106
+ }
107
+
108
+ /** @override */
109
+ print() {
110
+ return super.print({
111
+ pre: `&lt;${this.#closing ? '/' : ''}${this.#tag}`, post: `${this.#selfClosing ? '/' : ''}&gt;`,
112
+ });
113
+ }
114
+
115
+ /**
116
+ * @override
117
+ * @param {number} start 起始位置
118
+ */
119
+ lint(start = 0) {
120
+ const errors = super.lint(start);
121
+ let /** @type {LintError} */ refError;
122
+ if (this.name === 'h1' && !this.#closing) {
123
+ refError = generateForSelf(this, this.getRootNode().posFromIndex(start), '<h1>');
124
+ errors.push(refError);
125
+ }
126
+ try {
127
+ this.findMatchingTag();
128
+ } catch ({message: errorMsg}) {
129
+ const [message] = errorMsg.split(':');
130
+ refError ||= generateForSelf(this, this.getRootNode().posFromIndex(start), '');
131
+ errors.push({...refError, message, severity: message === '未闭合的标签' ? 'warning' : 'error'});
132
+ }
133
+ return errors;
59
134
  }
60
135
 
136
+ /** @override */
61
137
  text() {
62
- return `<${this.closing ? '/' : ''}${this.#tag}${super.text()}${this.selfClosing ? '/' : ''}>`;
138
+ return `<${this.#closing ? '/' : ''}${this.#tag}${super.text()}${this.#selfClosing ? '/' : ''}>`;
63
139
  }
64
140
 
65
- /** @param {string} tag */
141
+ /**
142
+ * 更换标签名
143
+ * @param {string} tag 标签名
144
+ * @throws `RangeError` 非法的HTML标签
145
+ */
66
146
  replaceTag(tag) {
67
147
  const name = tag.toLowerCase();
68
148
  if (!this.getAttribute('config').html.flat().includes(name)) {
@@ -71,27 +151,32 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
71
151
  this.setAttribute('name', name).#tag = tag;
72
152
  }
73
153
 
74
- /** @complexity `n` */
154
+ /**
155
+ * 搜索匹配的标签
156
+ * @complexity `n`
157
+ * @throws `SyntaxError` 同时闭合和自封闭的标签
158
+ * @throws `SyntaxError` 无效自封闭标签
159
+ * @throws `SyntaxError` 未闭合的标签
160
+ */
75
161
  findMatchingTag() {
76
162
  const {html} = this.getAttribute('config'),
77
- {name, parentElement, closing, selfClosing} = this,
78
- string = noWrap(this.toString());
79
- if (closing && selfClosing) {
163
+ {name: tagName, parentNode} = this,
164
+ string = noWrap(String(this));
165
+ if (this.#closing && this.#selfClosing) {
80
166
  throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
81
- } else if (html[2].includes(name) || selfClosing && html[1].includes(name)) { // 自封闭标签
167
+ } else if (html[2].includes(tagName) || this.#selfClosing && html[1].includes(tagName)) { // 自封闭标签
82
168
  return this;
83
- } else if (selfClosing && html[0].includes(name)) {
169
+ } else if (this.#selfClosing && html[0].includes(tagName)) {
84
170
  throw new SyntaxError(`无效自封闭标签:${string}`);
85
- } else if (!parentElement) {
86
- return;
171
+ } else if (!parentNode) {
172
+ return undefined;
87
173
  }
88
- const {children} = parentElement,
89
- i = children.indexOf(this),
90
- selector = `html#${name}`,
91
- siblings = closing
92
- ? children.slice(0, i).reverse().filter(child => child.matches(selector))
93
- : children.slice(i + 1).filter(child => child.matches(selector));
94
- let imbalance = closing ? -1 : 1;
174
+ const {childNodes} = parentNode,
175
+ i = childNodes.indexOf(this),
176
+ siblings = this.#closing
177
+ ? childNodes.slice(0, i).reverse().filter(({type, name}) => type === 'html' && name === tagName)
178
+ : childNodes.slice(i + 1).filter(({type, name}) => type === 'html' && name === tagName);
179
+ let imbalance = this.#closing ? -1 : 1;
95
180
  for (const token of siblings) {
96
181
  if (token.closing) {
97
182
  imbalance--;
@@ -102,35 +187,40 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
102
187
  return token;
103
188
  }
104
189
  }
105
- throw new SyntaxError(`未${closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
190
+ throw new SyntaxError(`未${this.#closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
106
191
  }
107
192
 
193
+ /** 局部闭合 */
108
194
  #localMatch() {
109
- this.selfClosing = false;
195
+ this.#selfClosing = false;
110
196
  const root = Parser.parse(`</${this.name}>`, false, 3, this.getAttribute('config'));
111
197
  this.after(root.firstChild);
112
198
  }
113
199
 
114
- /** @complexity `n` */
200
+ /**
201
+ * 修复无效自封闭标签
202
+ * @complexity `n`
203
+ * @throws `Error` 无法修复无效自封闭标签
204
+ */
115
205
  fix() {
116
206
  const config = this.getAttribute('config'),
117
- {parentElement, selfClosing, name, firstElementChild} = this;
118
- if (!parentElement || !selfClosing || !config.html[0].includes(name)) {
207
+ {parentNode, name: tagName, firstChild} = this;
208
+ if (!parentNode || !this.#selfClosing || !config.html[0].includes(tagName)) {
119
209
  return;
120
- } else if (firstElementChild.text().trim()) {
210
+ } else if (firstChild.text().trim()) {
121
211
  this.#localMatch();
122
212
  return;
123
213
  }
124
- const {children} = parentElement,
125
- i = children.indexOf(this),
214
+ const {childNodes} = parentNode,
215
+ i = childNodes.indexOf(this),
126
216
  /** @type {HtmlToken[]} */
127
- prevSiblings = children.slice(0, i).filter(child => child.matches(`html#${name}`)),
217
+ prevSiblings = childNodes.slice(0, i).filter(({type, name}) => type === 'html' && name === tagName),
128
218
  imbalance = prevSiblings.reduce((acc, {closing}) => acc + (closing ? 1 : -1), 0);
129
219
  if (imbalance < 0) {
130
- this.selfClosing = false;
131
- this.closing = true;
220
+ this.#selfClosing = false;
221
+ this.#closing = true;
132
222
  } else {
133
- Parser.warn('无法修复无效自封闭标签', noWrap(this.toString()));
223
+ Parser.warn('无法修复无效自封闭标签', noWrap(String(this)));
134
224
  throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
135
225
  }
136
226
  }