wikiparser-node 0.0.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 (65) hide show
  1. package/.eslintrc.json +229 -0
  2. package/LICENSE +674 -0
  3. package/README.md +1896 -0
  4. package/config/default.json +766 -0
  5. package/config/llwiki.json +686 -0
  6. package/config/moegirl.json +721 -0
  7. package/index.js +159 -0
  8. package/jsconfig.json +7 -0
  9. package/lib/element.js +690 -0
  10. package/lib/node.js +357 -0
  11. package/lib/ranges.js +122 -0
  12. package/lib/title.js +57 -0
  13. package/mixin/attributeParent.js +67 -0
  14. package/mixin/fixedToken.js +32 -0
  15. package/mixin/hidden.js +22 -0
  16. package/package.json +30 -0
  17. package/parser/brackets.js +107 -0
  18. package/parser/commentAndExt.js +61 -0
  19. package/parser/externalLinks.js +30 -0
  20. package/parser/hrAndDoubleUnderscore.js +26 -0
  21. package/parser/html.js +41 -0
  22. package/parser/links.js +92 -0
  23. package/parser/magicLinks.js +40 -0
  24. package/parser/quotes.js +63 -0
  25. package/parser/table.js +97 -0
  26. package/src/arg.js +150 -0
  27. package/src/atom/hidden.js +10 -0
  28. package/src/atom/index.js +33 -0
  29. package/src/attribute.js +342 -0
  30. package/src/extLink.js +116 -0
  31. package/src/heading.js +91 -0
  32. package/src/html.js +144 -0
  33. package/src/imageParameter.js +172 -0
  34. package/src/index.js +602 -0
  35. package/src/link/category.js +88 -0
  36. package/src/link/file.js +201 -0
  37. package/src/link/index.js +214 -0
  38. package/src/listToken.js +47 -0
  39. package/src/magicLink.js +66 -0
  40. package/src/nowiki/comment.js +45 -0
  41. package/src/nowiki/doubleUnderscore.js +42 -0
  42. package/src/nowiki/hr.js +41 -0
  43. package/src/nowiki/index.js +37 -0
  44. package/src/nowiki/noinclude.js +24 -0
  45. package/src/nowiki/quote.js +37 -0
  46. package/src/onlyinclude.js +42 -0
  47. package/src/parameter.js +165 -0
  48. package/src/syntax.js +80 -0
  49. package/src/table/index.js +867 -0
  50. package/src/table/td.js +259 -0
  51. package/src/table/tr.js +244 -0
  52. package/src/tagPair/ext.js +85 -0
  53. package/src/tagPair/include.js +45 -0
  54. package/src/tagPair/index.js +91 -0
  55. package/src/transclude.js +627 -0
  56. package/tool/index.js +898 -0
  57. package/typings/element.d.ts +28 -0
  58. package/typings/index.d.ts +49 -0
  59. package/typings/node.d.ts +23 -0
  60. package/typings/parser.d.ts +9 -0
  61. package/typings/table.d.ts +14 -0
  62. package/typings/token.d.ts +21 -0
  63. package/typings/tool.d.ts +10 -0
  64. package/util/debug.js +70 -0
  65. package/util/string.js +60 -0
