wikiparser-node 0.0.1 → 0.1.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 +70 -25
- package/config/default.json +1 -0
- package/config/llwiki.json +1 -0
- package/config/moegirl.json +1 -0
- package/errors/README +1 -0
- package/index.js +43 -9
- package/lib/element.js +70 -12
- package/lib/node.js +17 -9
- package/mixin/sol.js +38 -0
- package/package.json +1 -1
- 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 +3 -3
- package/src/attribute.js +31 -21
- package/src/extLink.js +19 -9
- package/src/heading.js +15 -20
- package/src/html.js +4 -3
- package/src/imageParameter.js +64 -10
- package/src/index.js +25 -14
- package/src/link/file.js +39 -7
- package/src/link/index.js +2 -4
- package/src/magicLink.js +10 -4
- 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 +3 -3
- package/src/table/index.js +35 -30
- package/src/table/td.js +2 -2
- package/src/table/tr.js +5 -11
- package/src/tagPair/index.js +1 -1
- package/src/transclude.js +14 -11
- package/tool/index.js +50 -40
- package/typings/index.d.ts +1 -0
- package/typings/node.d.ts +2 -2
- package/util/debug.js +3 -3
- package/util/string.js +4 -1
- package/src/listToken.js +0 -47
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/wikiparser-node)
|
|
1
2
|
# 目录
|
|
2
3
|
<details>
|
|
3
4
|
<summary>展开</summary>
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
10. [findEnclosingHtml](#token.findenclosinghtml)
|
|
29
30
|
11. [getCategories](#token.getcategories)
|
|
30
31
|
12. [redoQuotes](#token.redoquotes)
|
|
32
|
+
13. [print](#token.print)
|
|
31
33
|
2. [实例属性](#token.instance.properties)
|
|
32
34
|
1. [type](#token.type)
|
|
33
35
|
3. [原型属性](#token.prototype.properties)
|
|
@@ -161,17 +163,18 @@
|
|
|
161
163
|
6. [getKeys](#filetoken.getkeys)
|
|
162
164
|
7. [getValue](#filetoken.getvalue)
|
|
163
165
|
8. [setValue](#filetoken.setvalue)
|
|
166
|
+
2. [原型属性](#filetoken.prototype.properties)
|
|
167
|
+
1. [link](#filetoken.link)
|
|
168
|
+
2. [size](#filetoken.size)
|
|
169
|
+
3. [width](#filetoken.width)
|
|
170
|
+
4. [height](#filetoken.height)
|
|
164
171
|
18. [ImageParameterToken](#imageparametertoken)
|
|
165
172
|
1. [原型方法](#imageparametertoken.prototype.methods)
|
|
166
173
|
1. [getValue](#imageparametertoken.getvalue)
|
|
167
174
|
2. [setValue](#imageparametertoken.setvalue)
|
|
168
175
|
19. [ExtLinkToken](#extlinktoken)
|
|
169
176
|
1. [原型方法](#extlinktoken.prototype.methods)
|
|
170
|
-
1. [
|
|
171
|
-
2. [setTarget](#extlinktoken.settarget)
|
|
172
|
-
3. [setLinkText](#extlinktoken.setlinktext)
|
|
173
|
-
2. [原型属性](#extlinktoken.prototype.properties)
|
|
174
|
-
1. [protocol](#extlinktoken.protocol)
|
|
177
|
+
1. [setLinkText](#extlinktoken.setlinktext)
|
|
175
178
|
20. [MagicLinkToken](#magiclinktoken)
|
|
176
179
|
1. [原型方法](#magiclinktoken.prototype.methods)
|
|
177
180
|
1. [getUrl](#magiclinktoken.geturl)
|
|
@@ -424,6 +427,15 @@ assert.deepStrictEqual(root.childNodes, ["'", root.firstElementChild, "a", root.
|
|
|
424
427
|
var root = Parser.parse(wikitext);
|
|
425
428
|
assert(root.type === 'root');
|
|
426
429
|
```
|
|
430
|
+
|
|
431
|
+
**print**(format?: 'markup'\|'json' = 'markup'): void\|object<a id="token.print"></a>
|
|
432
|
+
- 打印解析生成的 AST。
|
|
433
|
+
|
|
434
|
+
```js
|
|
435
|
+
var root = Parser.parse("<ref>{{T|<br>\n----\n[[File:F|thumb|''[//example.net]'']]}}</ref>");
|
|
436
|
+
root.print();
|
|
437
|
+
root.print('json', 'example'); // JSON格式的输出结果将保存至 /printed/example.json 文件
|
|
438
|
+
```
|
|
427
439
|
</details>
|
|
428
440
|
|
|
429
441
|
## 原型属性<a id="token.prototype.properties"></a>
|
|
@@ -928,7 +940,7 @@ param.setValue(' 2 ');
|
|
|
928
940
|
assert(root.toString() === '{{a|b= 2 }}'); // setValue方法总是保留空白字符,哪怕是无效的
|
|
929
941
|
```
|
|
930
942
|
|
|
931
|
-
**rename**(key: string, force
|
|
943
|
+
**rename**(key: string, force?: boolean = false): void<a id="parametertoken.rename"></a>
|
|
932
944
|
- 重命名参数,可选是否在导致重复参数时抛出错误。
|
|
933
945
|
|
|
934
946
|
```js
|
|
@@ -1162,7 +1174,7 @@ assert(root.toString() === '{|\n!colspan=2|\n|-\n| \n!\n|}');
|
|
|
1162
1174
|
|:-:|:-:|:-:|
|
|
1163
1175
|
|<table><tr><td colspan=2>td</td></tr><tr><td>td</td><td>td</td></tr></table>|<table><tr><td colspan=2>td</td></tr><tr><td>td</td><th>th</th></tr></table>|<table><tr><th colspan=2>th</th></tr><tr><td>td</td><th>th</th></tr></table>|
|
|
1164
1176
|
|
|
1165
|
-
**insertTableRow**(row: number, attr: Record\<string, string\|boolean>, inner?: string, subtype?: 'td'\|'th', innerAttr?: Record\<string, string\|boolean>): TrToken<a id="tabletoken.inserttablerow"></a>
|
|
1177
|
+
**insertTableRow**(row: number, attr: Record\<string, string\|boolean>, inner?: string, subtype?: 'td'\|'th' = 'td', innerAttr?: Record\<string, string\|boolean>): TrToken<a id="tabletoken.inserttablerow"></a>
|
|
1166
1178
|
- 插入空行或一行单元格。
|
|
1167
1179
|
|
|
1168
1180
|
```js
|
|
@@ -1177,7 +1189,7 @@ assert(root.toString() === '{|\n|a|| rowspan="3"|b||c\n|- class="tr"\n|-\n! clas
|
|
|
1177
1189
|
|:-:|:-:|
|
|
1178
1190
|
|<table><tr><td>a</td><td rowspan=2>b</td><td>c</td></tr><tr><td>d</td><td>e</td></tr></table>|<table><tr><td>a</td><td rowspan=3>b</td><td>c</td></tr><tr><th>f</th><th>f</th></tr><tr><td>d</td><td>e</td></tr></table>|
|
|
1179
1191
|
|
|
1180
|
-
**insertTableCol**(x: number, inner: string, subtype
|
|
1192
|
+
**insertTableCol**(x: number, inner: string, subtype?: 'td'\|'th' = 'td', attr?: Record\<string, string\|boolean>): void<a id="tabletoken.inserttablecol"></a>
|
|
1181
1193
|
- 插入一列单元格。
|
|
1182
1194
|
|
|
1183
1195
|
```js
|
|
@@ -1191,7 +1203,7 @@ assert(root.toString() === '{|\n| colspan="3"|a\n|-\n|b\n! class="th"|d\n|c\n|}'
|
|
|
1191
1203
|
|:-:|:-:|
|
|
1192
1204
|
|<table><tr><td colspan=2 align="center">a</td></tr><tr><td>b</td><td>c</td></tr></table>|<table><tr><td colspan=3 align="center">a</td></tr><tr><td>b</td><th>d</th><td>c</td></tr></table>|
|
|
1193
1205
|
|
|
1194
|
-
**insertTableCell**(inner: string, coords: {row: number, column: number}\|{x: number, y: number}, subtype
|
|
1206
|
+
**insertTableCell**(inner: string, coords: {row: number, column: number}\|{x: number, y: number}, subtype?: 'td'\|'th' = 'td', attr?: Record\<string, string\|boolean>): [TdToken](#tdtoken)<a id="tabletoken.inserttablecell"></a>
|
|
1195
1207
|
- 插入一个单元格。
|
|
1196
1208
|
|
|
1197
1209
|
```js
|
|
@@ -1469,7 +1481,7 @@ link.asSelfLink();
|
|
|
1469
1481
|
assert(root.toString() === '[[#b]]');
|
|
1470
1482
|
```
|
|
1471
1483
|
|
|
1472
|
-
**setLinkText**(linkText
|
|
1484
|
+
**setLinkText**(linkText: string)<a id="linktoken.setlinktext"></a>
|
|
1473
1485
|
- 修改链接文本。
|
|
1474
1486
|
|
|
1475
1487
|
```js
|
|
@@ -1654,6 +1666,53 @@ assert(root.toString() === '[[file:a|100px|framed]]');
|
|
|
1654
1666
|
```
|
|
1655
1667
|
</details>
|
|
1656
1668
|
|
|
1669
|
+
## 原型属性<a id="filetoken.prototype.properties"></a>
|
|
1670
|
+
<details>
|
|
1671
|
+
<summary>展开</summary>
|
|
1672
|
+
|
|
1673
|
+
**link**: string\|symbol<a id="filetoken.link"></a>
|
|
1674
|
+
- 链接目标。
|
|
1675
|
+
|
|
1676
|
+
```js
|
|
1677
|
+
var root = Parser.parse('[[file:a|link=[[talk:b]]]]'),
|
|
1678
|
+
file = root.firstChild;
|
|
1679
|
+
assert(file.link === 'Talk:B');
|
|
1680
|
+
file.link = '//c';
|
|
1681
|
+
assert(root.toString() === '[[file:a|link=//c]]');
|
|
1682
|
+
```
|
|
1683
|
+
|
|
1684
|
+
**size**: {width: string, height: string}<a id="filetoken.size"></a>
|
|
1685
|
+
- 图片尺寸。
|
|
1686
|
+
|
|
1687
|
+
```js
|
|
1688
|
+
var root = Parser.parse('[[file:a|x1px]]'),
|
|
1689
|
+
file = root.firstChild;
|
|
1690
|
+
assert.deepStrictEqual(file.size, {width: '', height: '1'});
|
|
1691
|
+
```
|
|
1692
|
+
|
|
1693
|
+
**width**: number<a id="filetoken.width"></a>
|
|
1694
|
+
- 图片宽度。
|
|
1695
|
+
|
|
1696
|
+
```js
|
|
1697
|
+
var root = Parser.parse('[[file:a|1x1px]]'),
|
|
1698
|
+
file = root.firstChild;
|
|
1699
|
+
assert(file.width === '1');
|
|
1700
|
+
file.width = undefined;
|
|
1701
|
+
assert(root.toString() === '[[file:a|x1px]]');
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
**height**: number<a id="filetoken.height"></a>
|
|
1705
|
+
- 图片高度。
|
|
1706
|
+
|
|
1707
|
+
```js
|
|
1708
|
+
var root = Parser.parse('[[file:a|1x1px]]'),
|
|
1709
|
+
file = root.firstChild;
|
|
1710
|
+
assert(file.height === '1');
|
|
1711
|
+
file.height = undefined;
|
|
1712
|
+
assert(root.toString() === '[[file:a|1px]]');
|
|
1713
|
+
```
|
|
1714
|
+
</details>
|
|
1715
|
+
|
|
1657
1716
|
[返回目录](#目录)
|
|
1658
1717
|
|
|
1659
1718
|
# ImageParameterToken
|
|
@@ -1688,18 +1747,12 @@ assert(root.toString() === '[[file:a|x100px]]');
|
|
|
1688
1747
|
[返回目录](#目录)
|
|
1689
1748
|
|
|
1690
1749
|
# ExtLinkToken
|
|
1691
|
-
`[]
|
|
1750
|
+
`[]`内的外部链接。这个类同时混合了 [MagicLinkToken](#magiclinktoken) 类的方法和原型属性。
|
|
1692
1751
|
|
|
1693
1752
|
## 原型方法<a id="extlinktoken.prototype.methods"></a>
|
|
1694
1753
|
<details>
|
|
1695
1754
|
<summary>展开</summary>
|
|
1696
1755
|
|
|
1697
|
-
**getUrl**(): URL<a id="extlinktoken.geturl"></a>
|
|
1698
|
-
- 混合自 [MagicLinkToken.getUrl](#magiclinktoken.geturl)。
|
|
1699
|
-
|
|
1700
|
-
**setTarget**(url: string\|URL): void<a id="extlinktoken.settarget"></a>
|
|
1701
|
-
- 混合自 [MagicLinkToken.setTarget](#magiclinktoken.settarget)。
|
|
1702
|
-
|
|
1703
1756
|
**setLinkText**(text: string): void<a id="extlinktoken.setlinktext"></a>
|
|
1704
1757
|
- 修改外链文本。
|
|
1705
1758
|
|
|
@@ -1711,14 +1764,6 @@ assert(root.toString() === '[//example.org]');
|
|
|
1711
1764
|
```
|
|
1712
1765
|
</details>
|
|
1713
1766
|
|
|
1714
|
-
## 原型属性<a id="extlinktoken.prototype.properties"></a>
|
|
1715
|
-
<details>
|
|
1716
|
-
<summary>展开</summary>
|
|
1717
|
-
|
|
1718
|
-
**protocol**: string<a id="extlinktoken.protocol"></a>
|
|
1719
|
-
- 混合自 [MagicLinkToken.protocol](#magiclinktoken.protocol)。
|
|
1720
|
-
</details>
|
|
1721
|
-
|
|
1722
1767
|
[返回目录](#目录)
|
|
1723
1768
|
|
|
1724
1769
|
# MagicLinkToken
|
package/config/default.json
CHANGED
package/config/llwiki.json
CHANGED
package/config/moegirl.json
CHANGED
package/errors/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
这里记录解析失败时处于半解析状态的维基文本以及错误信息。
|
package/index.js
CHANGED
|
@@ -76,6 +76,7 @@ const /** @type {Parser} */ Parser = {
|
|
|
76
76
|
['QuoteToken'],
|
|
77
77
|
['ExtLinkToken'],
|
|
78
78
|
['MagicLinkToken'],
|
|
79
|
+
['ListToken', 'DdToken'],
|
|
79
80
|
],
|
|
80
81
|
|
|
81
82
|
config: './config/default',
|
|
@@ -108,7 +109,7 @@ const /** @type {Parser} */ Parser = {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
};
|
|
111
|
-
|
|
112
|
+
this.run(() => {
|
|
112
113
|
build(['title', 'fragment']);
|
|
113
114
|
});
|
|
114
115
|
}
|
|
@@ -119,25 +120,58 @@ const /** @type {Parser} */ Parser = {
|
|
|
119
120
|
|
|
120
121
|
parse(wikitext, include = false, maxStage = Parser.MAX_STAGE, config = Parser.getConfig()) {
|
|
121
122
|
const Token = require('./src');
|
|
123
|
+
let token;
|
|
122
124
|
this.run(() => {
|
|
123
125
|
if (typeof wikitext === 'string') {
|
|
124
|
-
|
|
125
|
-
} else if (
|
|
126
|
+
token = new Token(wikitext, config);
|
|
127
|
+
} else if (wikitext instanceof Token) {
|
|
128
|
+
token = wikitext;
|
|
129
|
+
wikitext = token.toString();
|
|
130
|
+
} else {
|
|
126
131
|
throw new TypeError('待解析的内容应为 String 或 Token!');
|
|
127
132
|
}
|
|
128
133
|
try {
|
|
129
|
-
|
|
134
|
+
token.parse(maxStage, include);
|
|
130
135
|
} catch (e) {
|
|
131
136
|
if (e instanceof Error) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
137
|
+
const file = `${__dirname}/errors/${new Date().toISOString()}`,
|
|
138
|
+
stage = token.getAttribute('stage');
|
|
139
|
+
fs.writeFileSync(file, stage === this.MAX_STAGE ? wikitext : token.toString());
|
|
140
|
+
fs.writeFileSync(`${file}.err`, e.stack);
|
|
141
|
+
fs.writeFileSync(`${file}.json`, JSON.stringify({
|
|
142
|
+
stage, include: token.getAttribute('include'), config: this.config,
|
|
143
|
+
}, null, '\t'));
|
|
136
144
|
}
|
|
137
145
|
throw e;
|
|
138
146
|
}
|
|
139
147
|
});
|
|
140
|
-
return
|
|
148
|
+
return token;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
reparse(date) {
|
|
152
|
+
const path = `${__dirname}/errors/`,
|
|
153
|
+
main = fs.readdirSync(path).find(name => name.startsWith(date) && name.endsWith('Z'));
|
|
154
|
+
if (!main) {
|
|
155
|
+
throw new RangeError(`找不到对应时间戳的错误记录:${date}`);
|
|
156
|
+
}
|
|
157
|
+
const Token = require('./src'),
|
|
158
|
+
file = `${path}${main}`,
|
|
159
|
+
wikitext = fs.readFileSync(file, 'utf8'),
|
|
160
|
+
{stage, include, config} = require(`${file}.json`);
|
|
161
|
+
this.config = config;
|
|
162
|
+
return this.run(() => {
|
|
163
|
+
const halfParsed = stage < this.MAX_STAGE,
|
|
164
|
+
token = new Token(wikitext, this.getConfig(), halfParsed);
|
|
165
|
+
if (halfParsed) {
|
|
166
|
+
token.setAttribute('stage', stage).parseOnce(stage, include);
|
|
167
|
+
} else {
|
|
168
|
+
token.parse(undefined, include);
|
|
169
|
+
}
|
|
170
|
+
fs.unlinkSync(file);
|
|
171
|
+
fs.unlinkSync(`${file}.err`);
|
|
172
|
+
fs.unlinkSync(`${file}.json`);
|
|
173
|
+
return token;
|
|
174
|
+
});
|
|
141
175
|
},
|
|
142
176
|
|
|
143
177
|
getTool() {
|
package/lib/element.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
{
|
|
3
|
+
const fs = require('fs'),
|
|
4
|
+
{externalUse} = require('../util/debug'),
|
|
5
|
+
{toCase, noWrap} = require('../util/string'),
|
|
5
6
|
{nth} = require('./ranges'),
|
|
6
7
|
EventEmitter = require('events'),
|
|
7
8
|
AstNode = require('./node'),
|
|
@@ -93,7 +94,7 @@ class AstElement extends AstNode {
|
|
|
93
94
|
this.addEventListener(type, listener, options);
|
|
94
95
|
}
|
|
95
96
|
} else if (typeof types !== 'string' || typeof listener !== 'function') {
|
|
96
|
-
typeError(
|
|
97
|
+
this.typeError('addEventListener', 'String', 'Function');
|
|
97
98
|
} else {
|
|
98
99
|
this.#events[options?.once ? 'once' : 'on'](types, listener);
|
|
99
100
|
}
|
|
@@ -109,7 +110,7 @@ class AstElement extends AstNode {
|
|
|
109
110
|
this.removeEventListener(type, listener);
|
|
110
111
|
}
|
|
111
112
|
} else if (typeof types !== 'string' || typeof listener !== 'function') {
|
|
112
|
-
typeError(
|
|
113
|
+
this.typeError('removeEventListener', 'String', 'Function');
|
|
113
114
|
} else {
|
|
114
115
|
this.#events.off(types, listener);
|
|
115
116
|
}
|
|
@@ -122,7 +123,7 @@ class AstElement extends AstNode {
|
|
|
122
123
|
this.removeAllEventListeners(type);
|
|
123
124
|
}
|
|
124
125
|
} else if (types !== undefined && typeof types !== 'string') {
|
|
125
|
-
typeError(
|
|
126
|
+
this.typeError('removeAllEventListeners', 'String');
|
|
126
127
|
} else {
|
|
127
128
|
this.#events.removeAllListeners(types);
|
|
128
129
|
}
|
|
@@ -134,7 +135,7 @@ class AstElement extends AstNode {
|
|
|
134
135
|
*/
|
|
135
136
|
listEventListeners(type) {
|
|
136
137
|
if (typeof type !== 'string') {
|
|
137
|
-
typeError(
|
|
138
|
+
this.typeError('listEventListeners', 'String');
|
|
138
139
|
}
|
|
139
140
|
return this.#events.listeners(type);
|
|
140
141
|
}
|
|
@@ -145,7 +146,7 @@ class AstElement extends AstNode {
|
|
|
145
146
|
*/
|
|
146
147
|
dispatchEvent(e, data) {
|
|
147
148
|
if (!(e instanceof Event)) {
|
|
148
|
-
typeError(
|
|
149
|
+
this.typeError('dispatchEvent', 'Event');
|
|
149
150
|
} else if (!e.target) { // 初始化
|
|
150
151
|
Object.defineProperty(e, 'target', {value: this, enumerable: true});
|
|
151
152
|
e.stopPropagation = function() {
|
|
@@ -357,7 +358,7 @@ class AstElement extends AstNode {
|
|
|
357
358
|
*/
|
|
358
359
|
matches(selector = '', simple = false) {
|
|
359
360
|
if (typeof selector !== 'string') {
|
|
360
|
-
typeError(
|
|
361
|
+
this.typeError('matches', 'String');
|
|
361
362
|
} else if (!selector.trim()) {
|
|
362
363
|
return true;
|
|
363
364
|
}
|
|
@@ -479,7 +480,7 @@ class AstElement extends AstNode {
|
|
|
479
480
|
*/
|
|
480
481
|
comparePosition(other) {
|
|
481
482
|
if (!(other instanceof AstElement)) {
|
|
482
|
-
typeError(
|
|
483
|
+
this.typeError('comparePosition', 'AstElement');
|
|
483
484
|
} else if (this === other) {
|
|
484
485
|
return 0;
|
|
485
486
|
} else if (this.contains(other)) {
|
|
@@ -541,7 +542,7 @@ class AstElement extends AstNode {
|
|
|
541
542
|
*/
|
|
542
543
|
posFromIndex(index) {
|
|
543
544
|
if (typeof index !== 'number') {
|
|
544
|
-
typeError(
|
|
545
|
+
this.typeError('posFromIndex', 'Number');
|
|
545
546
|
}
|
|
546
547
|
const text = this.toString();
|
|
547
548
|
if (index < -text.length || index >= text.length || !Number.isInteger(index)) {
|
|
@@ -558,7 +559,7 @@ class AstElement extends AstNode {
|
|
|
558
559
|
*/
|
|
559
560
|
indexFromPos(top, left) {
|
|
560
561
|
if (typeof top !== 'number' || typeof left !== 'number') {
|
|
561
|
-
typeError(
|
|
562
|
+
this.typeError('indexFromPos', 'Number');
|
|
562
563
|
} else if (top < 0 || left < 0 || !Number.isInteger(top) || !Number.isInteger(left)) {
|
|
563
564
|
return;
|
|
564
565
|
}
|
|
@@ -582,7 +583,7 @@ class AstElement extends AstNode {
|
|
|
582
583
|
*/
|
|
583
584
|
getRelativeIndex(j) {
|
|
584
585
|
if (j !== undefined && typeof j !== 'number') {
|
|
585
|
-
typeError(
|
|
586
|
+
this.typeError('getRelativeIndex', 'Number');
|
|
586
587
|
}
|
|
587
588
|
let /** @type {(string|this)[]} */ childNodes;
|
|
588
589
|
/**
|
|
@@ -684,6 +685,63 @@ class AstElement extends AstNode {
|
|
|
684
685
|
return node ? [[index + this.getRelativeIndex(i), node]] : [];
|
|
685
686
|
});
|
|
686
687
|
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* @template {'markup'|'json'} T
|
|
691
|
+
* @param {T} format
|
|
692
|
+
* @param {T extends 'markup' ? number : string} depth
|
|
693
|
+
* @returns {T extends 'markup' ? void : Record<string, any>}
|
|
694
|
+
*/
|
|
695
|
+
print(format = 'markup', depth = 0) {
|
|
696
|
+
if (format === 'json') {
|
|
697
|
+
const {childNodes, ...prop} = this,
|
|
698
|
+
json = {
|
|
699
|
+
...prop,
|
|
700
|
+
childNodes: childNodes.map(child => typeof child === 'string' ? child : child.print('json')),
|
|
701
|
+
};
|
|
702
|
+
if (typeof depth === 'string') {
|
|
703
|
+
fs.writeFileSync(
|
|
704
|
+
`${__dirname.slice(0, -3)}printed/${depth}${depth.endsWith('.json') ? '' : '.json'}`,
|
|
705
|
+
JSON.stringify(json, null, 2),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
return json;
|
|
709
|
+
} else if (typeof depth !== 'number') {
|
|
710
|
+
this.typeError('print', 'Number');
|
|
711
|
+
}
|
|
712
|
+
const indent = ' '.repeat(depth),
|
|
713
|
+
str = this.toString(),
|
|
714
|
+
{childNodes, type, firstChild} = this,
|
|
715
|
+
{length} = childNodes;
|
|
716
|
+
if (!str || length === 0 || typeof firstChild === 'string' && firstChild === str) {
|
|
717
|
+
console.log(`${indent}\x1b[32m<%s>\x1b[0m${noWrap(str)}\x1b[32m</%s>\x1b[0m`, type, type);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
Parser.info(`${indent}<${type}>`);
|
|
721
|
+
let i = this.getPadding();
|
|
722
|
+
if (i) {
|
|
723
|
+
console.log(`${indent} ${noWrap(str.slice(0, i))}`);
|
|
724
|
+
}
|
|
725
|
+
for (const [j, child] of childNodes.entries()) {
|
|
726
|
+
const childStr = String(child),
|
|
727
|
+
gap = j === length - 1 ? 0 : this.getGaps(j);
|
|
728
|
+
if (!childStr) {
|
|
729
|
+
// pass
|
|
730
|
+
} else if (typeof child === 'string') {
|
|
731
|
+
console.log(`${indent} ${noWrap(child)}`);
|
|
732
|
+
} else {
|
|
733
|
+
child.print('markup', depth + 1);
|
|
734
|
+
}
|
|
735
|
+
i += childStr.length + gap;
|
|
736
|
+
if (gap) {
|
|
737
|
+
console.log(`${indent} ${noWrap(str.slice(i - gap, i))}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (i < str.length) {
|
|
741
|
+
console.log(`${indent} ${noWrap(str.slice(i))}`);
|
|
742
|
+
}
|
|
743
|
+
Parser.info(`${indent}</${type}>`);
|
|
744
|
+
}
|
|
687
745
|
}
|
|
688
746
|
|
|
689
747
|
Parser.classes.AstElement = __filename;
|
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,38 @@
|
|
|
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} = this;
|
|
15
|
+
return previousVisibleSibling && !String(previousVisibleSibling).endsWith('\n') ? '\n' : '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @this {Token} */
|
|
19
|
+
appendNewLine() {
|
|
20
|
+
const {nextVisibleSibling} = this;
|
|
21
|
+
return nextVisibleSibling && !String(nextVisibleSibling).startsWith('\n') ? '\n' : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
toString(ownLine = false) {
|
|
25
|
+
return `${this.prependNewLine()}${super.toString()}${ownLine ? this.appendNewLine() : ''}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getPadding() {
|
|
29
|
+
return this.prependNewLine().length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
text(ownLine = false) {
|
|
33
|
+
return `${this.prependNewLine()}${super.text()}${ownLine ? this.appendNewLine() : ''}`;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
Parser.mixins.sol = __filename;
|
|
38
|
+
module.exports = sol;
|
package/package.json
CHANGED
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;
|