wikiparser-node 0.3.1 → 0.5.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 (80) hide show
  1. package/README.md +1 -1
  2. package/config/default.json +13 -17
  3. package/config/llwiki.json +11 -79
  4. package/config/moegirl.json +7 -1
  5. package/config/zhwiki.json +1269 -0
  6. package/index.js +130 -97
  7. package/lib/element.js +410 -518
  8. package/lib/node.js +493 -115
  9. package/lib/ranges.js +27 -19
  10. package/lib/text.js +175 -0
  11. package/lib/title.js +14 -6
  12. package/mixin/attributeParent.js +70 -24
  13. package/mixin/fixedToken.js +18 -10
  14. package/mixin/hidden.js +6 -4
  15. package/mixin/sol.js +39 -12
  16. package/package.json +17 -4
  17. package/parser/brackets.js +18 -18
  18. package/parser/commentAndExt.js +16 -14
  19. package/parser/converter.js +14 -13
  20. package/parser/externalLinks.js +12 -11
  21. package/parser/hrAndDoubleUnderscore.js +24 -14
  22. package/parser/html.js +8 -7
  23. package/parser/links.js +13 -13
  24. package/parser/list.js +12 -11
  25. package/parser/magicLinks.js +11 -10
  26. package/parser/quotes.js +6 -5
  27. package/parser/selector.js +175 -0
  28. package/parser/table.js +31 -24
  29. package/src/arg.js +91 -43
  30. package/src/atom/hidden.js +5 -2
  31. package/src/atom/index.js +17 -9
  32. package/src/attribute.js +210 -101
  33. package/src/converter.js +78 -43
  34. package/src/converterFlags.js +104 -45
  35. package/src/converterRule.js +136 -78
  36. package/src/extLink.js +81 -27
  37. package/src/gallery.js +63 -20
  38. package/src/heading.js +58 -20
  39. package/src/html.js +138 -48
  40. package/src/imageParameter.js +93 -58
  41. package/src/index.js +314 -186
  42. package/src/link/category.js +22 -54
  43. package/src/link/file.js +83 -32
  44. package/src/link/galleryImage.js +21 -7
  45. package/src/link/index.js +170 -81
  46. package/src/magicLink.js +64 -14
  47. package/src/nowiki/comment.js +36 -10
  48. package/src/nowiki/dd.js +37 -22
  49. package/src/nowiki/doubleUnderscore.js +21 -7
  50. package/src/nowiki/hr.js +11 -7
  51. package/src/nowiki/index.js +16 -9
  52. package/src/nowiki/list.js +2 -2
  53. package/src/nowiki/noinclude.js +8 -4
  54. package/src/nowiki/quote.js +38 -7
  55. package/src/onlyinclude.js +24 -7
  56. package/src/parameter.js +102 -62
  57. package/src/syntax.js +23 -20
  58. package/src/table/index.js +282 -174
  59. package/src/table/td.js +112 -61
  60. package/src/table/tr.js +135 -74
  61. package/src/tagPair/ext.js +30 -23
  62. package/src/tagPair/include.js +26 -11
  63. package/src/tagPair/index.js +72 -29
  64. package/src/transclude.js +235 -127
  65. package/tool/index.js +42 -32
  66. package/util/debug.js +21 -18
  67. package/util/diff.js +76 -0
  68. package/util/lint.js +40 -0
  69. package/util/string.js +56 -26
  70. package/.eslintrc.json +0 -319
  71. package/errors/README +0 -1
  72. package/jsconfig.json +0 -7
  73. package/printed/README +0 -1
  74. package/typings/element.d.ts +0 -28
  75. package/typings/index.d.ts +0 -52
  76. package/typings/node.d.ts +0 -23
  77. package/typings/parser.d.ts +0 -9
  78. package/typings/table.d.ts +0 -14
  79. package/typings/token.d.ts +0 -22
  80. package/typings/tool.d.ts +0 -10
package/tool/index.js CHANGED
@@ -1,7 +1,9 @@
1
+ /* eslint no-underscore-dangle: [2, {allowAfterThis: true, enforceInMethodNames: false, allow: ['__filename']}] */
1
2
  'use strict';
2
3
 
3
4
  const {typeError, externalUse} = require('../util/debug'),
4
5
  {text, noWrap} = require('../util/string'),
6
+ AstText = require('../lib/text'),
5
7
  Token = require('../src'),
6
8
  assert = require('assert/strict');
