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
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ const {explode} = require('../../util/string'),
4
+ {typeError, externalUse} = require('../../util/debug'),
5
+ /** @type {Parser} */ Parser = require('../..'),
6
+ LinkToken = require('.'),
7
+ ImageParameterToken = require('../imageParameter');
8
+
9
+ /**
10
+ * 图片
11
+ * @classdesc `{childNodes: [AtomToken, ...ImageParameterToken]}`
12
+ */
13
+ class FileToken extends LinkToken {
14
+ type = 'file';
15
+ /** @type {Set<string>} */ #keys = new Set();
16
+ /** @type {Record<string, Set<ImageParameterToken>>} */ #args = {};
17
+
18
+ setFragment = undefined;
19
+ asSelfLink = undefined;
20
+ setLinkText = undefined;
21
+ pipeTrick = undefined;
22
+
23
+ /**
24
+ * @param {string} link
25
+ * @param {string|undefined} text
26
+ * @param {Title} title
27
+ * @param {accum} accum
28
+ * @complexity `n`
29
+ */
30
+ constructor(link, text, title, config = Parser.getConfig(), accum = []) {
31
+ super(link, undefined, title, config, accum);
32
+ this.setAttribute('acceptable', {AtomToken: 0, ImageParameterToken: '1:'});
33
+ this.append(...explode('-{', '}-', '|', text).map(part => new ImageParameterToken(part, config, accum)));
34
+ this.seal(['setFragment', 'asSelfLink', 'setLinkText', 'pipeTrick']);
35
+ }
36
+
37
+ /**
38
+ * @param {number} i
39
+ * @complexity `n`
40
+ */
41
+ removeAt(i) {
42
+ const /** @type {ImageParameterToken} */ token = super.removeAt(i),
43
+ args = this.getArgs(token.name, false, false);
44
+ args.delete(token);
45
+ if (args.size === 0) {
46
+ this.#keys.delete(token.name);
47
+ }
48
+ return token;
49
+ }
50
+
51
+ /**
52
+ * @param {ImageParameterToken} token
53
+ * @complexity `n`
54
+ */
55
+ insertAt(token, i = this.childElementCount) {
56
+ if (!Parser.running) {
57
+ this.getArgs(token.name, false, false).add(token);
58
+ this.#keys.add(token.name);
59
+ }
60
+ return super.insertAt(token, i);
61
+ }
62
+
63
+ /** @returns {ImageParameterToken[]} */
64
+ getAllArgs() {
65
+ return this.childNodes.slice(1);
66
+ }
67
+
68
+ /** @complexity `n` */
69
+ getFrameArgs() {
70
+ const args = this.getAllArgs()
71
+ .filter(({name}) => ['manualthumb', 'frameless', 'framed', 'thumbnail'].includes(name));
72
+ if (args.length > 1) {
73
+ Parser.error(`警告:图片 ${this.name} 带有 ${args.length} 个框架参数,只有第 1 个 ${args[0].name} 会生效!`);
74
+ }
75
+ return args;
76
+ }
77
+
78
+ /**
79
+ * @param {string} key
80
+ * @complexity `n`
81
+ */
82
+ getArgs(key, copy = true) {
83
+ if (typeof key !== 'string') {
84
+ typeError(this, 'getArgs', 'String');
85
+ } else if (!copy && !Parser.debugging && externalUse('getArgs')) {
86
+ this.debugOnly('getArgs');
87
+ }
88
+ let args = this.#args[key];
89
+ if (!args) {
90
+ args = new Set(this.getAllArgs().filter(({name}) => key === name));
91
+ this.#args[key] = args;
92
+ }
93
+ return copy ? new Set(args) : args;
94
+ }
95
+
96
+ /**
97
+ * @param {string} key
98
+ * @complexity `n`
99
+ */
100
+ hasArg(key) {
101
+ return this.getArgs(key, false).size > 0;
102
+ }
103
+
104
+ /**
105
+ * @param {string} key
106
+ * @complexity `n`
107
+ */
108
+ getArg(key) {
109
+ return [...this.getArgs(key, false)].sort((a, b) => a.comparePosition(b)).at(-1);
110
+ }
111
+
112
+ /**
113
+ * @param {string} key
114
+ * @complexity `n`
115
+ */
116
+ removeArg(key) {
117
+ for (const token of this.getArgs(key, false)) {
118
+ this.removeChild(token);
119
+ }
120
+ }
121
+
122
+ /** @complexity `n` */
123
+ getKeys() {
124
+ const args = this.getAllArgs();
125
+ if (this.#keys.size === 0 && args.length) {
126
+ for (const {name} of args) {
127
+ this.#keys.add(name);
128
+ }
129
+ }
130
+ return [...this.#keys];
131
+ }
132
+
133
+ /**
134
+ * @param {string} key
135
+ * @complexity `n`
136
+ */
137
+ getValues(key) {
138
+ return [...this.getArgs(key, false)].map(token => token.getValue());
139
+ }
140
+
141
+ /**
142
+ * @template {string|undefined} T
143
+ * @param {T} key
144
+ * @returns {T extends undefined ? Object<string, string> : string|true}
145
+ * @complexity `n`
146
+ */
147
+ getValue(key) {
148
+ if (key !== undefined) {
149
+ return this.getArg(key)?.getValue();
150
+ }
151
+ return Object.fromEntries(this.getKeys().map(k => [k, this.getValue(k)]));
152
+ }
153
+
154
+ /**
155
+ * @param {string} key
156
+ * @param {string|boolean} value
157
+ * @complexity `n`
158
+ */
159
+ setValue(key, value) {
160
+ if (typeof key !== 'string') {
161
+ typeError(this, 'setValue', 'String');
162
+ } else if (value === false) {
163
+ this.removeArg(key);
164
+ return;
165
+ }
166
+ const token = this.getArg(key);
167
+ value = value === true ? value : String(value);
168
+ if (token) {
169
+ token.setValue(value);
170
+ return;
171
+ }
172
+ let syntax = '';
173
+ const config = this.getAttribute('config');
174
+ if (key !== 'caption') {
175
+ syntax = Object.entries(config.img).find(([, name]) => name === key)?.[0];
176
+ if (!syntax) {
177
+ throw new RangeError(`未定义的图片参数: ${key}`);
178
+ }
179
+ }
180
+ if (value === true) {
181
+ if (syntax.includes('$1')) {
182
+ typeError(this, 'setValue', 'Boolean');
183
+ }
184
+ const newArg = Parser.run(() => new ImageParameterToken(syntax, config));
185
+ this.appendChild(newArg);
186
+ return;
187
+ }
188
+ const wikitext = `[[File:F|${syntax ? syntax.replace('$1', value) : value}]]`,
189
+ root = Parser.parse(wikitext, this.getAttribute('include'), 6, config),
190
+ {childNodes: {length}, firstElementChild} = root;
191
+ if (length !== 1 || !firstElementChild?.matches('file#File:F')
192
+ || firstElementChild.childElementCount !== 2 || firstElementChild.lastElementChild.name !== key
193
+ ) {
194
+ throw new SyntaxError(`非法的 ${key} 参数:${value.replaceAll('\n', '\\n')}`);
195
+ }
196
+ this.appendChild(firstElementChild.lastChild);
197
+ }
198
+ }
199
+
200
+ Parser.classes.FileToken = __filename;
201
+ module.exports = FileToken;
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ const Title = require('../../lib/title'), // eslint-disable-line no-unused-vars
4
+ {text} = require('../../util/string'),
5
+ {undo} = require('../../util/debug'),
6
+ /** @type {Parser} */ Parser = require('../..'),
7
+ Token = require('..');
8
+
9
+ /**
10
+ * 内链
11
+ * @classdesc `{childNodes: [AtomToken, ?Token]}`
12
+ */
13
+ class LinkToken extends Token {
14
+ type = 'link';
15
+ selfLink;
16
+ fragment;
17
+ interwiki;
18
+
19
+ /**
20
+ * @param {string} link
21
+ * @param {string|undefined} linkText
22
+ * @param {Title} title
23
+ * @param {accum} accum
24
+ */
25
+ constructor(link, linkText, title, config = Parser.getConfig(), accum = []) {
26
+ super(undefined, config, true, accum, {AtomToken: 0, Token: 1});
27
+ const AtomToken = require('../atom');
28
+ this.appendChild(new AtomToken(link, 'link-target', config, accum, {
29
+ 'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
30
+ }));
31
+ if (linkText !== undefined) {
32
+ const inner = new Token(linkText, config, true, accum);
33
+ inner.type = 'link-text';
34
+ this.appendChild(inner.setAttribute('stage', 7));
35
+ }
36
+ this.selfLink = !title.title;
37
+ this.fragment = title.fragment;
38
+ this.interwiki = title.interwiki;
39
+ this.setAttribute('name', title.title).seal(['selfLink', 'fragment', 'interwiki']).protectChildren(0);
40
+ }
41
+
42
+ cloneNode() {
43
+ const [link, ...linkText] = this.cloneChildren();
44
+ return Parser.run(() => {
45
+ const /** @type {typeof LinkToken} */ Constructor = this.constructor,
46
+ token = new Constructor('', undefined, {
47
+ title: this.name, interwiki: this.interwiki, fragment: this.fragment,
48
+ }, this.getAttribute('config'));
49
+ token.firstElementChild.safeReplaceWith(link);
50
+ token.appendChild(...linkText);
51
+ return token.afterBuild();
52
+ });
53
+ }
54
+
55
+ afterBuild() {
56
+ if (this.name.includes('\x00')) {
57
+ this.setAttribute('name', text(this.buildFromStr(this.name)));
58
+ }
59
+ if (this.fragment.includes('\x00')) {
60
+ this.setAttribute('fragment', text(this.buildFromStr(this.fragment)));
61
+ }
62
+ const that = this;
63
+ const /** @type {AstListener} */ linkListener = (e, data) => {
64
+ const {prevTarget} = e;
65
+ if (prevTarget?.type === 'link-target') {
66
+ const name = prevTarget.text(),
67
+ {title, interwiki, fragment, ns, valid} = that.normalizeTitle(name);
68
+ if (!valid) {
69
+ undo(e, data);
70
+ throw new Error(`非法的内链目标:${name}`);
71
+ } else if (that.type === 'category' && (interwiki || ns !== 14)
72
+ || that.type === 'file' && (interwiki || ns !== 6)
73
+ ) {
74
+ undo(e, data);
75
+ throw new Error(`${that.type === 'file' ? '文件' : '分类'}链接不可更改命名空间:${name}`);
76
+ } else if (that.type === 'link' && !interwiki && [6, 14].includes(ns) && !name.trim().startsWith(':')) {
77
+ const {firstChild} = prevTarget;
78
+ if (typeof firstChild === 'string') {
79
+ prevTarget.setText(`:${firstChild}`);
80
+ } else {
81
+ prevTarget.prepend(':');
82
+ }
83
+ }
84
+ that.setAttribute('selfLink', !title).setAttribute('interwiki', interwiki)
85
+ .setAttribute('name', title).setAttribute('fragment', fragment);
86
+ }
87
+ };
88
+ this.addEventListener(['remove', 'insert', 'replace', 'text'], linkListener);
89
+ return this;
90
+ }
91
+
92
+ toString() {
93
+ return `[[${super.toString('|')}]]`;
94
+ }
95
+
96
+ getPadding() {
97
+ return 2;
98
+ }
99
+
100
+ getGaps() {
101
+ return 1;
102
+ }
103
+
104
+ text() {
105
+ return `[[${super.text('|')}]]`;
106
+ }
107
+
108
+ /** @returns {[number, string][]} */
109
+ plain() {
110
+ return this.childElementCount === 1 ? [] : this.lastElementChild.plain();
111
+ }
112
+
113
+ /** @param {string} link */
114
+ setTarget(link) {
115
+ link = String(link);
116
+ if (!/^\s*[:#]/.test(link)) {
117
+ link = `:${link}`;
118
+ }
119
+ const root = Parser.parse(`[[${link}]]`, this.getAttribute('include'), 6, this.getAttribute('config')),
120
+ {childNodes: {length}, firstElementChild} = root;
121
+ if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childElementCount !== 1) {
122
+ const msgs = {link: '内链', file: '文件链接', category: '分类'};
123
+ throw new SyntaxError(`非法的${msgs[this.type]}目标:${link}`);
124
+ }
125
+ const {firstChild} = firstElementChild;
126
+ root.destroy();
127
+ firstElementChild.destroy();
128
+ this.firstElementChild.safeReplaceWith(firstChild);
129
+ }
130
+
131
+ /** @param {string} fragment */
132
+ #setFragment(fragment, page = true) {
133
+ fragment = String(fragment).replace(/[<>[]#|=!]/g, p => encodeURIComponent(p));
134
+ const include = this.getAttribute('include'),
135
+ config = this.getAttribute('config'),
136
+ root = Parser.parse(`[[${page ? `:${this.name}` : ''}#${fragment}]]`, include, 6, config),
137
+ {childNodes: {length}, firstElementChild} = root;
138
+ if (length !== 1 || firstElementChild?.type !== 'link' || firstElementChild.childElementCount !== 1) {
139
+ throw new SyntaxError(`非法的 fragment:${fragment}`);
140
+ }
141
+ if (page) {
142
+ Parser.warn(`${this.constructor.name}.setFragment 方法会同时规范化页面名!`);
143
+ }
144
+ const {firstChild} = firstElementChild;
145
+ root.destroy();
146
+ firstElementChild.destroy();
147
+ this.firstElementChild.safeReplaceWith(firstChild);
148
+ }
149
+
150
+ /** @param {string} fragment */
151
+ setFragment(fragment) {
152
+ this.#setFragment(fragment);
153
+ }
154
+
155
+ asSelfLink(fragment = this.fragment) {
156
+ fragment = String(fragment);
157
+ if (!fragment.trim()) {
158
+ throw new RangeError(`${this.constructor.name}.asSelfLink 方法必须指定非空的 fragment!`);
159
+ }
160
+ this.#setFragment(fragment, false);
161
+ }
162
+
163
+ setLinkText(linkText = '') {
164
+ linkText = String(linkText);
165
+ let lastElementChild;
166
+ const config = this.getAttribute('config');
167
+ if (linkText) {
168
+ const root = Parser.parse(`[[${
169
+ this.type === 'category' ? 'Category:' : ''
170
+ }L|${linkText}]]`, this.getAttribute('include'), 6, config),
171
+ {childNodes: {length}, firstElementChild} = root;
172
+ if (length !== 1 || firstElementChild?.type !== this.type || firstElementChild.childElementCount !== 2) {
173
+ throw new SyntaxError(`非法的${this.type === 'link' ? '内链文字' : '分类关键字'}:${
174
+ linkText.replaceAll('\n', '\\n')
175
+ }`);
176
+ }
177
+ ({lastElementChild} = firstElementChild);
178
+ } else {
179
+ lastElementChild = Parser.run(() => new Token('', config));
180
+ lastElementChild.setAttribute('stage', 7).type = 'link-text';
181
+ }
182
+ if (this.childElementCount === 1) {
183
+ this.appendChild(lastElementChild);
184
+ } else {
185
+ this.lastElementChild.safeReplaceWith(lastElementChild);
186
+ }
187
+ }
188
+
189
+ pipeTrick() {
190
+ const linkText = this.firstElementChild.text();
191
+ if (/[#%]/.test(linkText)) {
192
+ throw new Error('Pipe trick 不能用于带有"#"或"%"的场合!');
193
+ }
194
+ const m1 = linkText.match(/^:?(?:[ \w\x80-\xff-]+:)?(.+?) ?\(.+\)$/);
195
+ if (m1) {
196
+ this.setLinkText(m1[1]);
197
+ return;
198
+ }
199
+ const m2 = linkText.match(/^:?(?:[ \w\x80-\xff-]+:)?(.+?) ?(.+)$/);
200
+ if (m2) {
201
+ this.setLinkText(m2[1]);
202
+ return;
203
+ }
204
+ const m3 = linkText.match(/^:?(?:[ \w\x80-\xff-]+:)?(.+?)(?: ?\(.+\))?(?:, |,|، ).+/);
205
+ if (m3) {
206
+ this.setLinkText(m3[1]);
207
+ return;
208
+ }
209
+ this.setLinkText(linkText);
210
+ }
211
+ }
212
+
213
+ Parser.classes.LinkToken = __filename;
214
+ module.exports = LinkToken;
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+ const Token = require('.'),
3
+ AtomToken = require('./atom'),
4
+ {fixToken} = require('./util');
5
+
6
+ class ListToken extends fixToken(Token) {
7
+ type = 'list';
8
+
9
+ /**
10
+ * @param {string} syntax
11
+ * @param {?string|number|Token|(string|Token)[]} content
12
+ * @param {Object<string, any>} config
13
+ * @param {Token} parent
14
+ * @param {Token[]} accum
15
+ */
16
+ constructor(syntax, content, config = require(Token.config), parent = null, accum = [], isTable = false) {
17
+ if (/[^:;#*]/.test(syntax)) {
18
+ throw new RangeError('List语法只接受":"、";"、"#"或"*"!');
19
+ }
20
+ super(new AtomToken(syntax, 'list-syntax'), config, true, parent, accum, ['AtomToken', 'Token']);
21
+ const inner = new Token(content, config, true, this, accum);
22
+ inner.type = 'list-inner';
23
+ inner.set('stage', isTable ? 4 : 10);
24
+ this.lists = new Set(syntax.split(''));
25
+ this.seal();
26
+ }
27
+
28
+ isDt() {
29
+ return this.$children[0].contains(';');
30
+ }
31
+
32
+ idDd() {
33
+ return this.$children[0].contains(':');
34
+ }
35
+
36
+ isOl() {
37
+ return this.$children[0].contains('#');
38
+ }
39
+
40
+ isUl() {
41
+ return this.$children[0].contains('*');
42
+ }
43
+ }
44
+
45
+ Token.classes.ListToken = ListToken;
46
+
47
+ module.exports = ListToken;
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const {typeError} = require('../util/debug'),
4
+ /** @type {Parser} */ Parser = require('..'),
5
+ Token = require('.');
6
+
7
+ /**
8
+ * 自由外链
9
+ * @classdesc `{childNodes: [...string|CommentToken|IncludeToken|NoincludeToken]}`
10
+ */
11
+ class MagicLinkToken extends Token {
12
+ type = 'free-ext-link';
13
+ #protocolRegex;
14
+
15
+ get protocol() {
16
+ return this.text().match(this.#protocolRegex)?.[0];
17
+ }
18
+ /** @param {string} value */
19
+ set protocol(value) {
20
+ if (typeof value !== 'string') {
21
+ typeError(this, 'protocol', 'String');
22
+ }
23
+ if (!new RegExp(`${this.#protocolRegex.source}$`, 'i').test(value)) {
24
+ throw new RangeError(`非法的外链协议:${value}`);
25
+ }
26
+ this.replaceChildren(this.text().replace(this.#protocolRegex, value));
27
+ }
28
+
29
+ /**
30
+ * @param {string} url
31
+ * @param {accum} accum
32
+ */
33
+ constructor(url, doubleSlash = false, config = Parser.getConfig(), accum = []) {
34
+ super(url, config, true, accum, {'Stage-1': ':', '!ExtToken': ''});
35
+ if (doubleSlash) {
36
+ this.type = 'ext-link-url';
37
+ }
38
+ this.#protocolRegex = new RegExp(`^(?:${config.protocol}${doubleSlash ? '|//' : ''})`, 'i');
39
+ }
40
+
41
+ getUrl() {
42
+ const url = this.text();
43
+ try {
44
+ return new URL(url);
45
+ } catch (e) {
46
+ if (e instanceof TypeError && e.message === 'Invalid URL') {
47
+ throw new Error(`非标准协议的外部链接:${url}`);
48
+ }
49
+ throw e;
50
+ }
51
+ }
52
+
53
+ /** @param {string|URL} url */
54
+ setTarget(url) {
55
+ url = String(url);
56
+ const root = Parser.parse(url, this.getAttribute('include'), 9, this.getAttribute('config')),
57
+ {childNodes: {length}, firstElementChild} = root;
58
+ if (length !== 1 || firstElementChild?.type !== 'free-ext-link') {
59
+ throw new SyntaxError(`非法的自由外链目标:${url}`);
60
+ }
61
+ this.replaceChildren(...firstElementChild.childNodes);
62
+ }
63
+ }
64
+
65
+ Parser.classes.MagicLinkToken = __filename;
66
+ module.exports = MagicLinkToken;
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const hidden = require('../../mixin/hidden'),
4
+ /** @type {Parser} */ Parser = require('../..'),
5
+ NowikiToken = require('.');
6
+
7
+ /**
8
+ * HTML注释,不可见
9
+ * @classdesc `{childNodes: [string]}`
10
+ */
11
+ class CommentToken extends hidden(NowikiToken) {
12
+ type = 'comment';
13
+ closed;
14
+
15
+ /**
16
+ * @param {string} wikitext
17
+ * @param {accum} accum
18
+ */
19
+ constructor(wikitext, closed = true, config = Parser.getConfig(), accum = []) {
20
+ super(wikitext, config, accum);
21
+ this.closed = closed;
22
+ }
23
+
24
+ /** @this {CommentToken & {firstChild: string}} */
25
+ cloneNode() {
26
+ return Parser.run(() => new CommentToken(this.firstChild, this.closed, this.getAttribute('config')));
27
+ }
28
+
29
+ /** @this {CommentToken & {firstChild: string}} */
30
+ toString() {
31
+ const {firstChild, closed, nextSibling} = this;
32
+ if (!closed && nextSibling) {
33
+ Parser.error('自动闭合HTML注释', firstChild.replaceAll('\n', '\\n'));
34
+ this.closed = true;
35
+ }
36
+ return `<!--${firstChild}${this.closed ? '-->' : ''}`;
37
+ }
38
+
39
+ getPadding() {
40
+ return 4;
41
+ }
42
+ }
43
+
44
+ Parser.classes.CommentToken = __filename;
45
+ module.exports = CommentToken;
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const hidden = require('../../mixin/hidden'),
4
+ /** @type {Parser} */ Parser = require('../..'),
5
+ NowikiToken = require('.');
6
+
7
+ /**
8
+ * 状态开关
9
+ * @classdesc `{childNodes: [string]}`
10
+ */
11
+ class DoubleUnderscoreToken extends hidden(NowikiToken) {
12
+ type = 'double-underscore';
13
+
14
+ /**
15
+ * @param {string} word
16
+ * @param {accum} accum
17
+ */
18
+ constructor(word, config = Parser.getConfig(), accum = []) {
19
+ super(word, config, accum);
20
+ this.setAttribute('name', word.toLowerCase());
21
+ }
22
+
23
+ cloneNode() {
24
+ return Parser.run(() => new DoubleUnderscoreToken(this.firstChild, this.getAttribute('config')));
25
+ }
26
+
27
+ /** @this {DoubleUnderscoreToken & {firstChild: string}} */
28
+ toString() {
29
+ return `__${this.firstChild}__`;
30
+ }
31
+
32
+ getPadding() {
33
+ return 2;
34
+ }
35
+
36
+ setText() {
37
+ throw new Error(`禁止修改 ${this.constructor.name}!`);
38
+ }
39
+ }
40
+
41
+ Parser.classes.DoubleUnderscoreToken = __filename;
42
+ module.exports = DoubleUnderscoreToken;
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const /** @type {Parser} */ Parser = require('../..'),
4
+ NowikiToken = require('.');
5
+
6
+ /**
7
+ * `<hr>`
8
+ * @classdesc `{childNodes: [string]}`
9
+ */
10
+ class HrToken extends NowikiToken {
11
+ type = 'hr';
12
+
13
+ /**
14
+ * @param {number} n
15
+ * @param {accum} accum
16
+ */
17
+ constructor(n, config = Parser.getConfig(), accum = []) {
18
+ super('-'.repeat(n), config, accum);
19
+ }
20
+
21
+ /** @this {HrToken & {firstChild: string}} */
22
+ cloneNode() {
23
+ return Parser.run(() => new HrToken(this.firstChild.length, this.getAttribute('config')));
24
+ }
25
+
26
+ /** @returns {[number, string][]} */
27
+ plain() {
28
+ return [];
29
+ }
30
+
31
+ /** @param {string} str */
32
+ setText(str) {
33
+ if (!/^-{4,}$/.test(str)) {
34
+ throw new RangeError('<hr>总是写作不少于4个的连续"-"!');
35
+ }
36
+ return super.setText(str);
37
+ }
38
+ }
39
+
40
+ Parser.classes.HrToken = __filename;
41
+ module.exports = HrToken;
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const fixedToken = require('../../mixin/fixedToken'),
4
+ /** @type {Parser} */ Parser = require('../..'),
5
+ Token = require('..');
6
+
7
+ /**
8
+ * 纯文字Token,不会被解析
9
+ * @classdesc `{childNodes: [string]}`
10
+ */
11
+ class NowikiToken extends fixedToken(Token) {
12
+ type = 'ext-inner';
13
+
14
+ /**
15
+ * @param {string} wikitext
16
+ * @param {accum} accum
17
+ */
18
+ constructor(wikitext, config = Parser.getConfig(), accum = []) {
19
+ super(wikitext, config, false, accum);
20
+ }
21
+
22
+ /** @this {NowikiToken & {firstChild: string}} */
23
+ cloneNode() {
24
+ const /** @type {typeof NowikiToken} */ Constructor = this.constructor,
25
+ token = Parser.run(() => new Constructor(this.firstChild, this.getAttribute('config')));
26
+ token.type = this.type;
27
+ return token;
28
+ }
29
+
30
+ /** @param {string} str */
31
+ setText(str) {
32
+ return super.setText(str, 0);
33
+ }
34
+ }
35
+
36
+ Parser.classes.NowikiToken = __filename;
37
+ module.exports = NowikiToken;