package/src/heading.js ADDED
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const {typeError} = require('../util/debug'),
4
+ fixedToken = require('../mixin/fixedToken'),
5
+ /** @type {Parser} */ Parser = require('..'),
6
+ Token = require('.');
7
+
8
+ /**
9
+ * 章节标题
10
+ * @classdesc `{childNodes: [Token, HiddenToken]}`
11
+ */
12
+ class HeadingToken extends fixedToken(Token) {
13
+ type = 'heading';
14
+
15
+ /**
16
+ * @param {number} level
17
+ * @param {string[]} input
18
+ * @param {accum} accum
19
+ */
20
+ constructor(level, input, config = Parser.getConfig(), accum = []) {
21
+ super(undefined, config, true, accum);
22
+ this.setAttribute('name', String(level));
23
+ const token = new Token(input[0], config, true, accum);
24
+ token.type = 'heading-title';
25
+ 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
+ });
30
+ this.append(token, trail);
31
+ }
32
+
33
+ cloneNode() {
34
+ const [title, trail] = this.cloneChildren(),
35
+ token = Parser.run(() => new HeadingToken(Number(this.name), [], this.getAttribute('config')));
36
+ token.firstElementChild.safeReplaceWith(title);
37
+ token.lastElementChild.safeReplaceWith(trail);
38
+ return token;
39
+ }
40
+
41
+ toString() {
42
+ const equals = '='.repeat(Number(this.name)),
43
+ {previousVisibleSibling, nextVisibleSibling} = this;
44
+ return `${
45
+ typeof previousVisibleSibling === 'string' && !previousVisibleSibling.endsWith('\n')
46
+ || previousVisibleSibling instanceof Token
47
+ ? '\n'
48
+ : ''
49
+ }${equals}${super.toString(equals)}${
50
+ typeof nextVisibleSibling === 'string' && !nextVisibleSibling.startsWith('\n')
51
+ || nextVisibleSibling instanceof Token
52
+ ? '\n'
53
+ : ''
54
+ }`;
55
+ }
56
+
57
+ getPadding() {
58
+ return Number(this.name);
59
+ }
60
+
61
+ getGaps() {
62
+ return Number(this.name);
63
+ }
64
+
65
+ /** @returns {string} */
66
+ text() {
67
+ const equals = '='.repeat(Number(this.name));
68
+ return `${equals}${this.firstElementChild.text()}${equals}`;
69
+ }
70
+
71
+ /** @returns {[number, string][]} */
72
+ plain() {
73
+ return this.firstElementChild.plain();
74
+ }
75
+
76
+ /** @param {number} n */
77
+ setLevel(n) {
78
+ if (typeof n !== 'number') {
79
+ typeError(this, 'setLevel', 'Number');
80
+ }
81
+ n = Math.min(Math.max(n, 1), 6);
82
+ this.setAttribute('name', String(n)).firstElementChild.setAttribute('name', this.name);
83
+ }
84
+
85
+ removeTrail() {
86
+ this.lastElementChild.replaceChildren();
87
+ }
88
+ }
89
+
90
+ Parser.classes.HeadingToken = __filename;
91
+ module.exports = HeadingToken;
package/src/html.js ADDED
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const fixedToken = require('../mixin/fixedToken'),
4
+ attributeParent = require('../mixin/attributeParent'),
5
+ /** @type {Parser} */ Parser = require('..'),
6
+ Token = require('.');
7
+
8
+ /**
9
+ * HTML标签
10
+ * @classdesc `{childNodes: [AttributeToken]}`
11
+ */
12
+ class HtmlToken extends attributeParent(fixedToken(Token)) {
13
+ type = 'html';
14
+ closing;
15
+ selfClosing;
16
+ #tag;
17
+
18
+ /**
19
+ * @param {string} name
20
+ * @param {AttributeToken} attr
21
+ * @param {boolean} closing
22
+ * @param {boolean} selfClosing
23
+ * @param {accum} accum
24
+ */
25
+ constructor(name, attr, closing, selfClosing, config = Parser.getConfig(), accum = []) {
26
+ super(undefined, config, true, accum);
27
+ this.appendChild(attr);
28
+ this.setAttribute('name', name.toLowerCase());
29
+ this.closing = closing;
30
+ this.selfClosing = selfClosing;
31
+ this.#tag = name;
32
+ }
33
+
34
+ cloneNode() {
35
+ const [attr] = this.cloneChildren(),
36
+ config = this.getAttribute('config');
37
+ return Parser.run(() => new HtmlToken(this.#tag, attr, this.closing, this.selfClosing, config));
38
+ }
39
+
40
+ /**
41
+ * @template {string} T
42
+ * @param {T} key
43
+ * @returns {TokenAttribute<T>}
44
+ */
45
+ getAttribute(key) {
46
+ if (key === 'tag') {
47
+ return this.#tag;
48
+ }
49
+ return super.getAttribute(key);
50
+ }
51
+
52
+ toString() {
53
+ return `<${this.closing ? '/' : ''}${this.#tag}${super.toString()}${this.selfClosing ? '/' : ''}>`;
54
+ }
55
+
56
+ getPadding() {
57
+ return this.#tag.length + (this.closing ? 2 : 1);
58
+ }
59
+
60
+ text() {
61
+ return `<${this.closing ? '/' : ''}${this.#tag}${super.text()}${this.selfClosing ? '/' : ''}>`;
62
+ }
63
+
64
+ /** @returns {[number, string][]} */
65
+ plain() {
66
+ return [];
67
+ }
68
+
69
+ /** @param {string} tag */
70
+ replaceTag(tag) {
71
+ const name = tag.toLowerCase();
72
+ if (!this.getAttribute('config').html.flat().includes(name)) {
73
+ throw new RangeError(`非法的HTML标签:${tag}`);
74
+ }
75
+ this.setAttribute('name', name).#tag = tag;
76
+ }
77
+
78
+ /** @complexity `n` */
79
+ findMatchingTag() {
80
+ const {html} = this.getAttribute('config'),
81
+ {name, parentElement, closing, selfClosing} = this,
82
+ string = this.toString().replaceAll('\n', '\\n');
83
+ if (closing && selfClosing) {
84
+ throw new SyntaxError(`同时闭合和自封闭的标签:${string}`);
85
+ } else if (html[2].includes(name) || selfClosing && html[1].includes(name)) { // 自封闭标签
86
+ return this;
87
+ } else if (selfClosing && html[0].includes(name)) {
88
+ throw new SyntaxError(`无效自封闭标签:${string}`);
89
+ } else if (!parentElement) {
90
+ return;
91
+ }
92
+ const {children} = parentElement,
93
+ i = children.indexOf(this),
94
+ selector = `html#${name}`,
95
+ siblings = closing
96
+ ? children.slice(0, i).reverse().filter(child => child.matches(selector))
97
+ : children.slice(i + 1).filter(child => child.matches(selector));
98
+ let imbalance = closing ? -1 : 1;
99
+ for (const token of siblings) {
100
+ if (token.closing) {
101
+ imbalance--;
102
+ } else {
103
+ imbalance++;
104
+ }
105
+ if (imbalance === 0) {
106
+ return token;
107
+ }
108
+ }
109
+ throw new SyntaxError(`未${closing ? '匹配的闭合' : '闭合的'}标签:${string}`);
110
+ }
111
+
112
+ #localMatch() {
113
+ this.selfClosing = false;
114
+ const root = Parser.parse(`</${this.name}>`, false, 3, this.getAttribute('config'));
115
+ this.after(root.firstChild);
116
+ }
117
+
118
+ /** @complexity `n` */
119
+ fix() {
120
+ const config = this.getAttribute('config'),
121
+ {parentElement, selfClosing, name, firstElementChild} = this;
122
+ if (!parentElement || !selfClosing || !config.html[0].includes(name)) {
123
+ return;
124
+ } else if (firstElementChild.text().trim()) {
125
+ this.#localMatch();
126
+ return;
127
+ }
128
+ const {children} = parentElement,
129
+ i = children.indexOf(this),
130
+ /** @type {HtmlToken[]} */
131
+ prevSiblings = children.slice(0, i).filter(child => child.matches(`html#${name}`)),
132
+ imbalance = prevSiblings.reduce((acc, {closing}) => acc + (closing ? 1 : -1), 0);
133
+ if (imbalance < 0) {
134
+ this.selfClosing = false;
135
+ this.closing = true;
136
+ } else {
137
+ Parser.warn('无法修复无效自封闭标签', this.toString().replaceAll('\n', '\\n'));
138
+ throw new Error(`无法修复无效自封闭标签:前文共有 ${imbalance} 个未匹配的闭合标签`);
139
+ }
140
+ }
141
+ }
142
+
143
+ Parser.classes.HtmlToken = __filename;
144
+ module.exports = HtmlToken;
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ const {typeError} = require('../util/debug'),
4
+ {extUrlChar} = require('../util/string'),
5
+ Title = require('../lib/title'),
6
+ /** @type {Parser} */ Parser = require('..'),
7
+ Token = require('.');
8
+
9
+ /**
10
+ * 图片参数
11
+ * @classdesc `{childNodes: ...(string|Token)}`
12
+ */
13
+ class ImageParameterToken extends Token {
14
+ type = 'image-parameter';
15
+ #syntax = '';
16
+
17
+ /**
18
+ * @param {string} key
19
+ * @param {string} value
20
+ */
21
+ static #validate(key, value, config = Parser.getConfig()) {
22
+ value = value.trim();
23
+ if (key === 'width') {
24
+ const mt = value.match(/^(\d*)(?:x(\d*))?$/);
25
+ return Number(mt?.[1]) > 0 || Number(mt?.[2]) > 0;
26
+ } else if (['alt', 'class', 'manualthumb', 'frameless', 'framed', 'thumbnail'].includes(key)) {
27
+ return true;
28
+ } else if (key === 'link') {
29
+ if (!value) {
30
+ return true;
31
+ }
32
+ const regex = new RegExp(`(?:${config.protocol}|//)${extUrlChar}`, 'ui');
33
+ if (regex.test(value)) {
34
+ return true;
35
+ }
36
+ if (/^\[\[.+]]$/.test(value)) {
37
+ value = value.slice(2, -2);
38
+ }
39
+ if (value.includes('%')) {
40
+ try {
41
+ value = decodeURIComponent(value);
42
+ } catch {}
43
+ }
44
+ return new Title(value, 0, config).valid;
45
+ }
46
+ return !isNaN(value);
47
+ }
48
+
49
+ /**
50
+ * @param {string} str
51
+ * @param {accum} accum
52
+ */
53
+ constructor(str, config = Parser.getConfig(), accum = []) {
54
+ const regexes = Object.entries(config.img).map(
55
+ /** @returns {[string, string, RegExp]} */
56
+ ([syntax, param]) => [syntax, param, new RegExp(`^(\\s*)${syntax.replace('$1', '(.*)')}(\\s*)$`)],
57
+ ),
58
+ param = regexes.find(([,, regex]) => regex.test(str));
59
+ if (param) {
60
+ const mt = str.match(param[2]);
61
+ if (mt.length === 4 && !ImageParameterToken.#validate(param[1], mt[2], config)) {
62
+ // pass
63
+ } else {
64
+ if (mt.length === 3) {
65
+ super(undefined, config, true, accum);
66
+ this.#syntax = str;
67
+ } else {
68
+ super(mt[2], config, true, accum, {'Stage-2': ':', '!HeadingToken': ':'});
69
+ this.#syntax = `${mt[1]}${param[0]}${mt[3]}`;
70
+ }
71
+ this.setAttribute('name', param[1]).setAttribute('stage', 7);
72
+ return;
73
+ }
74
+ }
75
+ super(str, config, true, accum);
76
+ this.setAttribute('name', 'caption').setAttribute('stage', 7);
77
+ }
78
+
79
+ cloneNode() {
80
+ const cloned = this.cloneChildren(),
81
+ config = this.getAttribute('config'),
82
+ token = Parser.run(() => new ImageParameterToken(this.#syntax.replace('$1', ''), config));
83
+ token.replaceChildren(...cloned);
84
+ return token;
85
+ }
86
+
87
+ /**
88
+ * @template {string} T
89
+ * @param {T} key
90
+ * @returns {TokenAttribute<T>}
91
+ */
92
+ getAttribute(key) {
93
+ if (key === 'syntax') {
94
+ return this.#syntax;
95
+ }
96
+ return super.getAttribute(key);
97
+ }
98
+
99
+ isPlain() {
100
+ return true;
101
+ }
102
+
103
+ #isVoid() {
104
+ return this.#syntax && !this.#syntax.includes('$1');
105
+ }
106
+
107
+ toString() {
108
+ if (!this.#syntax) {
109
+ return super.toString();
110
+ }
111
+ return this.#syntax.replace('$1', super.toString());
112
+ }
113
+
114
+ getPadding() {
115
+ return Math.max(0, this.#syntax.indexOf('$1'));
116
+ }
117
+
118
+ text() {
119
+ if (!this.#syntax) {
120
+ return super.text().trim();
121
+ }
122
+ return this.#syntax.replace('$1', super.text()).trim();
123
+ }
124
+
125
+ /**
126
+ * @template {string|Token} T
127
+ * @param {T} token
128
+ * @complexity `n`
129
+ */
130
+ insertAt(token, i = this.childNodes.length) {
131
+ if (!Parser.running && this.#isVoid()) {
132
+ throw new Error(`图片参数 ${this.name} 不接受自定义输入!`);
133
+ }
134
+ return super.insertAt(token, i);
135
+ }
136
+
137
+ /** @complexity `n` */
138
+ getValue() {
139
+ return this.#isVoid() || super.text();
140
+ }
141
+
142
+ /**
143
+ * @param {string|boolean} value
144
+ * @complexity `n`
145
+ */
146
+ setValue(value) {
147
+ if (this.#isVoid()) {
148
+ if (typeof value !== 'boolean') {
149
+ typeError(this, 'setValue', 'Boolean');
150
+ } else if (value === false) {
151
+ this.remove();
152
+ }
153
+ return;
154
+ } else if (typeof value !== 'string') {
155
+ typeError(this, 'setValue', 'String');
156
+ }
157
+ const root = Parser.parse(`[[File:F|${
158
+ this.#syntax ? this.#syntax.replace('$1', value) : value
159
+ }]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
160
+ {childNodes: {length}, firstElementChild} = root,
161
+ param = firstElementChild?.lastElementChild;
162
+ if (length !== 1 || !firstElementChild?.matches('file#File:F')
163
+ || firstElementChild.childElementCount !== 2 || param.name !== this.name
164
+ ) {
165
+ throw new SyntaxError(`非法的 ${this.name} 参数:${value.replaceAll('\n', '\\n')}`);
166
+ }
167
+ this.replaceChildren(...param.childNodes);
168
+ }
169
+ }
170
+
171
+ Parser.classes.ImageParameterToken = __filename;
172
+ module.exports = ImageParameterToken;