7
9
 
@@ -9,7 +11,7 @@ const /** @type {WeakMap<Token, Record<string, any>>} */ dataStore = new WeakMap
9
11
 
10
12
  /**
11
13
  * @param {string} method
12
- * @param {string|Token|Token[]} selector
14
+ * @param {AstText|Token|Token[]} selector
13
15
  * @returns {(token: Token) => boolean}
14
16
  */
15
17
  const matchesGenerator = (method, selector) => {
@@ -20,7 +22,7 @@ const matchesGenerator = (method, selector) => {
20
22
  } else if (selector instanceof Token) {
21
23
  return token => token === selector;
22
24
  }
23
- typeError(TokenCollection, method, 'String', 'Token', 'Array');
25
+ return typeError(TokenCollection, method, 'String', 'Token', 'Array'); // eslint-disable-line no-use-before-define
24
26
  };
25
27
 
26
28
  /** @extends {Array<Token>} */
@@ -37,10 +39,10 @@ class TokenCollection extends Array {
37
39
  for (const token of arr) {
38
40
  if (token === undefined) {
39
41
  continue;
40
- } else if (typeof token === 'string') {
42
+ } else if (token instanceof AstText) {
41
43
  this.push(token);
42
44
  } else if (!(token instanceof Token)) {
43
- this.typeError('constructor', 'String', 'Token');
45
+ this.typeError('constructor', 'AstText', 'Token');
44
46
  } else if (!this.includes(token)) {
45
47
  this.#roots.add(token.getRootNode());
46
48
  this.push(token);
@@ -58,20 +60,21 @@ class TokenCollection extends Array {
58
60
  return typeError(this.constructor, method, ...types);
59
61
  }
60
62
 
63
+ /** 节点排序 */
61
64
  #sort() {
62
- if (this.some(token => typeof token === 'string')) {
65
+ if (this.some(({type}) => type === 'text')) {
63
66
  return;
64
67
  }
65
68
  const rootArray = [...this.#roots];
66
69
  this.sort((a, b) => {
67
70
  const aRoot = a.getRootNode(),
68
71
  bRoot = b.getRootNode();
69
- return aRoot === bRoot ? a.comparePosition(b) : rootArray.indexOf(aRoot) - rootArray.indexOf(bRoot);
72
+ return aRoot === bRoot ? a.compareDocumentPosition(b) : rootArray.indexOf(aRoot) - rootArray.indexOf(bRoot);
70
73
  });
71
74
  }
72
75
 
73
76
  toArray() {
74
- return Array.from(this);
77
+ return [...this];
75
78
  }
76
79
 
77
80
  _filter(selector = '') {
@@ -108,9 +111,9 @@ class TokenCollection extends Array {
108
111
 
109
112
  /** @param {CollectionCallback<void, string|Token>} callback */
110
113
  each(callback) {
111
- this.forEach((ele, i) => { // 不能使用`for`...`of`
112
- callback.call(ele, i, ele);
113
- });
114
+ for (let i = 0; i < this.length; i++) {
115
+ callback.call(this[i], i, this[i]);
116
+ }
114
117
  return this;
115
118
  }
116
119
 
@@ -162,8 +165,9 @@ class TokenCollection extends Array {
162
165
  text(str) {
163
166
  /** @type {(ele: Token, i: number, str: string) => string} */
164
167
  const callback = typeof str === 'function' ? str.call : () => str;
165
- if (['string', 'function'].includes(typeof str)) {
166
- for (const [i, ele] of this.entries()) {
168
+ if (typeof str === 'string' || typeof str === 'function') {
169
+ for (let i = 0; i < this.length; i++) {
170
+ const ele = this[i];
167
171
  if (ele instanceof Token) {
168
172
  try {
169
173
  ele.replaceChildren(callback(ele, i, ele.text()));
@@ -237,7 +241,7 @@ class TokenCollection extends Array {
237
241
  } else if (selector instanceof Token) {
238
242
  return arr.some(ele => ele.contains(selector));
239
243
  }
240
- this.typeError('has', 'String', 'Token');
244
+ return this.typeError('has', 'String', 'Token');
241
245
  }
242
246
 
243
247
  /** @param {string} selector */
@@ -292,11 +296,11 @@ class TokenCollection extends Array {
292
296
  */
293
297
  _siblings(start, count, selector = '') {
294
298
  return this._create(arr => arr.flatMap(ele => {
295
- const {parentElement} = ele;
296
- if (!parentElement) {
299
+ const {parentNode} = ele;
300
+ if (!parentNode) {
297
301
  return undefined;
298
302
  }
299
- const {children} = parentElement,
303
+ const {children} = parentNode,
300
304
  i = children.indexOf(ele);
301
305
  children.splice(
302
306
  typeof start === 'function' ? start(i) : start,
@@ -323,7 +327,7 @@ class TokenCollection extends Array {
323
327
  * @param {string|Token|Token[]} selector
324
328
  */
325
329
  _until(method, selector, filter = '') {
326
- const matches = matchesGenerator(`${method.replace(/All$/, '')}Until`, selector);
330
+ const matches = matchesGenerator(`${method.replace(/All$/u, '')}Until`, selector);
327
331
  return this._create(arr => arr.flatMap(ele => {
328
332
  const tokens = $(ele)[method]().toArray(),
329
333
  tokenArray = method === 'nextAll' ? tokens : tokens.reverse(),
@@ -382,7 +386,7 @@ class TokenCollection extends Array {
382
386
  if (name !== undefined && typeof name !== 'string' && !Array.isArray(name)) {
383
387
  this.typeError('removeData', 'String', 'Array');
384
388
  }
385
- name = typeof name === 'string' ? name.split(/\s/) : name;
389
+ name = typeof name === 'string' ? name.split(/\s/u) : name;
386
390
  for (const token of this._filter()) {
387
391
  if (!$.dataStore.has(token)) {
388
392
  continue;
@@ -404,14 +408,14 @@ class TokenCollection extends Array {
404
408
  * @param {AstListener} handler
405
409
  */
406
410
  _addEventListener(events, selector, handler, once = false) {
407
- if (!['string', 'object'].includes(typeof events)) {
411
+ if (typeof events !== 'string' && typeof events !== 'object') {
408
412
  this.typeError(once ? 'once' : 'on', 'String', 'Object');
409
413
  } else if (typeof selector === 'function') {
410
414
  handler = selector;
411
415
  selector = undefined;
412
416
  }
413
417
  const eventPair = typeof events === 'string'
414
- ? events.split(/\s/).map(/** @returns {[string, AstListener]} */ event => [event, handler])
418
+ ? events.split(/\s/u).map(/** @returns {[string, AstListener]} */ event => [event, handler])
415
419
  : Object.entries(events);
416
420
  for (const token of this._filter(selector)) {
417
421
  for (const [event, listener] of eventPair) {
@@ -445,14 +449,14 @@ class TokenCollection extends Array {
445
449
  * @param {AstListener} handler
446
450
  */
447
451
  off(events, selector, handler) {
448
- if (!['string', 'object', 'undefined'].includes(typeof events)) {
452
+ if (typeof events !== 'string' && typeof events !== 'object' && events !== undefined) {
449
453
  this.typeError('off', 'String', 'Object');
450
454
  }
451
455
  handler = typeof selector === 'function' ? selector : handler;
452
456
  let eventPair;
453
457
  if (events) {
454
458
  eventPair = typeof events === 'string'
455
- ? events.split(/\s/).map(/** @returns {[string, AstListener]} */ event => [event, handler])
459
+ ? events.split(/\s/u).map(/** @returns {[string, AstListener]} */ event => [event, handler])
456
460
  : Object.entries(events);
457
461
  }
458
462
  for (const token of this._filter(selector)) {
@@ -460,7 +464,7 @@ class TokenCollection extends Array {
460
464
  token.removeAllEventListeners();
461
465
  } else {
462
466
  for (const [event, listener] of eventPair) {
463
- if (typeof event !== 'string' || !['function', 'undefined'].includes(typeof listener)) {
467
+ if (typeof event !== 'string' || typeof listener !== 'function' && listener !== undefined) {
464
468
  this.typeError('off', 'String', 'Function');
465
469
  } else if (listener) {
466
470
  token.removeEventListener(event, listener);
@@ -486,7 +490,7 @@ class TokenCollection extends Array {
486
490
  triggerHandler(event, data) {
487
491
  const firstToken = this._find();
488
492
  if (!firstToken) {
489
- return;
493
+ return undefined;
490
494
  }
491
495
  const e = typeof event === 'string' ? new Event(event) : event,
492
496
  listeners = firstToken.listEventListeners(typeof event === 'string' ? event : event.type);
@@ -504,7 +508,8 @@ class TokenCollection extends Array {
504
508
  */
505
509
  _insert(method, content, ...additional) {
506
510
  if (typeof content === 'function') {
507
- for (const [i, token] of this.entries()) {
511
+ for (let i = 0; i < this.length; i++) {
512
+ const token = this[i];
508
513
  if (token instanceof Token) {
509
514
  const result = content.call(token, i, token.toString());
510
515
  if (typeof result === 'string' || result instanceof Token) {
@@ -576,7 +581,8 @@ class TokenCollection extends Array {
576
581
  }
577
582
 
578
583
  remove(selector = '') {
579
- for (const token of this.removeData()._filter(selector)) {
584
+ this.removeData();
585
+ for (const token of this._filter(selector)) {
580
586
  token.remove();
581
587
  token.removeAllEventListeners();
582
588
  }
@@ -659,7 +665,8 @@ class TokenCollection extends Array {
659
665
  } else {
660
666
  this.typeError('val', 'String', 'Array', 'Function');
661
667
  }
662
- for (const [i, token] of this.entries()) {
668
+ for (let i = 0; i < this.length; i++) {
669
+ const token = this[i];
663
670
  if (token instanceof Token && typeof token.setValue === 'function' && token.setValue.length === 1) {
664
671
  token.setValue(toValue(i, token));
665
672
  }
@@ -678,7 +685,8 @@ class TokenCollection extends Array {
678
685
  const firstToken = this._find();
679
686
  return firstToken?.[getter] && firstToken[getter](name);
680
687
  }
681
- for (const [i, token] of this.entries()) {
688
+ for (let i = 0; i < this.length; i++) {
689
+ const token = this[i];
682
690
  if (token instanceof Token && typeof token[setter] === 'function') {
683
691
  if (typeof value === 'string') {
684
692
  token[setter](name, value);
@@ -784,6 +792,7 @@ class TokenCollection extends Array {
784
792
  this.typeError(method, 'Array', 'Function');
785
793
  }
786
794
  return this[method](
795
+
787
796
  /**
788
797
  * @this {string|Token}
789
798
  * @param {number} i
@@ -817,7 +826,7 @@ class TokenCollection extends Array {
817
826
  offset() {
818
827
  const firstToken = this._find();
819
828
  if (!firstToken) {
820
- return;
829
+ return undefined;
821
830
  }
822
831
  const {top, left} = firstToken.getBoundingClientRect();
823
832
  return {top, left};
@@ -837,20 +846,21 @@ class TokenCollection extends Array {
837
846
  }
838
847
  }
839
848
 
840
- /** @param {string|Token|Iterable<string|Token>} tokens */
849
+ /** @param {AstText|Token|Iterable<string|Token>} tokens */
841
850
  const $ = tokens => {
842
- if (typeof tokens === 'string' || tokens instanceof Token) {
851
+ if (tokens instanceof AstText || tokens instanceof Token) {
843
852
  tokens = [tokens];
844
853
  }
845
854
  return new Proxy(new TokenCollection(...tokens), {
846
855
  /** @param {PropertyKey} prop */
847
856
  get(obj, prop) {
848
857
  if (prop === Symbol.iterator || typeof obj[prop] !== 'function'
849
- || !prop.startsWith('_') && Object.getOwnPropertyDescriptor(obj.constructor.prototype, prop)
858
+ || prop[0] !== '_' && Object.getOwnPropertyDescriptor(obj.constructor.prototype, prop)
850
859
  || !externalUse(prop, true)
851
860
  ) {
852
861
  return obj[prop];
853
862
  }
863
+ return undefined;
854
864
  },
855
865
  set(obj, prop, val) {
856
866
  if (prop === 'prevObject' && (val === undefined || val instanceof TokenCollection)) {
package/util/debug.js CHANGED
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * @param {function}
5
- * @param {string} method
6
- * @param {...string} args
4
+ * 定制TypeError消息
5
+ * @param {Function} constructor 类
6
+ * @param {string} method 方法名称
7
+ * @param {...string} args 可接受的参数类型
8
+ * @throws `TypeError`
7
9
  */
8
10
  const typeError = ({name}, method, ...args) => {
9
11
  throw new TypeError(`${name}.${method} 方法仅接受 ${args.join('、')} 作为输入参数!`);
@@ -11,28 +13,30 @@ const typeError = ({name}, method, ...args) => {
11
13
 
12
14
  /**
13
15
  * 不是被构造器或原型方法调用
14
- * @param {string} name
16
+ * @param {string} name 方法名称
15
17
  */
16
- const externalUse = (name, proxy = false) => {
17
- if (!proxy && require('..').running) {
18
+ const externalUse = name => {
19
+ const Parser = require('..');
20
+ if (Parser.running) {
18
21
  return false;
19
22
  }
20
- const regex = RegExp(`^${
21
- proxy ? 'Proxy' : 'new \\w*Token$|^(?:AstNode|AstElement|\\w*Token)'
22
- }\\.(?!${name}$)`);
23
+ const regex = new RegExp(`^new \\w*Token$|^(?:Ast\\w*|\\w*Token)\\.(?!${name}$)`, 'u');
23
24
  try {
24
- throw new Error();
25
+ throw new Error(); // eslint-disable-line unicorn/error-message
25
26
  } catch (e) {
26
27
  if (e instanceof Error) {
27
- const mt = e.stack.match(/(?<=^\s+at )(?:new )?[\w.]+(?= \(\/)/gm);
28
+ const mt = e.stack.match(/(?<=^\s+at )(?:new )?[\w.]+(?= \(\/)/gmu);
28
29
  return !mt.slice(2).some(func => regex.test(func));
29
30
  }
30
31
  }
32
+ return false;
31
33
  };
32
34
 
33
35
  /**
34
- * @param {AstEvent} e
35
- * @param {AstEventData} data
36
+ * 撤销最近一次Mutation
37
+ * @param {AstEvent} e 事件
38
+ * @param {AstEventData} data 事件数据
39
+ * @throws `RangeError` 无法撤销的事件类型
36
40
  */
37
41
  const undo = (e, data) => {
38
42
  const {target, type} = e;
@@ -40,6 +44,7 @@ const undo = (e, data) => {
40
44
  case 'remove': {
41
45
  const childNodes = [...target.childNodes];
42
46
  childNodes.splice(data.position, 0, data.removed);
47
+ data.removed.setAttribute('parentNode', target);
43
48
  target.setAttribute('childNodes', childNodes);
44
49
  break;
45
50
  }
@@ -53,15 +58,13 @@ const undo = (e, data) => {
53
58
  const {parentNode} = target,
54
59
  childNodes = [...parentNode.childNodes];
55
60
  childNodes.splice(data.position, 1, data.oldToken);
61
+ data.oldToken.setAttribute('parentNode', parentNode);
56
62
  parentNode.setAttribute('childNodes', childNodes);
57
63
  break;
58
64
  }
59
- case 'text': {
60
- const childNodes = [...target.childNodes];
61
- childNodes[data.position] = data.oldText;
62
- target.setAttribute('childNodes', childNodes);
65
+ case 'text':
66
+ target.replaceData(data.oldText);
63
67
  break;
64
- }
65
68
  default:
66
69
  throw new RangeError(`无法撤销未知类型的事件:${type}`);
67
70
  }
package/util/diff.js ADDED
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const {spawn} = require('child_process'),
4
+ fs = require('fs/promises');
5
+
6
+ process.on('unhandledRejection', e => {
7
+ console.error(e);
8
+ });
9
+
10
+ /**
11
+ * 将shell命令转化为Promise对象
12
+ * @param {string} command shell指令
13
+ * @param {string[]} args shell输入参数
14
+ * @returns {Promise<?string>}
15
+ */
16
+ const cmd = (command, args) => new Promise(resolve => {
17
+ let timer, shell;
18
+
19
+ /**
20
+ * 清除进程并返回
21
+ * @param {any} val 返回值
22
+ */
23
+ const r = val => {
24
+ clearTimeout(timer);
25
+ shell.kill('SIGINT');
26
+ resolve(val);
27
+ };
28
+ try {
29
+ shell = spawn(command, args);
30
+ timer = setTimeout(() => {
31
+ shell.kill('SIGINT');
32
+ }, 60 * 1000);
33
+ let buf = '';
34
+ shell.stdout.on('data', data => {
35
+ buf += data.toString();
36
+ });
37
+ shell.stdout.on('end', () => {
38
+ r(buf);
39
+ });
40
+ shell.on('exit', () => {
41
+ r(shell.killed ? null : '');
42
+ });
43
+ shell.on('error', () => {
44
+ r(null);
45
+ });
46
+ } catch {
47
+ r(null);
48
+ }
49
+ });
50
+
51
+ /**
52
+ * 比较两个文件
53
+ * @param {string} oldStr 旧文本
54
+ * @param {string} newStr 新文本
55
+ * @param {string} uid 唯一标识
56
+ */
57
+ const diff = async (oldStr, newStr, uid = '') => {
58
+ if (oldStr === newStr) {
59
+ return;
60
+ }
61
+ const oldFile = `diffOld${uid}`,
62
+ newFile = `diffNew${uid}`;
63
+ await Promise.all([fs.writeFile(oldFile, oldStr), fs.writeFile(newFile, newStr)]);
64
+ const stdout = await cmd('git', [
65
+ 'diff',
66
+ '--color-words=[\xC0-\xFF][\x80-\xBF]+|<?/?\\w+/?>?|[^[:space:]]',
67
+ '-U0',
68
+ '--no-index',
69
+ oldFile,
70
+ newFile,
71
+ ]);
72
+ await Promise.all([fs.unlink(oldFile), fs.unlink(newFile)]);
73
+ console.log(stdout?.split('\n')?.slice(4)?.join('\n'));
74
+ };
75
+
76
+ module.exports = diff;
package/util/lint.js ADDED
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const Token = require('../src');
4
+
5
+ /**
6
+ * 生成对于子节点的LintError对象
7
+ * @param {Token} child 子节点
8
+ * @param {{top: number, left: number}} boundingRect 父节点的绝对定位
9
+ * @param {string} message 错误信息
10
+ * @param {'error'|'warning'} severity 严重程度
11
+ * @returns {LintError}
12
+ */
13
+ const generateForChild = (child, boundingRect, message, severity = 'error') => {
14
+ const {style: {top: offsetTop, left: offsetLeft, height, width}} = child,
15
+ {top, left} = boundingRect,
16
+ startLine = top + offsetTop,
17
+ endLine = startLine + height - 1,
18
+ startCol = offsetTop ? offsetLeft : left + offsetLeft,
19
+ endCol = height > 1 ? width : startCol + width;
20
+ return {message, severity, startLine, endLine, startCol, endCol};
21
+ };
22
+
23
+ /**
24
+ * 生成对于自己的LintError对象
25
+ * @param {Token} token 节点
26
+ * @param {{top: number, left: number}} boundingRect 绝对定位
27
+ * @param {string} message 错误信息
28
+ * @param {'error'|'warning'} severity 严重程度
29
+ * @returns {LintError}
30
+ */
31
+ const generateForSelf = (token, boundingRect, message, severity = 'error') => ({
32
+ message,
33
+ severity,
34
+ startLine: boundingRect.top,
35
+ endLine: boundingRect.top + token.offsetHeight - 1,
36
+ startCol: boundingRect.left,
37
+ endCol: token.offsetHeight > 1 ? token.offsetWidth : boundingRect.left + token.offsetWidth,
38
+ });
39
+
40
+ module.exports = {generateForChild, generateForSelf};
package/util/string.js CHANGED
@@ -2,40 +2,62 @@
2
2
 
3
3
  /**
4
4
  * optionally convert to lower cases
5
- * @param {string} val
6
- * @param {string|undefined} i
5
+ * @param {string} val 属性值
6
+ * @param {string|undefined} i 是否对大小写不敏感
7
7
  */
8
8
  const toCase = (val, i) => i ? val.toLowerCase() : val;
9
9
 
10
10
  /**
11
11
  * remove half-parsed comment-like tokens
12
- * @param {string} str
12
+ * @param {string} str 原字符串
13
13
  */
14
- const removeComment = str => str.replace(/\0\d+c\x7f/g, '');
14
+ const removeComment = str => str.replaceAll(/\0\d+c\x7F/gu, '');
15
15
 
16
- /** @param {string} str */
17
- const ucfirst = str => str && `${str[0].toUpperCase()}${str.slice(1)}`;
16
+ /**
17
+ * 以HTML格式打印
18
+ * @param {(AstText|AstElement)[]} childNodes 子节点
19
+ * @param {printOpt} opt 选项
20
+ */
21
+ const print = (childNodes, opt = {}) => {
22
+ const AstText = require('../lib/text'),
23
+ AstElement = require('../lib/element');
24
+ const {pre = '', post = '', sep = ''} = opt,
25
+ entities = {'&': 'amp', '<': 'lt', '>': 'gt'};
26
+ return `${pre}${childNodes.map(
27
+ child => child instanceof AstElement
28
+ ? child.print()
29
+ : String(child).replaceAll(/[&<>]/gu, p => `&${entities[p]};`),
30
+ ).join(sep)}${post}`;
31
+ };
18
32
 
19
- /** @param {string} str */
20
- const escapeRegExp = str => str.replace(/[\\{}()|.?*+\-^$[\]]/g, '\\$&');
33
+ /**
34
+ * escape special chars for RegExp constructor
35
+ * @param {string} str RegExp source
36
+ */
37
+ const escapeRegExp = str => str.replaceAll(/[\\{}()|.?*+^$[\]]/gu, '\\$&');
21
38
 
22
- /** @param {(string|AstNode)[]} childNodes */
39
+ /**
40
+ * extract effective wikitext
41
+ * @param {(string|AstNode)[]} childNodes a Token's contents
42
+ * @param {string} separator delimiter between nodes
43
+ */
23
44
  const text = (childNodes, separator = '') => {
24
45
  const AstNode = require('../lib/node');
25
46
  return childNodes.map(child => typeof child === 'string' ? child : child.text()).join(separator);
26
47
  };
27
48
 
28
49
  /**
29
- * @param {string} start
30
- * @param {string} end
31
- * @param {string} separator
32
- * @param {string} str
50
+ * a more sophisticated string-explode function
51
+ * @param {string} start start syntax of a nested AST node
52
+ * @param {string} end end syntax of a nested AST node
53
+ * @param {string} separator syntax for explosion
54
+ * @param {string} str string to be exploded
33
55
  */
34
56
  const explode = (start, end, separator, str) => {
35
57
  if (str === undefined) {
36
58
  return [];
37
59
  }
38
- const regex = RegExp(`${[start, end, separator].map(escapeRegExp).join('|')}`, 'g'),
60
+ const regex = new RegExp(`${[start, end, separator].map(escapeRegExp).join('|')}`, 'gu'),
39
61
  /** @type {string[]} */ exploded = [];
40
62
  let mt = regex.exec(str),
41
63
  depth = 0,
@@ -54,22 +76,30 @@ const explode = (start, end, separator, str) => {
54
76
  return exploded;
55
77
  };
56
78
 
57
- /** @param {string} str */
79
+ /**
80
+ * escape newlines
81
+ * @param {string} str 原字符串
82
+ */
58
83
  const noWrap = str => str.replaceAll('\n', '\\n');
59
84
 
60
85
  /**
61
- * @param {string|Token} token
62
- * @returns {string}
86
+ * convert newline in text nodes to single whitespace
87
+ * @param {Token & {childNodes: AstText[]}} token 父节点
63
88
  */
64
- const normalizeSpace = (token = '', separator = '') => {
65
- const Token = require('../src');
66
- return typeof token === 'string'
67
- ? token.replaceAll('\n', ' ')
68
- : token.childNodes.map(child => typeof child === 'string' ? normalizeSpace(child) : child.toString())
69
- .join(separator);
89
+ const normalizeSpace = token => {
90
+ if (token === undefined) {
91
+ return;
92
+ }
93
+ const Token = require('../src'),
94
+ AstText = require('../lib/text');
95
+ for (const child of token.childNodes) {
96
+ if (child.type === 'text') {
97
+ child.replaceData(child.data.replaceAll('\n', ' '));
98
+ }
99
+ }
70
100
  };
71
101
 
72
- const extUrlChar = '(?:\\[[\\da-f:.]+\\]|[^[\\]<>"\\0-\\x1f\\x7f\\p{Zs}\\ufffd])'
73
- + '(?:[^[\\]<>"\\0-\\x1f\\x7f\\p{Zs}\\ufffd]|\\0\\d+c\\x7f)*';
102
+ const extUrlChar = '(?:\\[[\\da-f:.]+\\]|[^[\\]<>"\\0-\\x1F\\x7F\\p{Zs}\\uFFFD])'
103
+ + '(?:[^[\\]<>"\\0-\\x1F\\x7F\\p{Zs}\\uFFFD]|\\0\\d+c\\x7F)*';
74
104
 
75
- module.exports = {toCase, removeComment, ucfirst, escapeRegExp, text, explode, noWrap, normalizeSpace, extUrlChar};
105
+ module.exports = {toCase, removeComment, print, escapeRegExp, text, explode, noWrap, normalizeSpace, extUrlChar};