wikiparser-node 1.7.1 → 1.8.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.
@@ -77,62 +77,79 @@ index_1.Token.prototype.sections =
77
77
  if (this.type !== 'root') {
78
78
  return undefined;
79
79
  }
80
- const { childNodes } = this, headings = [...childNodes.entries()]
80
+ const { childNodes, length } = this, headings = [...childNodes.entries()]
81
81
  .filter((entry) => entry[1].type === 'heading')
82
- .map(([i, { level }]) => [i, level]), lastHeading = [-1, -1, -1, -1, -1, -1], sections = new Array(headings.length);
82
+ .map(([i, { level }]) => [i, level]), lastHeading = [-1, -1, -1, -1, -1, -1], sections = headings.map(([i]) => {
83
+ const range = this.createRange();
84
+ range.setStart(this, i);
85
+ return range;
86
+ });
83
87
  for (let i = 0; i < headings.length; i++) {
84
88
  const [index, level] = headings[i];
85
89
  for (let j = level; j < 6; j++) {
86
90
  const last = lastHeading[j];
87
91
  if (last >= 0) {
88
- sections[last] = childNodes.slice(headings[last][0], index);
92
+ sections[last].setEnd(this, index);
89
93
  }
90
94
  lastHeading[j] = j === level ? i : -1;
91
95
  }
92
96
  }
93
97
  for (const last of lastHeading) {
94
98
  if (last >= 0) {
95
- sections[last] = childNodes.slice(headings[last][0]);
99
+ sections[last].setEnd(this, length);
96
100
  }
97
101
  }
98
- sections.unshift(childNodes.slice(0, headings[0]?.[0]));
102
+ const range = this.createRange();
103
+ range.setStart(this, 0);
104
+ range.setEnd(this, headings[0]?.[0] ?? length);
105
+ sections.unshift(range);
99
106
  return sections;
100
107
  };
101
108
  index_1.Token.prototype.findEnclosingHtml =
102
109
  /** @implements */
