wikiparser-node 0.8.0 → 0.8.1-m

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 (77) hide show
  1. package/config/minimum.json +142 -0
  2. package/index.js +11 -253
  3. package/lib/element.js +7 -481
  4. package/lib/node.js +6 -552
  5. package/lib/text.js +70 -110
  6. package/lib/title.js +0 -21
  7. package/mixin/hidden.js +0 -3
  8. package/package.json +5 -5
  9. package/parser/brackets.js +0 -1
  10. package/parser/commentAndExt.js +3 -4
  11. package/parser/converter.js +0 -1
  12. package/parser/externalLinks.js +0 -1
  13. package/parser/hrAndDoubleUnderscore.js +0 -1
  14. package/parser/html.js +0 -1
  15. package/parser/links.js +4 -5
  16. package/parser/list.js +0 -1
  17. package/parser/magicLinks.js +4 -5
  18. package/parser/quotes.js +1 -2
  19. package/parser/table.js +0 -1
  20. package/src/arg.js +2 -116
  21. package/src/atom/hidden.js +0 -2
  22. package/src/atom/index.js +0 -17
  23. package/src/attribute.js +15 -182
  24. package/src/attributes.js +4 -308
  25. package/src/converter.js +2 -108
  26. package/src/converterFlags.js +0 -187
  27. package/src/converterRule.js +1 -184
  28. package/src/extLink.js +1 -120
  29. package/src/gallery.js +6 -57
  30. package/src/hasNowiki/index.js +0 -12
  31. package/src/hasNowiki/pre.js +0 -12
  32. package/src/heading.js +4 -55
  33. package/src/html.js +3 -118
  34. package/src/imageParameter.js +19 -189
  35. package/src/imagemap.js +1 -60
  36. package/src/imagemapLink.js +1 -13
  37. package/src/index.js +3 -529
  38. package/src/link/category.js +1 -37
  39. package/src/link/file.js +2 -159
  40. package/src/link/galleryImage.js +1 -59
  41. package/src/link/index.js +1 -259
  42. package/src/magicLink.js +9 -90
  43. package/src/nested/choose.js +0 -1
  44. package/src/nested/combobox.js +0 -1
  45. package/src/nested/index.js +3 -30
  46. package/src/nested/references.js +0 -1
  47. package/src/nowiki/comment.js +1 -25
  48. package/src/nowiki/dd.js +1 -47
  49. package/src/nowiki/doubleUnderscore.js +1 -31
  50. package/src/nowiki/hr.js +1 -20
  51. package/src/nowiki/index.js +1 -23
  52. package/src/nowiki/list.js +2 -5
  53. package/src/nowiki/noinclude.js +0 -14
  54. package/src/nowiki/quote.js +2 -16
  55. package/src/onlyinclude.js +1 -26
  56. package/src/paramTag/index.js +1 -24
  57. package/src/paramTag/inputbox.js +1 -4
  58. package/src/parameter.js +6 -148
  59. package/src/syntax.js +0 -68
  60. package/src/table/index.js +2 -940
  61. package/src/table/td.js +5 -225
  62. package/src/table/tr.js +2 -247
  63. package/src/tagPair/ext.js +3 -24
  64. package/src/tagPair/include.js +0 -24
  65. package/src/tagPair/index.js +2 -51
  66. package/src/transclude.js +20 -519
  67. package/util/string.js +1 -48
  68. package/README.md +0 -39
  69. package/lib/ranges.js +0 -130
  70. package/mixin/attributeParent.js +0 -117
  71. package/mixin/fixedToken.js +0 -40
  72. package/mixin/singleLine.js +0 -31
  73. package/mixin/sol.js +0 -65
  74. package/parser/selector.js +0 -177
  75. package/src/charinsert.js +0 -97
  76. package/tool/index.js +0 -1202
  77. package/util/debug.js +0 -73
package/lib/element.js CHANGED
@@ -2,437 +2,27 @@
2
2
 
