wikiparser-node 0.0.2 → 0.2.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.
- package/README.md +226 -7
- package/config/default.json +12 -1
- package/config/llwiki.json +12 -1
- package/config/moegirl.json +9 -1
- package/errors/2022-07-04T22:30:41.785Z +1 -0
- package/errors/2022-07-04T22:30:41.785Z.err +11 -0
- package/errors/2022-07-04T22:30:41.785Z.json +5 -0
- package/errors/README +1 -0
- package/index.js +85 -9
- package/lib/element.js +72 -13
- package/lib/node.js +17 -9
- package/mixin/sol.js +42 -0
- package/package.json +1 -1
- package/parser/converter.js +44 -0
- package/parser/externalLinks.js +1 -1
- package/parser/list.js +58 -0
- package/parser/table.js +2 -2
- package/printed/README +1 -0
- package/src/arg.js +9 -9
- package/src/attribute.js +33 -23
- package/src/converter.js +135 -0
- package/src/converterFlags.js +214 -0
- package/src/converterRule.js +209 -0
- package/src/extLink.js +23 -10
- package/src/heading.js +15 -20
- package/src/html.js +4 -3
- package/src/imageParameter.js +6 -7
- package/src/index.js +38 -23
- package/src/link/file.js +9 -9
- package/src/link/index.js +9 -11
- package/src/magicLink.js +2 -3
- package/src/nowiki/comment.js +1 -1
- package/src/nowiki/dd.js +49 -0
- package/src/nowiki/hr.js +3 -2
- package/src/nowiki/list.js +16 -0
- package/src/parameter.js +5 -5
- package/src/syntax.js +3 -1
- package/src/table/index.js +35 -30
- package/src/table/td.js +3 -2
- package/src/table/tr.js +6 -12
- package/src/tagPair/index.js +1 -1
- package/src/transclude.js +28 -25
- package/tool/index.js +50 -40
- package/typings/index.d.ts +3 -0
- package/typings/node.d.ts +3 -3
- package/typings/token.d.ts +1 -0
- package/util/debug.js +3 -3
- package/util/string.js +16 -1
- package/src/listToken.js +0 -47
package/lib/node.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {typeError, externalUse} = require('../util/debug'),
|
|
4
|
-
{text} = require('../util/string'),
|
|
4
|
+
{text, noWrap} = require('../util/string'),
|
|
5
5
|
assert = require('assert/strict'),
|
|
6
6
|
/** @type {Parser} */ Parser = require('..');
|
|
7
7
|
|
|
@@ -34,6 +34,14 @@ class AstNode {
|
|
|
34
34
|
throw new Error(`${this.constructor.name}.${method} 方法仅用于代码调试!`);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} method
|
|
39
|
+
* @param {...string} types
|
|
40
|
+
*/
|
|
41
|
+
typeError(method, ...types) {
|
|
42
|
+
return typeError(this.constructor, method, ...types);
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
/** @param {string|string[]} keys */
|
|
38
46
|
seal(keys) {
|
|
39
47
|
if (!Parser.running && !Parser.debugging) {
|
|
@@ -68,7 +76,7 @@ class AstNode {
|
|
|
68
76
|
/** @param {PropertyKey} key */
|
|
69
77
|
hasAttribute(key) {
|
|
70
78
|
if (!['string', 'number', 'symbol'].includes(typeof key)) {
|
|
71
|
-
typeError(
|
|
79
|
+
this.typeError('hasAttribute', 'String', 'Number', 'Symbol');
|
|
72
80
|
}
|
|
73
81
|
return key in this;
|
|
74
82
|
}
|
|
@@ -142,7 +150,7 @@ class AstNode {
|
|
|
142
150
|
*/
|
|
143
151
|
toggleAttribute(key, force) {
|
|
144
152
|
if (force !== undefined && typeof force !== 'boolean') {
|
|
145
|
-
typeError(
|
|
153
|
+
this.typeError('toggleAttribute', 'Boolean');
|
|
146
154
|
} else if (this.hasAttribute(key) && typeof this[key] !== 'boolean') {
|
|
147
155
|
throw new RangeError(`${key} 属性的值不为 Boolean!`);
|
|
148
156
|
}
|
|
@@ -175,7 +183,7 @@ class AstNode {
|
|
|
175
183
|
*/
|
|
176
184
|
contains(node) {
|
|
177
185
|
if (!(node instanceof AstNode)) {
|
|
178
|
-
typeError(
|
|
186
|
+
this.typeError('contains', 'Token');
|
|
179
187
|
}
|
|
180
188
|
return node === this || this.childNodes.some(child => child instanceof AstNode && child.contains(node));
|
|
181
189
|
}
|
|
@@ -185,7 +193,7 @@ class AstNode {
|
|
|
185
193
|
if (!Parser.debugging && externalUse('verifyChild')) {
|
|
186
194
|
this.debugOnly('verifyChild');
|
|
187
195
|
} else if (typeof i !== 'number') {
|
|
188
|
-
typeError(
|
|
196
|
+
this.typeError('verifyChild', 'Number');
|
|
189
197
|
}
|
|
190
198
|
const {length} = this.childNodes;
|
|
191
199
|
if (i < -length || i >= length + addition || !Number.isInteger(i)) {
|
|
@@ -216,7 +224,7 @@ class AstNode {
|
|
|
216
224
|
Parser.error('找不到子节点!', node);
|
|
217
225
|
throw new RangeError('找不到子节点!');
|
|
218
226
|
} else if (typeof node === 'string' && childNodes.lastIndexOf(node) > i) {
|
|
219
|
-
throw new RangeError(`重复的纯文本节点 ${node
|
|
227
|
+
throw new RangeError(`重复的纯文本节点 ${noWrap(node)}!`);
|
|
220
228
|
}
|
|
221
229
|
return i;
|
|
222
230
|
}
|
|
@@ -238,7 +246,7 @@ class AstNode {
|
|
|
238
246
|
*/
|
|
239
247
|
insertAt(node, i = this.childNodes.length) {
|
|
240
248
|
if (typeof node !== 'string' && !(node instanceof AstNode)) {
|
|
241
|
-
typeError(
|
|
249
|
+
this.typeError('insertAt', 'String', 'Token');
|
|
242
250
|
} else if (node instanceof AstNode && node.contains(this)) {
|
|
243
251
|
Parser.error('不能插入祖先节点!', node);
|
|
244
252
|
throw new RangeError('不能插入祖先节点!');
|
|
@@ -297,7 +305,7 @@ class AstNode {
|
|
|
297
305
|
/** @param {string} str */
|
|
298
306
|
setText(str, i = 0) {
|
|
299
307
|
if (typeof str !== 'string') {
|
|
300
|
-
typeError(
|
|
308
|
+
this.typeError('setText', 'String');
|
|
301
309
|
}
|
|
302
310
|
this.verifyChild(i);
|
|
303
311
|
const oldText = this.childNodes.at(i);
|
|
@@ -316,7 +324,7 @@ class AstNode {
|
|
|
316
324
|
*/
|
|
317
325
|
splitText(i, offset) {
|
|
318
326
|
if (typeof offset !== 'number') {
|
|
319
|
-
typeError(
|
|
327
|
+
this.typeError('splitText', 'Number');
|
|
320
328
|
}
|
|
321
329
|
this.verifyChild(i);
|
|
322
330
|
const oldText = this.childNodes.at(i);
|
package/mixin/sol.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..'),
|
|
4
|
+
Token = require('../src'); // eslint-disable-line no-unused-vars
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @template T
|
|
8
|
+
* @param {T} constructor
|
|
9
|
+
* @returns {T}
|
|
10
|
+
*/
|
|
11
|
+
const sol = constructor => class extends constructor {
|
|
12
|
+
/** @this {Token} */
|
|
13
|
+
prependNewLine() {
|
|
14
|
+
const {previousVisibleSibling = '', parentNode} = this;
|
|
15
|
+
return (previousVisibleSibling || parentNode?.type !== 'root') && !String(previousVisibleSibling).endsWith('\n')
|
|
16
|
+
? '\n'
|
|
17
|
+
: '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @this {Token} */
|
|
21
|
+
appendNewLine() {
|
|
22
|
+
const {nextVisibleSibling = '', parentNode} = this;
|
|
23
|
+
return (nextVisibleSibling || parentNode?.type !== 'root') && !String(nextVisibleSibling ?? '').startsWith('\n')
|
|
24
|
+
? '\n'
|
|
25
|
+
: '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toString(ownLine = false) {
|
|
29
|
+
return `${this.prependNewLine()}${super.toString()}${ownLine ? this.appendNewLine() : ''}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getPadding() {
|
|
33
|
+
return this.prependNewLine().length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
text(ownLine = false) {
|
|
37
|
+
return `${this.prependNewLine()}${super.text()}${ownLine ? this.appendNewLine() : ''}`;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
Parser.mixins.sol = __filename;
|
|
42
|
+
module.exports = sol;
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} firstChild
|
|
7
|
+
* @param {accum} accum
|
|
8
|
+
*/
|
|
9
|
+
const parseConverter = (firstChild, config = Parser.getConfig(), accum = []) => {
|
|
10
|
+
const ConverterToken = require('../src/converter'),
|
|
11
|
+
regex1 = /-{/g,
|
|
12
|
+
regex2 = /-{|}-/g,
|
|
13
|
+
/** @type {RegExpExecArray[]} */ stack = [];
|
|
14
|
+
let regex = regex1,
|
|
15
|
+
mt = regex.exec(firstChild);
|
|
16
|
+
while (mt) {
|
|
17
|
+
const {0: syntax, index} = mt;
|
|
18
|
+
if (syntax === '}-') {
|
|
19
|
+
const top = stack.pop(),
|
|
20
|
+
{length} = accum,
|
|
21
|
+
str = firstChild.slice(top.index + 2, index),
|
|
22
|
+
i = str.indexOf('|'),
|
|
23
|
+
[flags, text] = i === -1 ? [[], str] : [str.slice(0, i).split(';'), str.slice(i + 1)],
|
|
24
|
+
temp = text.replace(/(?<=&[#a-z0-9]+);/i, '\x01'),
|
|
25
|
+
variants = `(?:${config.variants.join('|')})`,
|
|
26
|
+
rules = temp.split(new RegExp(`;(?=\\s*(?:${variants}|[^;]*?=>\\s*${variants})\\s*:)`))
|
|
27
|
+
.map(rule => rule.replaceAll('\x01', ';'));
|
|
28
|
+
new ConverterToken(flags, rules, config, accum);
|
|
29
|
+
firstChild = `${firstChild.slice(0, top.index)}\x00${length}v\x7f${firstChild.slice(index + 2)}`;
|
|
30
|
+
if (stack.length === 0) {
|
|
31
|
+
regex = regex1;
|
|
32
|
+
}
|
|
33
|
+
regex.lastIndex = top.index + 3 + String(length).length;
|
|
34
|
+
} else {
|
|
35
|
+
stack.push(mt);
|
|
36
|
+
regex = regex2;
|
|
37
|
+
}
|
|
38
|
+
mt = regex.exec(firstChild);
|
|
39
|
+
}
|
|
40
|
+
return firstChild;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
Parser.parsers.parseConverter = __filename;
|
|
44
|
+
module.exports = parseConverter;
|
package/parser/externalLinks.js
CHANGED
|
@@ -10,7 +10,7 @@ const {extUrlChar} = require('../util/string'),
|
|
|
10
10
|
const parseExternalLinks = (firstChild, config = Parser.getConfig(), accum = []) => {
|
|
11
11
|
const ExtLinkToken = require('../src/extLink'),
|
|
12
12
|
regex = new RegExp(
|
|
13
|
-
`\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]
|
|
13
|
+
`\\[((?:${config.protocol}|//)${extUrlChar})(\\p{Zs}*)([^\\]\x01-\x08\x0a-\x1f\ufffd]*)\\]`,
|
|
14
14
|
'gui',
|
|
15
15
|
);
|
|
16
16
|
return firstChild.replace(regex, /** @type {function(...string): string} */ (_, url, space, text) => {
|
package/parser/list.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const /** @type {Parser} */ Parser = require('..');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} text
|
|
7
|
+
* @param {accum} accum
|
|
8
|
+
*/
|
|
9
|
+
const parseList = (text, config = Parser.getConfig(), accum = []) => {
|
|
10
|
+
const mt = text.match(/^(?:[;:*#]|\x00\d+c\x7f)*[;:*#]/);
|
|
11
|
+
if (!mt) {
|
|
12
|
+
return text;
|
|
13
|
+
}
|
|
14
|
+
const ListToken = require('../src/nowiki/list'),
|
|
15
|
+
[prefix] = mt;
|
|
16
|
+
text = `\x00${accum.length}d\x7f${text.slice(prefix.length)}`;
|
|
17
|
+
new ListToken(prefix, config, accum);
|
|
18
|
+
let dt = prefix.split(';').length - 1;
|
|
19
|
+
if (!dt) {
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
const DdToken = require('../src/nowiki/dd');
|
|
23
|
+
let regex = /:+|-{/g,
|
|
24
|
+
ex = regex.exec(text),
|
|
25
|
+
lc = 0;
|
|
26
|
+
while (ex && dt) {
|
|
27
|
+
const {0: syntax, index} = ex;
|
|
28
|
+
if (syntax[0] === ':') {
|
|
29
|
+
if (syntax.length >= dt) {
|
|
30
|
+
new DdToken(':'.repeat(dt), config, accum);
|
|
31
|
+
return `${text.slice(0, index)}\x00${accum.length - 1}d\x7f${text.slice(index + dt)}`;
|
|
32
|
+
}
|
|
33
|
+
text = `${text.slice(0, index)}\x00${accum.length}d\x7f${text.slice(regex.lastIndex)}`;
|
|
34
|
+
dt -= syntax.length;
|
|
35
|
+
regex.lastIndex = index + 4 + String(accum.length).length;
|
|
36
|
+
new DdToken(syntax, config, accum);
|
|
37
|
+
} else if (syntax === '-{') {
|
|
38
|
+
if (!lc) {
|
|
39
|
+
const {lastIndex} = regex;
|
|
40
|
+
regex = /-{|}-/g;
|
|
41
|
+
regex.lastIndex = lastIndex;
|
|
42
|
+
}
|
|
43
|
+
lc++;
|
|
44
|
+
} else {
|
|
45
|
+
lc--;
|
|
46
|
+
if (!lc) {
|
|
47
|
+
const {lastIndex} = regex;
|
|
48
|
+
regex = /:+|-{/g;
|
|
49
|
+
regex.lastIndex = lastIndex;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
ex = regex.exec(text);
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
Parser.parsers.parseList = __filename;
|
|
58
|
+
module.exports = parseList;
|
package/parser/table.js
CHANGED
|
@@ -54,10 +54,10 @@ const parseTable = ({firstChild, type}, config = Parser.getConfig(), accum = [])
|
|
|
54
54
|
}
|
|
55
55
|
const [, closing, row, cell, attr] = matches;
|
|
56
56
|
if (closing) {
|
|
57
|
-
while (top
|
|
57
|
+
while (!(top instanceof TableToken)) {
|
|
58
58
|
top = stack.pop();
|
|
59
59
|
}
|
|
60
|
-
top.close(`\n${spaces}${closing}
|
|
60
|
+
top.close(`\n${spaces}${closing}`, true);
|
|
61
61
|
push(attr, stack.at(-1));
|
|
62
62
|
} else if (row) {
|
|
63
63
|
if (top.type === 'td') {
|
package/printed/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
这里存放以 JSON 格式打印的 AST。
|
package/src/arg.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {text} = require('../util/string'),
|
|
3
|
+
const {text, noWrap} = require('../util/string'),
|
|
4
4
|
/** @type {Parser} */ Parser = require('..'),
|
|
5
5
|
Token = require('.');
|
|
6
6
|
|
|
@@ -75,13 +75,13 @@ class ArgToken extends Token {
|
|
|
75
75
|
|
|
76
76
|
/** @returns {[number, string][]} */
|
|
77
77
|
plain() {
|
|
78
|
-
return this.
|
|
78
|
+
return this.childNodes.length > 1 ? this.children[1].plain() : [];
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** @complexity `n` */
|
|
82
82
|
removeRedundant() {
|
|
83
83
|
Parser.run(() => {
|
|
84
|
-
for (let i = this.
|
|
84
|
+
for (let i = this.childNodes.length - 1; i > 1; i--) {
|
|
85
85
|
super.removeAt(i);
|
|
86
86
|
}
|
|
87
87
|
});
|
|
@@ -100,8 +100,8 @@ class ArgToken extends Token {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/** @param {Token} token */
|
|
103
|
-
insertAt(token, i = this.
|
|
104
|
-
const j = i < 0 ? i + this.
|
|
103
|
+
insertAt(token, i = this.childNodes.length) {
|
|
104
|
+
const j = i < 0 ? i + this.childNodes.length : i;
|
|
105
105
|
if (j > 1 && !Parser.running) {
|
|
106
106
|
throw new RangeError(`${this.constructor.name} 不可插入 arg-redundant 子节点!`);
|
|
107
107
|
}
|
|
@@ -117,8 +117,8 @@ class ArgToken extends Token {
|
|
|
117
117
|
name = String(name);
|
|
118
118
|
const root = Parser.parse(`{{{${name}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
119
119
|
{childNodes: {length}, firstElementChild} = root;
|
|
120
|
-
if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.
|
|
121
|
-
throw new SyntaxError(`非法的参数名称:${name
|
|
120
|
+
if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childNodes.length !== 1) {
|
|
121
|
+
throw new SyntaxError(`非法的参数名称:${noWrap(name)}`);
|
|
122
122
|
}
|
|
123
123
|
const newName = firstElementChild.firstElementChild;
|
|
124
124
|
root.destroy();
|
|
@@ -131,8 +131,8 @@ class ArgToken extends Token {
|
|
|
131
131
|
value = String(value);
|
|
132
132
|
const root = Parser.parse(`{{{|${value}}}}`, this.getAttribute('include'), 2, this.getAttribute('config')),
|
|
133
133
|
{childNodes: {length}, firstElementChild} = root;
|
|
134
|
-
if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.
|
|
135
|
-
throw new SyntaxError(`非法的参数预设值:${value
|
|
134
|
+
if (length !== 1 || firstElementChild?.type !== 'arg' || firstElementChild.childNodes.length !== 2) {
|
|
135
|
+
throw new SyntaxError(`非法的参数预设值:${noWrap(value)}`);
|
|
136
136
|
}
|
|
137
137
|
const [, oldDefault] = this.children,
|
|
138
138
|
newDefault = firstElementChild.lastElementChild;
|
package/src/attribute.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
{toCase, removeComment} = require('../util/string'),
|
|
3
|
+
const {externalUse} = require('../util/debug'),
|
|
4
|
+
{toCase, removeComment, normalizeSpace} = require('../util/string'),
|
|
5
5
|
/** @type {Parser} */ Parser = require('..'),
|
|
6
6
|
Token = require('.');
|
|
7
7
|
|
|
@@ -28,14 +28,13 @@ class AttributeToken extends Token {
|
|
|
28
28
|
) {
|
|
29
29
|
equal = '{{=}}';
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
return [...this.#attr].map(([k, v]) => {
|
|
32
32
|
if (v === true) {
|
|
33
33
|
return k;
|
|
34
34
|
}
|
|
35
35
|
const quote = v.includes('"') ? "'" : '"';
|
|
36
36
|
return `${k}${equal}${quote}${v}${quote}`;
|
|
37
37
|
}).join(' ');
|
|
38
|
-
return str && ` ${str}`;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
/** @complexity `n` */
|
|
@@ -56,17 +55,17 @@ class AttributeToken extends Token {
|
|
|
56
55
|
*/
|
|
57
56
|
#parseAttr() {
|
|
58
57
|
this.#attr.clear();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
let string = this.toString(),
|
|
59
|
+
/** @type {Token & {firstChild: string}} */ token;
|
|
60
|
+
if (this.type !== 'ext-attr' && !Parser.running) {
|
|
61
|
+
const config = this.getAttribute('config'),
|
|
62
|
+
include = this.getAttribute('include');
|
|
63
|
+
token = Parser.run(() => new Token(string, config).parseOnce(0, include).parseOnce());
|
|
64
|
+
string = token.firstChild;
|
|
65
|
+
}
|
|
66
|
+
string = removeComment(string).replace(/\x00\d+~\x7f/g, '=');
|
|
67
|
+
const build = /** @param {string|boolean} str */ str =>
|
|
68
|
+
typeof str === 'boolean' || !token ? str : token.buildFromStr(str).map(String).join('');
|
|
70
69
|
for (const [, key,, quoted, unquoted] of string
|
|
71
70
|
.matchAll(/([^\s/][^\s/=]*)(?:\s*=\s*(?:(["'])(.*?)(?:\2|$)|(\S*)))?/sg)
|
|
72
71
|
) {
|
|
@@ -147,7 +146,7 @@ class AttributeToken extends Token {
|
|
|
147
146
|
/** @param {string} key */
|
|
148
147
|
hasAttr(key) {
|
|
149
148
|
if (typeof key !== 'string') {
|
|
150
|
-
typeError(
|
|
149
|
+
this.typeError('hasAttr', 'String');
|
|
151
150
|
}
|
|
152
151
|
return this.#attr.has(key.toLowerCase().trim());
|
|
153
152
|
}
|
|
@@ -161,7 +160,7 @@ class AttributeToken extends Token {
|
|
|
161
160
|
if (key === undefined) {
|
|
162
161
|
return Object.fromEntries(this.#attr);
|
|
163
162
|
} else if (typeof key !== 'string') {
|
|
164
|
-
typeError(
|
|
163
|
+
this.typeError('getAttr', 'String');
|
|
165
164
|
}
|
|
166
165
|
return this.#attr.get(key.toLowerCase().trim());
|
|
167
166
|
}
|
|
@@ -182,7 +181,7 @@ class AttributeToken extends Token {
|
|
|
182
181
|
setAttr(key, value, init = false) {
|
|
183
182
|
init &&= !externalUse('setAttr');
|
|
184
183
|
if (typeof key !== 'string' || !['string', 'boolean'].includes(typeof value)) {
|
|
185
|
-
typeError(
|
|
184
|
+
this.typeError('setValue', 'String', 'Boolean');
|
|
186
185
|
} else if (!init && this.type === 'ext-attr' && typeof value === 'string' && value.includes('>')) {
|
|
187
186
|
throw new RangeError('扩展标签属性不能包含 ">"!');
|
|
188
187
|
}
|
|
@@ -214,7 +213,7 @@ class AttributeToken extends Token {
|
|
|
214
213
|
*/
|
|
215
214
|
removeAttr(key) {
|
|
216
215
|
if (typeof key !== 'string') {
|
|
217
|
-
typeError(
|
|
216
|
+
this.typeError('removeAttr', 'String');
|
|
218
217
|
}
|
|
219
218
|
key = key.toLowerCase().trim();
|
|
220
219
|
if (this.#attr.delete(key)) {
|
|
@@ -229,7 +228,7 @@ class AttributeToken extends Token {
|
|
|
229
228
|
*/
|
|
230
229
|
toggleAttr(key, force) {
|
|
231
230
|
if (typeof key !== 'string') {
|
|
232
|
-
typeError(
|
|
231
|
+
this.typeError('toggleAttr', 'String');
|
|
233
232
|
} else if (force !== undefined) {
|
|
234
233
|
force = Boolean(force);
|
|
235
234
|
}
|
|
@@ -241,13 +240,24 @@ class AttributeToken extends Token {
|
|
|
241
240
|
this.setAttr(key, force === true || force === undefined && value === false);
|
|
242
241
|
}
|
|
243
242
|
|
|
243
|
+
#leadingSpace(str = super.toString()) {
|
|
244
|
+
return this.type !== 'table-attr' && str && !/^\s/.test(str) ? ' ' : '';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** @this {AttributeToken & Token} */
|
|
244
248
|
toString() {
|
|
245
|
-
const str = super.toString();
|
|
246
|
-
return this
|
|
249
|
+
const str = this.type === 'table-attr' ? normalizeSpace(this) : super.toString();
|
|
250
|
+
return `${this.#leadingSpace(str)}${str}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getPadding() {
|
|
254
|
+
return this.#leadingSpace().length;
|
|
247
255
|
}
|
|
248
256
|
|
|
249
257
|
text() {
|
|
250
|
-
|
|
258
|
+
let str = this.#updateFromAttr();
|
|
259
|
+
str = `${this.#leadingSpace(str)}${str}`;
|
|
260
|
+
return this.type === 'table-attr' ? normalizeSpace(str) : str;
|
|
251
261
|
}
|
|
252
262
|
|
|
253
263
|
/** @returns {[number, string][]} */
|
package/src/converter.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {text} = require('../util/string'),
|
|
4
|
+
/** @type {Parser} */ Parser = require('..'),
|
|
5
|
+
Token = require('.'),
|
|
6
|
+
ConverterFlagsToken = require('./converterFlags'),
|
|
7
|
+
ConverterRuleToken = require('./converterRule');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 转换
|
|
11
|
+
* @classdesc `{childNodes: [ConverterFlagsToken, ...ConverterRuleToken]}`
|
|
12
|
+
*/
|
|
13
|
+
class ConverterToken extends Token {
|
|
14
|
+
type = 'converter';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string[]} flags
|
|
18
|
+
* @param {string[]} rules
|
|
19
|
+
* @param {accum} accum
|
|
20
|
+
*/
|
|
21
|
+
constructor(flags, rules, config = Parser.getConfig(), accum = []) {
|
|
22
|
+
super(undefined, config, false, accum);
|
|
23
|
+
this.append(new ConverterFlagsToken(flags, config, accum));
|
|
24
|
+
if (rules.length) {
|
|
25
|
+
const [firstRule] = rules,
|
|
26
|
+
hasColon = firstRule.includes(':'),
|
|
27
|
+
firstRuleToken = new ConverterRuleToken(firstRule, hasColon, config, accum);
|
|
28
|
+
if (hasColon && firstRuleToken.childNodes.length === 1) {
|
|
29
|
+
this.appendChild(new ConverterRuleToken(rules.join(';'), false, config, accum));
|
|
30
|
+
} else {
|
|
31
|
+
this.append(
|
|
32
|
+
firstRuleToken,
|
|
33
|
+
...rules.slice(1).map(rule => new ConverterRuleToken(rule, true, config, accum)),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.protectChildren(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
cloneNode() {
|
|
41
|
+
const [flags, ...rules] = this.cloneChildren(),
|
|
42
|
+
token = Parser.run(() => new ConverterToken([], [], this.getAttribute('config')));
|
|
43
|
+
token.firstElementChild.safeReplaceWith(flags);
|
|
44
|
+
token.append(...rules);
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toString() {
|
|
49
|
+
const [flags, ...rules] = this.children;
|
|
50
|
+
return `-{${flags.toString()}${flags.childNodes.length ? '|' : ''}${rules.map(String).join(';')}}-`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getPadding() {
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @param {number} i */
|
|
58
|
+
getGaps(i = 0) {
|
|
59
|
+
i = i < 0 ? i + this.childNodes.length : i;
|
|
60
|
+
return i || this.firstElementChild.childNodes.length ? 1 : 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
text() {
|
|
64
|
+
const [flags, ...rules] = this.children;
|
|
65
|
+
return `-{${flags.text()}|${text(rules, ';')}}-`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @returns {[number, string][]} */
|
|
69
|
+
plain() {
|
|
70
|
+
return this.children.slice(1).flatMap(child => child.plain());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
|
|
74
|
+
getAllFlags() {
|
|
75
|
+
return this.firstChild.getAllFlags();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
|
|
79
|
+
getEffectiveFlags() {
|
|
80
|
+
return this.firstChild.getEffectiveFlags();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @this {ConverterToken & {firstChild: ConverterFlagsToken}} */
|
|
84
|
+
getUnknownFlags() {
|
|
85
|
+
return this.firstChild.getUnknownFlags();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @this {ConverterToken & {firstChild: ConverterFlagsToken}}
|
|
90
|
+
* @param {string} flag
|
|
91
|
+
*/
|
|
92
|
+
hasFlag(flag) {
|
|
93
|
+
return this.firstChild.hasFlag(flag);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @this {ConverterToken & {firstChild: ConverterFlagsToken}}
|
|
98
|
+
* @param {string} flag
|
|
99
|
+
*/
|
|
100
|
+
hasEffectiveFlag(flag) {
|
|
101
|
+
return this.firstChild.hasEffectiveFlag(flag);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @this {ConverterToken & {firstChild: ConverterFlagsToken}}
|
|
106
|
+
* @param {string} flag
|
|
107
|
+
*/
|
|
108
|
+
removeFlag(flag) {
|
|
109
|
+
this.firstChild.removeFlag(flag);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @this {ConverterToken & {firstChild: ConverterFlagsToken}}
|
|
114
|
+
* @param {string} flag
|
|
115
|
+
*/
|
|
116
|
+
setFlag(flag) {
|
|
117
|
+
this.firstChild.setFlag(flag);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @this {ConverterToken & {firstChild: ConverterFlagsToken}}
|
|
122
|
+
* @param {string} flag
|
|
123
|
+
*/
|
|
124
|
+
toggleFlag(flag) {
|
|
125
|
+
this.firstChild.toggleFlag(flag);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @this {ConverterToken & {children: [ConverterFlagsToken, ConverterRuleToken]}} */
|
|
129
|
+
get noConvert() {
|
|
130
|
+
return this.childNodes.length < 3 && !this.children[1]?.variant;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
Parser.classes.ConverterToken = __filename;
|
|
135
|
+
module.exports = ConverterToken;
|