103
110
  function (tag) {
104
111
  tag = tag?.toLowerCase();
105
- if (tag !== undefined && !this.getAttribute('config').html.slice(0, 2).flat().includes(tag)) {
112
+ const { html } = this.getAttribute('config'), normalTags = new Set(html[0]), voidTags = new Set(html[2]);
113
+ if (tag !== undefined && !html.slice(0, 2).flat().includes(tag)) {
106
114
  throw new RangeError(`非法的标签或空标签:${tag}`);
107
115
  }
108
116
  const { parentNode } = this;
109
117
  if (!parentNode) {
110
118
  return undefined;
111
119
  }
112
- const isHtml = (0, debug_1.isToken)('html'), { childNodes, length } = parentNode, index = childNodes.indexOf(this);
113
- let i;
114
- for (i = index - 1; i >= 0; i--) {
115
- const child = childNodes[i];
116
- if (isHtml(child) && (!tag || child.name === tag) && !child.selfClosing && !child.closing) {
117
- break;
120
+ const isHtml = (0, debug_1.isToken)('html'),
121
+ /**
122
+ * 检查是否为指定的 HTML 标签
123
+ * @param node 节点
124
+ * @param name 标签名
125
+ * @param closing 是否为闭合标签
126
+ */
127
+ checkHtml = (node, name, closing) => isHtml(node)
128
+ && (!name && !voidTags.has(node.name) || node.name === name)
129
+ && (normalTags.has(node.name) || !node.selfClosing)
130
+ && node.closing === closing;
131
+ const { childNodes, length } = parentNode, index = childNodes.indexOf(this);
132
+ let i = index - 1, j = length;
133
+ for (; i >= 0; i--) {
134
+ const open = childNodes[i];
135
+ if (checkHtml(open, tag, false)) {
136
+ for (j = index + 1; j < length; j++) {
137
+ const close = childNodes[j];
138
+ if (checkHtml(close, open.name, true)) {
139
+ break;
140
+ }
141
+ }
142
+ if (j < length) {
143
+ break;
144
+ }
118
145
  }
119
146
  }
120
147
  if (i === -1) {
121
148
  return parentNode.findEnclosingHtml(tag);
122
149
  }
123
- const opening = childNodes[i];
124
- for (i = index + 1; i < length; i++) {
125
- const child = childNodes[i];
126
- if (isHtml(child) && child.name === opening.name && !child.selfClosing && child.closing) {
127
- break;
128
- }
129
- }
130
- if (i === length) {
131
- return parentNode.findEnclosingHtml(tag);
132
- }
133
150
  const range = this.createRange();
134
- range.setStartBefore(opening);
135
- range.setEnd(parentNode, i + 1);
151
+ range.setStart(parentNode, i);
152
+ range.setEnd(parentNode, j + 1);
136
153
  return range;
137
154
  };
138
155
  index_1.Token.prototype.redoQuotes =
@@ -22,10 +22,6 @@ export declare abstract class AstElement extends AstNode {
22
22
  get outerText(): string;
23
23
  /** 不可见 */
24
24
  get hidden(): boolean;
25
- /** 后一个可见的兄弟节点 */
26
- get nextVisibleSibling(): AstNodes | undefined;
27
- /** 前一个可见的兄弟节点 */
28
- get previousVisibleSibling(): AstNodes | undefined;
29
25
  /** 内部高度 */
30
26
  get clientHeight(): number | undefined;
31
27
  /** 内部宽度 */
@@ -99,8 +95,6 @@ export declare abstract class AstElement extends AstNode {
99
95
  * @param start
100
96
  */
101
97
  json(file?: string, start?: number): AST;
102
- /** 销毁 */
103
- destroy(): void;
104
98
  /**
105
99
  * 检查是否符合选择器
106
100
  * @param selector 选择器
@@ -126,11 +120,6 @@ export declare abstract class AstElement extends AstNode {
126
120
  * @param tag 标签名
127
121
  */
128
122
  getElementsByTagName<T = Token>(tag: string): T[];
129
- /**
130
- * 获取某一行的wikitext
131
- * @param n 行号
132
- */
133
- getLine(n: number): string | undefined;
134
123
  /**
135
124
  * 在开头批量插入子节点
136
125
  * @param elements 插入节点
@@ -69,22 +69,6 @@ class AstElement extends node_1.AstNode {
69
69
  get hidden() {
70
70
  return this.text() === '';
71
71
  }
72
- /** 后一个可见的兄弟节点 */
73
- get nextVisibleSibling() {
74
- let { nextSibling } = this;
75
- while (nextSibling?.text() === '') {
76
- ({ nextSibling } = nextSibling);
77
- }
78
- return nextSibling;
79
- }
80
- /** 前一个可见的兄弟节点 */
81
- get previousVisibleSibling() {
82
- let { previousSibling } = this;
83
- while (previousSibling?.text() === '') {
84
- ({ previousSibling } = previousSibling);
85
- }
86
- return previousSibling;
87
- }
88
72
  /** 内部高度 */
89
73
  get clientHeight() {
90
74
  const { innerText } = this;
@@ -347,14 +331,6 @@ class AstElement extends node_1.AstNode {
347
331
  return json;
348
332
  }
349
333
  /* NOT FOR BROWSER */
350
- /** 销毁 */
351
- destroy() {
352
- this.parentNode?.destroy();
353
- for (const child of this.childNodes) {
354
- child.setAttribute('parentNode', undefined);
355
- }
356
- Object.setPrototypeOf(this, null);
357
- }
358
334
  /** 是否受保护。保护条件来自Token,这里仅提前用于:required和:optional伪选择器。 */
359
335
  #isProtected() {
360
336
  const { parentNode } = this;
@@ -583,13 +559,6 @@ class AstElement extends node_1.AstNode {
583
559
  getElementsByTagName(tag) {
584
560
  return this.#getElementsBy((({ type, name }) => name === tag && (type === 'html' || type === 'ext')));
585
561
  }
586
- /**
587
- * 获取某一行的wikitext
588
- * @param n 行号
589
- */
590
- getLine(n) {
591
- return String(this).split('\n', n + 1)[n];
592
- }
593
562
  /**
594
563
  * 在开头批量插入子节点
595
564
  * @param elements 插入节点
@@ -19,6 +19,7 @@ export declare abstract class AstNode implements AstNodeBase {
19
19
  type: TokenTypes | 'text';
20
20
  data?: string | undefined;
21
21
  readonly childNodes: readonly AstNodes[];
22
+ abstract text(): string;
22
23
  abstract lint(): LintError[];
23
24
  abstract print(): string;
24
25
  /** 首位子节点 */
@@ -39,6 +40,10 @@ export declare abstract class AstNode implements AstNodeBase {
39
40
  get nextElementSibling(): Token | undefined;
40
41
  /** 前一个非文本兄弟节点 */
41
42
  get previousElementSibling(): Token | undefined;
43
+ /** 后一个可见的兄弟节点 */
44
+ get nextVisibleSibling(): AstNodes | undefined;
45
+ /** 前一个可见的兄弟节点 */
46
+ get previousVisibleSibling(): AstNodes | undefined;
42
47
  /** 是否具有根节点 */
43
48
  get isConnected(): boolean;
44
49
  /** 后方是否还有其他节点(不含后代) */
@@ -51,6 +56,15 @@ export declare abstract class AstNode implements AstNodeBase {
51
56
  get style(): Position & Dimension & {
52
57
  padding: number;
53
58
  };
59
+ /** 字体样式 */
60
+ get font(): {
61
+ bold: boolean;
62
+ italic: boolean;
63
+ };
64
+ /** 是否粗体 */
65
+ get bold(): boolean;
66
+ /** 是否斜体 */
67
+ get italic(): boolean;
54
68
  constructor();
55
69
  /** 获取根节点 */
56
70
  getRootNode(): Token | this;
@@ -142,4 +156,11 @@ export declare abstract class AstNode implements AstNodeBase {
142
156
  indexFromPos(top: number, left: number): number | undefined;
143
157
  /** 获取当前节点的行列位置和大小 */
144
158
  getBoundingClientRect(): Dimension & Position;
159
+ /** 销毁 */
160
+ destroy(): void;
161
+ /**
162
+ * 获取某一行的wikitext
163
+ * @param n 行号
164
+ */
165
+ getLine(n: number): string | undefined;
145
166
  }
package/dist/lib/node.js CHANGED
@@ -4,6 +4,7 @@ exports.AstNode = void 0;
4
4
  const assert = require("assert/strict");
5
5
  const EventEmitter = require("events");
6
6
  const constants_1 = require("../util/constants");
7
+ const debug_1 = require("../util/debug");
7
8
  /**
8
9
  * 计算字符串的行列数
9
10
  * @param str 字符串
@@ -79,6 +80,22 @@ class AstNode {
79
80
  const childNodes = this.parentNode?.childNodes, i = childNodes?.indexOf(this);
80
81
  return childNodes?.slice(0, i).reverse().find((child) => child.type !== 'text');
81
82
  }
83
+ /** 后一个可见的兄弟节点 */
84
+ get nextVisibleSibling() {
85
+ let { nextSibling } = this;
86
+ while (nextSibling?.text() === '') {
87
+ ({ nextSibling } = nextSibling);
88
+ }
89
+ return nextSibling;
90
+ }
91
+ /** 前一个可见的兄弟节点 */
92
+ get previousVisibleSibling() {
93
+ let { previousSibling } = this;
94
+ while (previousSibling?.text() === '') {
95
+ ({ previousSibling } = previousSibling);
96
+ }
97
+ return previousSibling;
98
+ }
82
99
  /** 是否具有根节点 */
83
100
  get isConnected() {
84
101
  return this.getRootNode().type === 'root';
@@ -111,6 +128,34 @@ class AstNode {
111
128
  get fixed() {
112
129
  return false;
113
130
  }
131
+ /** 字体样式 */
132
+ get font() {
133
+ const { parentNode } = this, acceptable = parentNode?.getAttribute('acceptable');
134
+ if (!parentNode || acceptable && !('QuoteToken' in acceptable)) {
135
+ return { bold: false, italic: false };
136
+ }
137
+ const { childNodes } = parentNode, index = childNodes.indexOf(this), isQuote = (0, debug_1.isToken)('quote');
138
+ let bold = false, italic = false;
139
+ for (let i = index - 1; i >= 0; i--) {
140
+ const child = childNodes[i];
141
+ if (isQuote(child)) {
142
+ bold = child.bold ? !bold : bold;
143
+ italic = child.italic ? !italic : italic;
144
+ }
145
+ else if (child.type === 'text' && child.data.includes('\n')) {
146
+ break;
147
+ }
148
+ }
149
+ return { bold, italic };
150
+ }
151
+ /** 是否粗体 */
152
+ get bold() {
153
+ return this.font.bold;
154
+ }
155
+ /** 是否斜体 */
156
+ get italic() {
157
+ return this.font.italic;
158
+ }
114
159
  constructor() {
115
160
  Object.defineProperty(this, 'childNodes', { writable: false });
116
161
  Object.freeze(this.childNodes);
@@ -415,6 +460,21 @@ class AstNode {
415
460
  getBoundingClientRect() {
416
461
  return { ...this.#getDimension(), ...this.getRootNode().posFromIndex(this.getAbsoluteIndex()) };
417
462
  }
463
+ /** 销毁 */
464
+ destroy() {
465
+ this.parentNode?.destroy();
466
+ for (const child of this.childNodes) {
467
+ child.setAttribute('parentNode', undefined);
468
+ }
469
+ Object.setPrototypeOf(this, null);
470
+ }
471
+ /**
472
+ * 获取某一行的wikitext
473
+ * @param n 行号
474
+ */
475
+ getLine(n) {
476
+ return String(this).split('\n', n + 1)[n];
477
+ }
418
478
  }
419
479
  exports.AstNode = AstNode;
420
480
  constants_1.classes['AstNode'] = __filename;
@@ -119,12 +119,12 @@ export declare class Token extends AstElement {
119
119
  /** 深拷贝节点 */
120
120
  cloneNode(): this;
121
121
  /** 获取全部章节 */
122
- sections(): (AstText | Token)[][] | undefined;
122
+ sections(): AstRange[] | undefined;
123
123
  /**
124
124
  * 获取指定章节
125
125
  * @param n 章节序号
126
126
  */
127
- section(n: number): (AstText | Token)[] | undefined;
127
+ section(n: number): AstRange | undefined;
128
128
  /**
129
129
  * 获取指定的外层HTML标签
130
130
  * @param tag HTML标签名
@@ -3,11 +3,16 @@ import type { LintError, AST } from '../../base';
3
3
  /** `''`和`'''` */
4
4
  export declare abstract class QuoteToken extends NowikiBaseToken {
5
5
  readonly type = "quote";
6
- /** 是否粗体 */
6
+ /** @override */
7
7
  get bold(): boolean;
8
- /** 是否斜体 */
8
+ /** @override */
9
9
  get italic(): boolean;
10
10
  /** @override */
11
+ get font(): {
12
+ bold: boolean;
13
+ italic: boolean;
14
+ };
15
+ /** @override */
11
16
  lint(start?: number): LintError[];
12
17
  /** @override */
13
18
  json(): AST;
@@ -57,14 +57,20 @@ let QuoteToken = (() => {
57
57
  __runInitializers(_classThis, _classExtraInitializers);
58
58
  }
59
59
  type = 'quote';
60
- /** 是否粗体 */
60
+ /** @override */
61
61
  get bold() {
62
62
  return this.innerText.length !== 2;
63
63
  }
64
- /** 是否斜体 */
64
+ /** @override */
65
65
  get italic() {
66
66
  return this.innerText.length !== 3;
67
67
  }
68
+ /* NOT FOR BROWSER */
69
+ /** @override */
70
+ get font() {
71
+ return { bold: this.bold, italic: this.italic };
72
+ }
73
+ /* NOT FOR BROWSER END */
68
74
  /** @override */
69
75
  lint(start = this.getAbsoluteIndex()) {
70
76
  const { previousSibling, nextSibling, bold } = this, message = index_1.default.msg('lonely "$1"', `'`), errors = [];
@@ -11,7 +11,7 @@ exports.extUrlChar = `(?:${commonExtUrlChar}|\0\\d+[c!~]\x7F)*`;
11
11
  */
12
12
  const factory = (regex, replace) => (str) => str.replace(regex, replace);
13
13
  /** 清理解析专用的不可见字符 */
14
- exports.tidy = factory(/[\0\x7F]/gu, '');
14
+ exports.tidy = factory(/[\0\x7F]|\r$/gmu, '');
15
15
  /** remove half-parsed comment-like tokens */
16
16
  exports.removeComment = factory(/\0\d+c\x7F/gu, '');
17
17
  /** escape special chars for RegExp constructor */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wikiparser-node",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "A Node.js parser for MediaWiki markup with AST",
5
5
  "keywords": [
6
6
  "mediawiki",
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};