3
3
  const fs = require('fs'),
4
4
  path = require('path'),
5
- {toCase, noWrap, print} = require('../util/string'),
6
- {nth} = require('./ranges'),
7
- parseSelector = require('../parser/selector'),
8
- Parser = require('..'),
9
5
  AstNode = require('./node'),
10
6
  AstText = require('./text');
11
7
 
12
- /**
13
- * 检测:lang()伪选择器
14
- * @param {AstElement & {attributes: Records<string, string|true>}} node 节点
15
- * @param {RegExp} regex 语言正则
16
- */
17
- const matchesLang = ({attributes}, regex) => {
18
- const /** @type {string} */ lang = attributes?.lang;
19
- return typeof lang === 'string' && regex.test(lang);
20
- };
21
-
22
8
  /** 类似HTMLElement */
23
9
  class AstElement extends AstNode {
24
10
  /** @type {string} */ name;
25
11
 
26
- /**
27
- * 检查是否符合某条属性规则
28
- * @param {string} key 属性键
29
- * @param {string|undefined} equal 属性规则运算符,`equal`存在时`val`和`i`也一定存在
30
- * @param {string|undefined} val 属性值
31
- * @param {string|undefined} i 是否对大小写不敏感
32
- * @throws `RangeError` 复杂属性
33
- */
34
- #matchesAttr = (key, equal, val, i) => {
35
- if (!equal) {
36
- return this.hasAttribute(key);
37
- } else if (!this.hasAttribute(key)) {
38
- return equal === '!=';
39
- }
40
- val = toCase(val, i);
41
- let thisVal = this.getAttribute(key);
42
- if (thisVal instanceof RegExp) {
43
- thisVal = thisVal.source;
44
- }
45
- if (equal === '~=') {
46
- const thisVals = typeof thisVal === 'string' ? thisVal.split(/\s/u) : thisVal;
47
- return Boolean(thisVals?.[Symbol.iterator]) && [...thisVals].some(v => toCase(v, i) === val);
48
- } else if (typeof thisVal !== 'string') {
49
- throw new RangeError(`复杂属性 ${key} 不能用于选择器!`);
50
- }
51
- thisVal = toCase(thisVal, i);
52
- switch (equal) {
53
- case '|=':
54
- return thisVal === val || thisVal.startsWith(`${val}-`);
55
- case '^=':
56
- return thisVal.startsWith(val);
57
- case '$=':
58
- return thisVal.endsWith(val);
59
- case '*=':
60
- return thisVal.includes(val);
61
- case '!=':
62
- return thisVal !== val;
63
- default: // `=`
64
- return thisVal === val;
65
- }
66
- };
67
-
68
12
  /** 子节点总数 */
69
13
  get length() {
70
14
  return this.childNodes.length;
71
15
  }
72
16
 
