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/src/gallery.js CHANGED
@@ -1,28 +1,28 @@
1
1
  'use strict';
2
2
 
3
- const {text} = require('../util/string'),
4
- /** @type {Parser} */ Parser = require('..'),
3
+ const Parser = require('..'),
5
4
  Token = require('.'),
6
- GalleryImageToken = require('./link/galleryImage');
5
+ GalleryImageToken = require('./link/galleryImage'),
6
+ HiddenToken = require('./atom/hidden');
7
7
 
8
8
  /**
9
9
  * gallery标签
10
- * @classdesc `{childNodes: (string|FileToken)[]]}`
10
+ * @classdesc `{childNodes: ...(GalleryImageToken|HiddenToken|AstText)}`
11
11
  */
12
12
  class GalleryToken extends Token {
13
13
  type = 'ext-inner';
14
14
  name = 'gallery';
15
15
 
16
16
  /**
17
- * @param {string} inner
17
+ * @param {string} inner 标签内部wikitext
18
18
  * @param {accum} accum
19
19
  */
20
20
  constructor(inner, config = Parser.getConfig(), accum = []) {
21
- super(undefined, config, true, accum, {String: ':', GalleryImageToken: ':'});
21
+ super(undefined, config, true, accum, {AstText: ':', GalleryImageToken: ':'});
22
22
  for (const line of inner?.split('\n') ?? []) {
23
- const matches = line.match(/^([^|]+)(?:\|(.*))?/);
23
+ const matches = /^([^|]+)(?:\|(.*))?/u.exec(line);
24
24
  if (!matches) {
25
- this.appendChild(line);
25
+ this.appendChild(line.trim() ? new HiddenToken(line, undefined, config, [], {AstText: ':'}) : line);
26
26
  continue;
27
27
  }
28
28
  const [, file, alt] = matches;
@@ -32,31 +32,58 @@ class GalleryToken extends Token {
32
32
  } catch {
33
33
  title = this.normalizeTitle(file, 6, true);
34
34
  }
35
- if (!title.valid) {
36
- this.appendChild(line);
37
- } else {
35
+ if (title.valid) {
38
36
  this.appendChild(new GalleryImageToken(file, alt, title, config, accum));
37
+ } else {
38
+ this.appendChild(new HiddenToken(line, undefined, config, [], {AstText: ':'}));
39
39
  }
40
40
  }
41
41
  }
42
42
 
43
+ /** @override */
43
44
  cloneNode() {
44
- const cloned = this.cloneChildren(),
45
+ const cloned = this.cloneChildNodes(),
45
46
  token = Parser.run(() => new GalleryToken(undefined, this.getAttribute('config')));
46
47
  token.append(...cloned);
47
48
  return token;
48
49
  }
49
50
 
50
- toString() {
51
- return super.toString('\n');
51
+ /**
52
+ * @override
53
+ * @param {string} selector
54
+ */
55
+ toString(selector) {
56
+ return super.toString(selector, '\n');
52
57
  }
53
58
 
59
+ /** @override */
54
60
  getGaps() {
55
61
  return 1;
56
62
  }
57
63
 
64
+ /** @override */
58
65
  text() {
59
- return text(this.children, '\n');
66
+ return super.text('\n').replaceAll(/\n\s*\n/gu, '\n');
67
+ }
68
+
69
+ /**
70
+ * 插入图片
71
+ * @param {string} file 图片文件名
72
+ * @param {number} i 插入位置
73
+ * @throws `SyntaxError` 非法的文件名
74
+ */
75
+ insertImage(file, i = this.childNodes.length) {
76
+ let title;
77
+ try {
78
+ title = this.normalizeTitle(decodeURIComponent(file), 6, true);
79
+ } catch {
80
+ title = this.normalizeTitle(file, 6, true);
81
+ }
82
+ if (!title.valid) {
83
+ throw new SyntaxError(`非法的文件名:${file}`);
84
+ }
85
+ const token = Parser.run(() => new GalleryImageToken(file, undefined, title, this.getAttribute('config')));
86
+ return this.insertAt(token, i);
60
87
  }
61
88
  }
62
89
 
package/src/heading.js CHANGED
@@ -2,19 +2,24 @@
2
2
 
3
3
  const fixedToken = require('../mixin/fixedToken'),
4
4
  sol = require('../mixin/sol'),
5
- /** @type {Parser} */ Parser = require('..'),
5
+ Parser = require('..'),
6
6
  Token = require('.');
7
7
 
8
8
  /**
9
9
  * 章节标题
10
- * @classdesc `{childNodes: [Token, HiddenToken]}`
10
+ * @classdesc `{childNodes: [Token, SyntaxToken]}`
11
11
  */
12
12
  class HeadingToken extends fixedToken(sol(Token)) {
13
13
  type = 'heading';
14
14
 
15
+ /** 内部wikitext */
16
+ get innerText() {
17
+ return this.firstElementChild.text();
18
+ }
19
+
15
20
  /**
16
- * @param {number} level
17
- * @param {string[]} input
21
+ * @param {number} level 标题层级
22
+ * @param {string[]} input 标题文字
18
23
  * @param {accum} accum
19
24
  */
20
25
  constructor(level, input, config = Parser.getConfig(), accum = []) {
@@ -23,38 +28,48 @@ class HeadingToken extends fixedToken(sol(Token)) {
23
28
  const token = new Token(input[0], config, true, accum);
24
29
  token.type = 'heading-title';
25
30
  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
- });
31
+ const SyntaxToken = require('./syntax');
32
+ const trail = new SyntaxToken(input[1], /^[^\S\n]*$/u, 'heading-trail', config, accum, {
33
+ 'Stage-1': ':', '!ExtToken': '',
34
+ });
30
35
  this.append(token, trail);
31
36
  }
32
37
 
38
+ /** @override */
33
39
  cloneNode() {
34
- const [title, trail] = this.cloneChildren(),
40
+ const [title, trail] = this.cloneChildNodes(),
35
41
  token = Parser.run(() => new HeadingToken(Number(this.name), [], this.getAttribute('config')));
36
42
  token.firstElementChild.safeReplaceWith(title);
37
43
  token.lastElementChild.safeReplaceWith(trail);
38
44
  return token;
39
45
  }
40
46
 
41
- /** @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}} */
42
- toString() {
47
+ /**
48
+ * @override
49
+ * @this {{prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'} & HeadingToken}
50
+ * @param {string} selector
51
+ */
52
+ toString(selector) {
43
53
  const equals = '='.repeat(Number(this.name));
44
- return `${this.prependNewLine()}${equals}${
45
- this.firstElementChild.toString()
46
- }${equals}${this.lastElementChild.toString()}${this.appendNewLine()}`;
54
+ return selector && this.matches(selector)
55
+ ? ''
56
+ : `${this.prependNewLine()}${equals}${
57
+ this.firstElementChild.toString(selector)
58
+ }${equals}${this.lastElementChild.toString(selector)}${this.appendNewLine()}`;
47
59
  }
48
60
 
61
+ /** @override */
49
62
  getPadding() {
50
63
  return super.getPadding() + Number(this.name);
51
64
  }
52
65
 
66
+ /** @override */
53
67
  getGaps() {
54
68
  return Number(this.name);
55
69
  }
56
70
 
57
71
  /**
72
+ * @override
58
73
  * @this {HeadingToken & {prependNewLine(): ''|'\n', appendNewLine(): ''|'\n'}}
59
74
  * @returns {string}
60
75
  */
@@ -63,7 +78,10 @@ class HeadingToken extends fixedToken(sol(Token)) {
63
78
  return `${this.prependNewLine()}${equals}${this.firstElementChild.text()}${equals}${this.appendNewLine()}`;
64
79
  }
65
80
 
66
- /** @param {number} n */
81
+ /**
82
+ * 设置标题层级
83
+ * @param {number} n 标题层级
84
+ */
67
85
  setLevel(n) {
68
86
  if (typeof n !== 'number') {
69
87
  this.typeError('setLevel', 'Number');
@@ -72,6 +90,7 @@ class HeadingToken extends fixedToken(sol(Token)) {
72
90
  this.setAttribute('name', String(n)).firstElementChild.setAttribute('name', this.name);
73
91
  }
74
92
 
93
+ /** 移除标题后的不可见内容 */
75
94
  removeTrail() {
76
95
  this.lastElementChild.replaceChildren();
77
96
  }
package/src/html.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const {noWrap} = require('../util/string'),
4
4
  fixedToken = require('../mixin/fixedToken'),
5
5
  attributeParent = require('../mixin/attributeParent'),
6
- /** @type {Parser} */ Parser = require('..'),
6
+ Parser = require('..'),
7
7
  Token = require('.');
8
8
 
9
9
  /**
@@ -12,57 +12,108 @@ const {noWrap} = require('../util/string'),
12
12
  */
13
13
  class HtmlToken extends attributeParent(fixedToken(Token)) {
14
14
  type = 'html';
15
- closing;
16
- selfClosing;
15
+ #closing;
16
+ #selfClosing;
17
17
  #tag;
18
18
 
19
+ /** getter */
20
+ get closing() {
21
+ return this.#closing;
22
+ }
23
+
24
+ /** @throws `Error` 自闭合标签或空标签 */
25
+ set closing(value) {
26
+ if (!value) {
27
+ this.#closing = false;
28
+ return;
29
+ } else if (this.#selfClosing) {
30
+ throw new Error(`这是一个自闭合标签!`);
31
+ }
32
+ const {html: [,, tags]} = this.getAttribute('config');
33
+ if (tags.includes(this.name)) {
34
+ throw new Error(`这是一个空标签!`);
35
+ }
36
+ this.#closing = true;
37
+ }
38
+
39
+ /** getter */
40
+ get selfClosing() {
41
+ return this.#selfClosing;
42
+ }
43
+
44
+ /** @throws `Error` 闭合标签或无效自闭合标签 */
45
+ set selfClosing(value) {
46
+ if (!value) {
47
+ this.#selfClosing = false;
48
+ return;
49
+ } else if (this.#closing) {
50
+ throw new Error('这是一个闭合标签!');
51
+ }
52
+ const {html: [tags]} = this.getAttribute('config');
53
+ if (tags.includes(this.name)) {
54
+ throw new Error(`<${this.name}>标签自闭合无效!`);
55
+ }
56
+ this.#selfClosing = true;
57
+ }
58
+
19
59
  /**
20
- * @param {string} name
21
- * @param {AttributeToken} attr
22
- * @param {boolean} closing
23
- * @param {boolean} selfClosing
60
+ * @param {string} name 标签名
61
+ * @param {AttributeToken} attr 标签属性
62
+ * @param {boolean} closing 是否闭合
63
+ * @param {boolean} selfClosing 是否自封闭
24
64
  * @param {accum} accum
25
65
  */
26
66
  constructor(name, attr, closing, selfClosing, config = Parser.getConfig(), accum = []) {
27
67
  super(undefined, config, true, accum);
28
68
  this.appendChild(attr);
29
69
  this.setAttribute('name', name.toLowerCase());
30
- this.closing = closing;
31
- this.selfClosing = selfClosing;
70
+ this.#closing = closing;
71
+ this.#selfClosing = selfClosing;
32
72
  this.#tag = name;
33
73
  }
34
74
 
75
+ /** @override */
35
76
  cloneNode() {
36
- const [attr] = this.cloneChildren(),
77
+ const [attr] = this.cloneChildNodes(),
37
78
  config = this.getAttribute('config');
38
- return Parser.run(() => new HtmlToken(this.#tag, attr, this.closing, this.selfClosing, config));
79
+ return Parser.run(() => new HtmlToken(this.#tag, attr, this.#closing, this.#selfClosing, config));
39
80
  }
40
81
 
41
82
  /**
83
+ * @override
42
84
  * @template {string} T
43
- * @param {T} key
85
+ * @param {T} key 属性键
44
86
  * @returns {TokenAttribute<T>}
45
87
  */
46
88
  getAttribute(key) {
47
- if (key === 'tag') {
48
- return this.#tag;
49
- }
50
- return super.getAttribute(key);
89
+ return key === 'tag' ? this.#tag : super.getAttribute(key);
51
90
  }
52
91
 
53
- toString() {
54
- return `<${this.closing ? '/' : ''}${this.#tag}${super.toString()}${this.selfClosing ? '/' : ''}>`;
92
+ /**
93
+ * @override
94
+ * @param {string} selector
95
+ */
96
+ toString(selector) {
97
+ return selector && this.matches(selector)
98
+ ? ''
99
+ : `<${this.#closing ? '/' : ''}${this.#tag}${super.toString(selector)}${this.#selfClosing ? '/' : ''}>`;
55
100
  }
56
101
 
102
+ /** @override */
57
103
  getPadding() {
58
- return this.#tag.length + (this.closing ? 2 : 1);
104
+ return this.#tag.length + (this.#closing ? 2 : 1);
59
105
  }
60
106
 
107
+ /** @override */
61
108
  text() {
62
- return `<${this.closing ? '/' : ''}${this.#tag}${super.text()}${this.selfClosing ? '/' : ''}>`;
109
+ return `<${this.#closing ? '/' : ''}${this.#tag}${super.text()}${this.#selfClosing ? '/' : ''}>`;
63
110
  }
64
111
 
65
- /** @param {string} tag */
112
+ /**
113
+ * 更换标签名
114
+ * @param {string} tag 标签名
115
+ * @throws `RangeError` 非法的HTML标签
116
+ */
66
117
  replaceTag(tag) {
67
118
  const name = tag.toLowerCase();
68
119
  if (!this.getAttribute('config').html.flat().includes(name)) {
@@ -71,21 +122,27 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
71
122
  this.setAttribute('name', name).#tag = tag;
72
123
  }
73
124
 
74
- /** @complexity `n` */
125
+ /**
126
+ * 搜索匹配的标签
127
+ * @complexity `n`
128
+ * @throws `SyntaxError` 同时闭合和自封闭的标签
129
+ * @throws `SyntaxError` 无效自封闭标签
130
+ * @throws `SyntaxError` 未闭合的标签
131
+ */
75
132
  findMatchingTag() {
76
133
  const {html} = this.getAttribute('config'),
77
- {name, parentElement, closing, selfClosing} = this,
78
- string = noWrap(this.toString());
134
+ {name, parentNode, closing, selfClosing} = this,
135
+ string = noWrap(String(this));
79
136
  if (closing && selfClosing) {
80
137
  throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
81
138
  } else if (html[2].includes(name) || selfClosing && html[1].includes(name)) { // 自封闭标签
82
139
  return this;
83
140
  } else if (selfClosing && html[0].includes(name)) {
84
141
  throw new SyntaxError(`无效自封闭标签:${string}`);
85
- } else if (!parentElement) {
86
- return;
142
+ } else if (!parentNode) {
143
+ return undefined;
87
144
  }
88
- const {children} = parentElement,
145
+ const {children} = parentNode,
89
146
  i = children.indexOf(this),
90
147
  selector = `html#${name}`,
91
148
  siblings = closing
@@ -105,32 +162,37 @@ class HtmlToken extends attributeParent(fixedToken(Token)) {
105
162
  throw new SyntaxError(`未${closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
106
163
  }
107
164
 
165
+ /** 局部闭合 */
108
166
  #localMatch() {
109
- this.selfClosing = false;
167
+ this.#selfClosing = false;
110
168
  const root = Parser.parse(`</${this.name}>`, false, 3, this.getAttribute('config'));
111
169
  this.after(root.firstChild);
112
170
  }
113
171
 
114
- /** @complexity `n` */
172
+ /**
173
+ * 修复无效自封闭标签
174
+ * @complexity `n`
175
+ * @throws `Error` 无法修复无效自封闭标签
176
+ */
115
177
  fix() {
116
178
  const config = this.getAttribute('config'),
117
- {parentElement, selfClosing, name, firstElementChild} = this;
118
- if (!parentElement || !selfClosing || !config.html[0].includes(name)) {
179
+ {parentNode, name, firstElementChild} = this;
180
+ if (!parentNode || !this.#selfClosing || !config.html[0].includes(name)) {
119
181
  return;
120
182
  } else if (firstElementChild.text().trim()) {
121
183
  this.#localMatch();
122
184
  return;
123
185
  }
124
- const {children} = parentElement,
186
+ const {children} = parentNode,
125
187
  i = children.indexOf(this),
126
188
  /** @type {HtmlToken[]} */
127
189
  prevSiblings = children.slice(0, i).filter(child => child.matches(`html#${name}`)),
128
190
  imbalance = prevSiblings.reduce((acc, {closing}) => acc + (closing ? 1 : -1), 0);
129
191
  if (imbalance < 0) {
130
- this.selfClosing = false;
131
- this.closing = true;
192
+ this.#selfClosing = false;
193
+ this.#closing = true;
132
194
  } else {
133
- Parser.warn('无法修复无效自封闭标签', noWrap(this.toString()));
195
+ Parser.warn('无法修复无效自封闭标签', noWrap(String(this)));
134
196
  throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
135
197
  }
136
198
  }