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 CHANGED
@@ -1,3 +1,4 @@
1
+ [![npm version](https://badge.fury.io/js/wikiparser-node.svg)](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. [getUrl](#extlinktoken.geturl)
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: boolean): void<a id="parametertoken.rename"></a>
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: 'td'\|'th', attr: Record\<string, string\|boolean>): void<a id="tabletoken.inserttablecol"></a>
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: 'td'\|'th', attr: Record\<string, string\|boolean>): [TdToken](#tdtoken)<a id="tabletoken.inserttablecell"></a>
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?: string)<a id="linktoken.setlinktext"></a>
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
@@ -119,6 +119,7 @@
119
119
  "829": "Module talk"
120
120
  },
121
121
  "nsid": {
122
+ "": 0,
122
123
  "media": -2,
123
124
  "媒体": -2,
124
125
  "媒体文件": -2,
@@ -110,6 +110,7 @@
110
110
  "829": "Module talk"
111
111
  },
112
112
  "nsid": {
113
+ "": 0,
113
114
  "media": -2,
114
115
  "媒体": -2,
115
116
  "媒体文件": -2,
@@ -114,6 +114,7 @@
114
114
  "829": "Module talk"
115
115
  },
116
116
  "nsid": {
117
+ "": 0,
117
118
  "media": -2,
118
119
  "媒体": -2,
119
120
  "媒体文件": -2,
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
- Parser.run(() => {
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
- wikitext = new Token(wikitext, config);
125
- } else if (!(wikitext instanceof Token)) {
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
- wikitext.parse(maxStage, include);
134
+ token.parse(maxStage, include);
130
135
  } catch (e) {
131
136
  if (e instanceof Error) {
132
- fs.writeFileSync(
133
- `${__dirname}/errors/${new Date().toISOString()}`,
134
- `${e.stack}\n\n\n${wikitext.toString()}`,
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 wikitext;
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 {typeError, externalUse} = require('../util/debug'),
4
- {toCase} = require('../util/string'),
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(this, 'addEventListener', 'String', 'Function');
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(this, 'removeEventListener', 'String', 'Function');
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(this, 'removeAllEventListeners', 'String');
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(this, 'listEventListeners', 'String');
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(this, 'dispatchEvent', 'Event');
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(this, 'matches', 'String');
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(this, 'comparePosition', 'AstElement');
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(this, 'posFromIndex', 'Number');
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(this, 'indexFromPos', 'Number');
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(this, 'getRelativeIndex', 'Number');
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(this, 'hasAttribute', 'String', 'Number', 'Symbol');
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(this, 'toggleAttribute', 'Boolean');
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(this, 'contains', 'Token');
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(this, 'verifyChild', 'Number');
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.replaceAll('\n', '\\n')}!`);
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(this, 'insertAt', 'String', 'Token');
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(this, 'setText', 'String');
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(this, 'splitText', 'Number');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikiparser-node",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "A Node.js parser for MediaWiki markup with AST",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -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}*)([^\\]\\x01-\\x08\\x0a-\\x1f\\ufffd]*)\\]`,
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;