73
- /**
74
- * 全部非文本子节点
75
- * @complexity `n`
76
- */
77
- get children() {
78
- const /** @type {this[]} */ children = this.childNodes.filter(({type}) => type !== 'text');
79
- return children;
80
- }
81
-
82
- /**
83
- * 首位非文本子节点
84
- * @returns {this}
85
- */
86
- get firstElementChild() {
87
- return this.childNodes.find(({type}) => type !== 'text');
88
- }
89
-
90
- /**
91
- * 末位非文本子节点
92
- * @complexity `n`
93
- */
94
- get lastElementChild() {
95
- return this.children.at(-1);
96
- }
97
-
98
- /**
99
- * 非文本子节点总数
100
- * @complexity `n`
101
- */
102
- get childElementCount() {
103
- return this.children.length;
104
- }
105
-
106
- /** 父节点 */
107
- get parentElement() {
108
- return this.parentNode;
109
- }
110
-
111
- /**
112
- * AstElement.prototype.text()的getter写法
113
- * @complexity `n`
114
- */
115
- get outerText() {
116
- return this.text();
117
- }
118
-
119
- /**
120
- * 不可见
121
- */
122
- get hidden() {
123
- return this.text() === '';
124
- }
125
-
126
- /**
127
- * 后一个可见的兄弟节点
128
- * @complexity `n`
129
- */
130
- get nextVisibleSibling() {
131
- let {nextSibling} = this;
132
- while (nextSibling?.text() === '') {
133
- ({nextSibling} = nextSibling);
134
- }
135
- return nextSibling;
136
- }
137
-
138
- /**
139
- * 前一个可见的兄弟节点
140
- * @complexity `n`
141
- */
142
- get previousVisibleSibling() {
143
- let {previousSibling} = this;
144
- while (previousSibling?.text() === '') {
145
- ({previousSibling} = previousSibling);
146
- }
147
- return previousSibling;
148
- }
149
-
150
- /** 内部高度 */
151
- get clientHeight() {
152
- const {innerText} = this;
153
- return typeof innerText === 'string' ? innerText.split('\n').length : undefined;
154
- }
155
-
156
- /** 内部宽度 */
157
- get clientWidth() {
158
- const {innerText} = this;
159
- return typeof innerText === 'string' ? innerText.split('\n').at(-1).length : undefined;
160
- }
161
-
162
- constructor() {
163
- super();
164
- this.seal('name');
165
- }
166
-
167
- /**
168
- * 销毁
169
- * @complexity `n`
170
- * @param {boolean} deep 是否从根节点开始销毁
171
- * @throws `Error` 不能销毁子节点
172
- */
173
- destroy(deep) {
174
- if (this.parentNode && !deep) {
175
- throw new Error('不能销毁子节点!');
176
- }
177
- this.parentNode?.destroy(deep);
178
- for (const child of this.childNodes) {
179
- child.setAttribute('parentNode');
180
- }
181
- Object.setPrototypeOf(this, null);
182
- }
183
-
184
- /**
185
- * @override
186
- * @template {string} T
187
- * @param {T} key 属性键
188
- * @returns {TokenAttribute<T>}
189
- */
190
- getAttribute(key) {
191
- return key === 'matchesAttr' ? this.#matchesAttr : super.getAttribute(key);
192
- }
193
-
194
- /** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
195
- #isProtected() {
196
- const /** @type {{parentNode: AstElement & {constructor: {fixed: boolean}}}} */ {parentNode} = this;
197
- if (!parentNode) {
198
- return undefined;
199
- }
200
- const {childNodes, constructor: {fixed}} = parentNode,
201
- protectedIndices = parentNode.getAttribute('protectedChildren')?.applyTo(childNodes);
202
- return fixed || protectedIndices?.includes(childNodes.indexOf(this));
203
- }
204
-
205
- /**
206
- * 检查是否符合解析后的选择器,不含节点关系
207
- * @this {AstElement & {link: string, constructor: {fixed: boolean}}}
208
- * @param {SelectorArray} step 解析后的选择器
209
- * @throws `SyntaxError` 未定义的伪选择器
210
- */
211
- #matches(step) {
212
- const {parentNode, type, name, childNodes, link, constructor: {fixed, name: tokenName}} = this,
213
- children = parentNode?.children,
214
- childrenOfType = children?.filter(({type: t}) => t === type),
215
- siblingsCount = children?.length ?? 1,
216
- siblingsCountOfType = childrenOfType?.length ?? 1,
217
- index = (children?.indexOf(this) ?? 0) + 1,
218
- indexOfType = (childrenOfType?.indexOf(this) ?? 0) + 1,
219
- lastIndex = siblingsCount - index + 1,
220
- lastIndexOfType = siblingsCountOfType - indexOfType + 1;
221
- return step.every(selector => {
222
- if (typeof selector === 'string') {
223
- switch (selector) { // 情形1:简单伪选择器、type和name
224
- case '*':
225
- return true;
226
- case ':root':
227
- return !parentNode;
228
- case ':first-child':
229
- return index === 1;
230
- case ':first-of-type':
231
- return indexOfType === 1;
232
- case ':last-child':
233
- return lastIndex === 1;
234
- case ':last-of-type':
235
- return lastIndexOfType === 1;
236
- case ':only-child':
237
- return siblingsCount === 1;
238
- case ':only-of-type':
239
- return siblingsCountOfType === 1;
240
- case ':empty':
241
- return !childNodes.some(child => child instanceof AstElement || String(child));
242
- case ':parent':
243
- return childNodes.some(child => child instanceof AstElement || String(child));
244
- case ':header':
245
- return type === 'heading';
246
- case ':hidden':
247
- return this.text() === '';
248
- case ':visible':
249
- return this.text() !== '';
250
- case ':only-whitespace':
251
- return this.text().trim() === '';
252
- case ':any-link':
253
- return type === 'link' || type === 'free-ext-link' || type === 'ext-link'
254
- || (type === 'file' || type === 'gallery-image' && link);
255
- case ':local-link':
256
- return (type === 'link' || type === 'file' || type === 'gallery-image')
257
- && link?.[0] === '#';
258
- case ':read-only':
259
- return fixed;
260
- case ':read-write':
261
- return !fixed;
262
- case ':invalid':
263
- return type === 'table-inter' || tokenName === 'HiddenToken';
264
- case ':required':
265
- return this.#isProtected() === true;
266
- case ':optional':
267
- return this.#isProtected() === false;
268
- default: {
269
- const [t, n] = selector.split('#');
270
- return (!t || t === type || Boolean(Parser.typeAliases[type]?.includes(t)))
271
- && (!n || n === name);
272
- }
273
- }
274
- } else if (selector.length === 4) { // 情形2:属性选择器
275
- return this.getAttribute('matchesAttr')(...selector);
276
- }
277
- const [s, pseudo] = selector; // 情形3:复杂伪选择器
278
- switch (pseudo) {
279
- case 'is':
280
- return this.matches(s);
281
- case 'not':
282
- return !this.matches(s);
283
- case 'nth-child':
284
- return nth(s, index);
285
- case 'nth-of-type':
286
- return nth(s, indexOfType);
287
- case 'nth-last-child':
288
- return nth(s, lastIndex);
289
- case 'nth-last-of-type':
290
- return nth(s, lastIndexOfType);
291
- case 'contains':
292
- return this.text().includes(s);
293
- case 'has':
294
- return Boolean(this.querySelector(s));
295
- case 'lang': {
296
- const regex = new RegExp(`^${s}(?:-|$)`, 'u');
297
- return matchesLang(this, regex)
298
- || this.getAncestors().some(ancestor => matchesLang(ancestor, regex));
299
- }
300
- default:
301
- throw new SyntaxError(`未定义的伪选择器!${pseudo}`);
302
- }
303
- });
304
- }
305
-
306
- /**
307
- * 检查是否符合选择器
308
- * @param {string|SelectorArray[]} selector
309
- * @returns {boolean}
310
- * @complexity `n`
311
- */
312
- matches(selector) {
313
- if (selector === undefined) {
314
- return true;
315
- } else if (typeof selector === 'string') {
316
- const stack = parseSelector(selector),
317
- /** @type {Set<string>} */
318
- pseudos = new Set(stack.flat(2).filter(step => typeof step === 'string' && step[0] === ':'));
319
- if (pseudos.size > 0) {
320
- Parser.warn('检测到伪选择器,请确认是否需要将":"转义成"\\:"。', pseudos);
321
- }
322
- return Parser.run(() => stack.some(condition => this.matches(condition)));
323
- } else if (!Parser.running) {
324
- this.typeError('matches', 'String');
325
- }
326
- selector = [...selector];
327
- const step = selector.pop();
328
- if (this.#matches(step)) {
329
- const {parentNode, previousElementSibling} = this;
330
- switch (selector.at(-1)?.relation) {
331
- case undefined:
332
- return true;
333
- case '>':
334
- return parentNode?.matches(selector);
335
- case '+':
336
- return previousElementSibling?.matches(selector);
337
- case '~': {
338
- if (!parentNode) {
339
- return false;
340
- }
341
- const {children} = parentNode,
342
- i = children.indexOf(this);
343
- return children.slice(0, i).some(child => child.matches(selector));
344
- }
345
- default: // ' '
346
- return this.getAncestors().some(ancestor => ancestor.matches(selector));
347
- }
348
- }
349
- return false;
350
- }
351
-
352
- /**
353
- * 符合选择器的第一个后代节点
354
- * @param {string} selector
355
- * @returns {this|undefined}
356
- * @complexity `n`
357
- */
358
- querySelector(selector) {
359
- for (const child of this.children) {
360
- if (child.matches(selector)) {
361
- return child;
362
- }
363
- const descendant = child.querySelector(selector);
364
- if (descendant) {
365
- return descendant;
366
- }
367
- }
368
- return undefined;
369
- }
370
-
371
- /**
372
- * 符合选择器的所有后代节点
373
- * @param {string} selector
374
- * @complexity `n`
375
- */
376
- querySelectorAll(selector) {
377
- const /** @type {this[]} */ descendants = [];
378
- for (const child of this.children) {
379
- if (child.matches(selector)) {
380
- descendants.push(child);
381
- }
382
- descendants.push(...child.querySelectorAll(selector));
383
- }
384
- return descendants;
385
- }
386
-
387
- /**
388
- * 类选择器
389
- * @param {string} className 类名之一
390
- */
391
- getElementsByClassName(className) {
392
- return typeof className === 'string'
393
- ? this.querySelectorAll(`[className~="${className.replace(/(?<!\\)"/gu, '\\"')}"]`)
394
- : this.typeError('getElementsByClassName', 'String');
395
- }
396
-
397
- /**
398
- * 标签名选择器
399
- * @param {string} name 标签名
400
- */
401
- getElementsByTagName(name) {
402
- if (typeof name === 'string') {
403
- name = name.replace(/(?<!\\)"/gu, '\\"');
404
- return this.querySelectorAll(`ext[name="${name}"], html[name="${name}"]`);
405
- }
406
- return this.typeError('getElementsByTagName', 'String');
407
- }
408
-
409
- /**
410
- * 获取某一行的wikitext
411
- * @param {number} n 行号
412
- */
413
- getLine(n) {
414
- return String(this).split('\n', n + 1).at(-1);
415
- }
416
-
417
- /**
418
- * 在开头批量插入子节点
419
- * @param {...this} elements 插入节点
420
- * @complexity `n`
421
- */
422
- prepend(...elements) {
423
- for (let i = 0; i < elements.length; i++) {
424
- this.insertAt(elements[i], i);
425
- }
426
- }
427
-
428
17
  /**
429
18
  * 最近的祖先节点
430
19
  * @param {string} selector
431
20
  */
432
21
  closest(selector) {
22
+ const types = new Set(selector.split(',').map(type => type.trim()));
433
23
  let {parentNode} = this;
434
24
  while (parentNode) {
435
- if (parentNode.matches(selector)) {
25
+ if (types.has(parentNode.type)) {
436
26
  return parentNode;
437
27
  }
438
28
  ({parentNode} = parentNode);
@@ -457,9 +47,7 @@ class AstElement extends AstNode {
457
47
  * @complexity `n`
458
48
  */
459
49
  replaceChildren(...elements) {
460
- for (let i = this.length - 1; i >= 0; i--) {
461
- this.removeAt(i);
462
- }
50
+ this.childNodes.length = 0;
463
51
  this.append(...elements);
464
52
  }
465
53
 
@@ -467,29 +55,24 @@ class AstElement extends AstNode {
467
55
  * 修改文本子节点
468
56
  * @param {string} str 新文本
469
57
  * @param {number} i 子节点位置
470
- * @throws `RangeError` 对应位置的子节点不是文本节点
471
58
  */
472
59
  setText(str, i = 0) {
473
- this.getAttribute('verifyChild')(i);
474
- const /** @type {AstText} */ oldText = this.childNodes.at(i),
475
- {type, data, constructor: {name}} = oldText;
60
+ const /** @type {AstText} */ oldText = this.childNodes[i],
61
+ {type, data} = oldText;
476
62
  if (type === 'text') {
477
63
  oldText.replaceData(str);
478
64
  return data;
479
65
  }
480
- throw new RangeError(`第 ${i} 个子节点是 ${name}!`);
66
+ return undefined;
481
67
  }
482
68
 
483
69
  /**
484
70
  * 还原为wikitext
485
- * @param {string} selector
486
71
  * @param {string} separator 子节点间的连接符
487
72
  * @returns {string}
488
73
  */
489
74
  toString(selector, separator = '') {
490
- return selector && this.matches(selector)
491
- ? ''
492
- : this.childNodes.map(child => child.toString(selector)).join(separator);
75
+ return this.childNodes.map(child => child.toString()).join(separator);
493
76
  }
494
77
 
495
78
  static lintIgnoredExt = new Set([
@@ -530,17 +113,6 @@ class AstElement extends AstNode {
530
113
  return errors;
531
114
  }
532
115
 
533
- /**
534
- * 以HTML格式打印
535
- * @param {printOpt} opt 选项
536
- * @returns {string}
537
- */
538
- print(opt = {}) {
539
- return String(this)
540
- ? `<span class="wpb-${opt.class || this.type}">${print(this.childNodes, opt)}</span>`
541
- : '';
542
- }
543
-
544
116
  /**
545
117
  * 保存为JSON
546
118
  * @param {string} file 文件名
@@ -560,52 +132,6 @@ class AstElement extends AstNode {
560
132
  }
561
133
  return json;
562
134
  }
563
-
564
- /**
565
- * 输出AST
566
- * @param {number} depth 当前深度
567
- * @returns {void}
568
- */
569
- echo(depth = 0) {
570
- if (!Number.isInteger(depth) || depth < 0) {
571
- this.typeError('print', 'Number');
572
- }
573
- const indent = ' '.repeat(depth),
574
- str = String(this),
575
- {childNodes, type, length} = this;
576
- if (childNodes.every(child => child.type === 'text' || !String(child))) {
577
- console.log(`${indent}\x1B[32m<%s>\x1B[0m${noWrap(str)}\x1B[32m</%s>\x1B[0m`, type, type);
578
- return undefined;
579
- }
580
- Parser.info(`${indent}<${type}>`);
581
- let i = this.getPadding();
582
- if (i) {
583
- console.log(`${indent} ${noWrap(str.slice(0, i))}`);
584
- }
585
- for (let j = 0; j < length; j++) {
586
- const child = childNodes[j],
587
- childStr = String(child),
588
- gap = j === length - 1 ? 0 : this.getGaps(j);
589
- if (!childStr) {
590
- // pass
591
- } else if (child.type === 'text') {
592
- console.log(`${indent} ${noWrap(String(child))}`);
593
- } else {
594
- child.echo(depth + 1);
595
- }
596
- i += childStr.length;
597
- if (gap) {
598
- console.log(`${indent} ${noWrap(str.slice(i, i + gap))}`);
599
- i += gap;
600
- }
601
- }
602
- if (i < str.length) {
603
- console.log(`${indent} ${noWrap(str.slice(i))}`);
604
- }
605
- Parser.info(`${indent}</${type}>`);
606
- return undefined;
607
- }
608
135
  }
609
136
 
610
- Parser.classes.AstElement = __filename;
611
137
  module.exports = AstElement;