wikiparser-node 0.4.0 → 0.6.1

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 (87) hide show
  1. package/config/default.json +129 -66
  2. package/config/zhwiki.json +4 -4
  3. package/index.js +97 -65
  4. package/lib/element.js +159 -302
  5. package/lib/node.js +384 -198
  6. package/lib/ranges.js +3 -4
  7. package/lib/text.js +65 -36
  8. package/lib/title.js +9 -8
  9. package/mixin/fixedToken.js +4 -4
  10. package/mixin/hidden.js +2 -0
  11. package/mixin/sol.js +16 -7
  12. package/package.json +14 -3
  13. package/parser/brackets.js +8 -2
  14. package/parser/commentAndExt.js +1 -1
  15. package/parser/converter.js +1 -1
  16. package/parser/externalLinks.js +2 -2
  17. package/parser/hrAndDoubleUnderscore.js +8 -7
  18. package/parser/links.js +8 -9
  19. package/parser/magicLinks.js +1 -1
  20. package/parser/selector.js +5 -5
  21. package/parser/table.js +18 -16
  22. package/src/arg.js +71 -42
  23. package/src/atom/index.js +7 -5
  24. package/src/attribute.js +102 -64
  25. package/src/charinsert.js +91 -0
  26. package/src/converter.js +34 -15
  27. package/src/converterFlags.js +87 -40
  28. package/src/converterRule.js +59 -53
  29. package/src/extLink.js +45 -37
  30. package/src/gallery.js +71 -16
  31. package/src/hasNowiki/index.js +42 -0
  32. package/src/hasNowiki/pre.js +40 -0
  33. package/src/heading.js +41 -18
  34. package/src/html.js +76 -48
  35. package/src/imageParameter.js +73 -51
  36. package/src/imagemap.js +205 -0
  37. package/src/imagemapLink.js +43 -0
  38. package/src/index.js +243 -138
  39. package/src/link/category.js +10 -14
  40. package/src/link/file.js +112 -56
  41. package/src/link/galleryImage.js +74 -10
  42. package/src/link/index.js +86 -61
  43. package/src/magicLink.js +48 -21
  44. package/src/nested/choose.js +24 -0
  45. package/src/nested/combobox.js +23 -0
  46. package/src/nested/index.js +88 -0
  47. package/src/nested/references.js +23 -0
  48. package/src/nowiki/comment.js +18 -4
  49. package/src/nowiki/dd.js +2 -2
  50. package/src/nowiki/doubleUnderscore.js +16 -11
  51. package/src/nowiki/index.js +12 -0
  52. package/src/nowiki/quote.js +28 -1
  53. package/src/onlyinclude.js +15 -8
  54. package/src/paramTag/index.js +83 -0
  55. package/src/paramTag/inputbox.js +42 -0
  56. package/src/parameter.js +73 -46
  57. package/src/syntax.js +9 -1
  58. package/src/table/index.js +58 -44
  59. package/src/table/td.js +63 -63
  60. package/src/table/tr.js +52 -35
  61. package/src/tagPair/ext.js +60 -43
  62. package/src/tagPair/include.js +11 -1
  63. package/src/tagPair/index.js +29 -20
  64. package/src/transclude.js +214 -166
  65. package/tool/index.js +720 -439
  66. package/util/base.js +17 -0
  67. package/util/debug.js +1 -1
  68. package/{test/util.js → util/diff.js} +15 -19
  69. package/util/lint.js +40 -0
  70. package/util/string.js +37 -20
  71. package/.eslintrc.json +0 -714
  72. package/errors/README +0 -1
  73. package/jsconfig.json +0 -7
  74. package/printed/README +0 -1
  75. package/printed/example.json +0 -120
  76. package/test/api.js +0 -83
  77. package/test/real.js +0 -133
  78. package/test/test.js +0 -28
  79. package/typings/api.d.ts +0 -13
  80. package/typings/array.d.ts +0 -28
  81. package/typings/event.d.ts +0 -24
  82. package/typings/index.d.ts +0 -94
  83. package/typings/node.d.ts +0 -29
  84. package/typings/parser.d.ts +0 -16
  85. package/typings/table.d.ts +0 -14
  86. package/typings/token.d.ts +0 -22
  87. package/typings/tool.d.ts +0 -11
package/tool/index.js CHANGED
@@ -1,60 +1,100 @@
1
- /* eslint no-underscore-dangle: [2, {allowAfterThis: true, enforceInMethodNames: false, allow: ['__filename']}] */
2
1
  'use strict';
3
2
 
4
- const {typeError, externalUse} = require('../util/debug'),
5
- {text, noWrap} = require('../util/string'),
6
- AstText = require('../lib/text'),
3
+ const {typeError} = require('../util/debug'),
4
+ {text} = require('../util/string'),
5
+ {isPlainObject} = require('../util/base'),
6
+ Parser = require('..'),
7
+ AstNode = require('../lib/node'),
7
8
  Token = require('../src'),
8
- assert = require('assert/strict');
9
+ AttributeToken = require('../src/attribute');
9
10
 
10
- const /** @type {WeakMap<Token, Record<string, any>>} */ dataStore = new WeakMap();
11
+ const /** @type {WeakMap<Token, Record<string, *>>} */ cache = new WeakMap();
11
12
 
12
- /**
13
- * @param {string} method
14
- * @param {AstText|Token|Token[]} selector
15
- * @returns {(token: Token) => boolean}
16
- */
17
- const matchesGenerator = (method, selector) => {
18
- if (typeof selector === 'string') {
19
- return token => token instanceof Token && token.matches(selector);
20
- } else if (Array.isArray(selector)) {
21
- return token => selector.includes(token);
22
- } else if (selector instanceof Token) {
23
- return token => token === selector;
24
- }
25
- return typeError(TokenCollection, method, 'String', 'Token', 'Array'); // eslint-disable-line no-use-before-define
26
- };
27
-
28
- /** @extends {Array<Token>} */
29
- class TokenCollection extends Array {
30
- /** @type {TokenCollection} */ prevObject;
13
+ /** Token集合 */
14
+ class TokenCollection {
15
+ /** @type {AstNode[]} */ array = [];
31
16
  /** @type {Set<Token>} */ #roots = new Set();
17
+ /** @type {TokenCollection} */ prevObject;
18
+
19
+ /**
20
+ * 生成匹配函数
21
+ * @param {string} method
22
+ * @param {string|AstNode|Iterable<AstNode>} selector
23
+ * @returns {(token: AstNode) => boolean}
24
+ */
25
+ static #matchesGenerator = (method, selector) => {
26
+ if (selector === undefined || typeof selector === 'string') {
27
+ return token => token instanceof Token && token.matches(selector);
28
+ } else if (selector?.[Symbol.iterator]) {
29
+ return token => new Set(selector).has(token);
30
+ }
31
+ return selector instanceof AstNode
32
+ ? token => token === selector
33
+ : typeError(TokenCollection, method, 'String', 'AstNode', 'Iterable');
34
+ };
35
+
36
+ /** @ignore */
37
+ [Symbol.iterator]() {
38
+ return this.array[Symbol.iterator]();
39
+ }
40
+
41
+ /** 数组长度 */
42
+ get length() {
43
+ return this.array.length;
44
+ }
45
+
46
+ /** 数组长度 */
47
+ size() {
48
+ return this.array.length;
49
+ }
50
+
51
+ /**
52
+ * 筛选Token节点
53
+ * @returns {Token[]}
54
+ */
55
+ get #tokens() {
56
+ return this.array.filter(ele => ele instanceof Token);
57
+ }
32
58
 
33
- /** @param {...string|Token} arr */
34
- constructor(...arr) {
35
- super();
36
- if (arr.length === 1 && arr[0] === 0) {
37
- return;
59
+ /**
60
+ * 第一个Token节点
61
+ * @returns {Token}
62
+ */
63
+ get #firstToken() {
64
+ return this.array.find(ele => ele instanceof Token);
65
+ }
66
+
67
+ /**
68
+ * @param {Iterable<AstNode>} arr 节点数组
69
+ * @param {TokenCollection} prevObject 前身
70
+ */
71
+ constructor(arr, prevObject) {
72
+ if (prevObject && !(prevObject instanceof TokenCollection)) {
73
+ this.typeError('constructor', 'TokenCollection');
38
74
  }
39
75
  for (const token of arr) {
40
76
  if (token === undefined) {
41
77
  continue;
42
- } else if (token instanceof AstText) {
43
- this.push(token);
44
- } else if (!(token instanceof Token)) {
45
- this.typeError('constructor', 'AstText', 'Token');
46
- } else if (!this.includes(token)) {
78
+ } else if (!(token instanceof AstNode)) {
79
+ this.typeError('constructor', 'AstNode');
80
+ } else if (!this.array.includes(token)) {
47
81
  this.#roots.add(token.getRootNode());
48
- this.push(token);
82
+ this.array.push(token);
49
83
  }
50
84
  }
51
- Object.defineProperty(this, 'prevObject', {enumerable: false});
85
+ this.prevObject = prevObject;
52
86
  this.#sort();
87
+ Object.defineProperties(this, {
88
+ array: {writable: false, configurable: false},
89
+ prevObject: {enumerable: prevObject, writable: false, configurable: false},
90
+ });
91
+ Object.freeze(this.array);
53
92
  }
54
93
 
55
94
  /**
95
+ * 抛出TypeError
56
96
  * @param {string} method
57
- * @param {...string} types
97
+ * @param {...string} types 可接受的参数类型
58
98
  */
59
99
  typeError(method, ...types) {
60
100
  return typeError(this.constructor, method, ...types);
@@ -62,316 +102,399 @@ class TokenCollection extends Array {
62
102
 
63
103
  /** 节点排序 */
64
104
  #sort() {
65
- if (this.some(({type}) => type === 'text')) {
66
- return;
67
- }
68
105
  const rootArray = [...this.#roots];
69
- this.sort((a, b) => {
106
+ this.array.sort(/** @type {(a: AstNode, b: AstNode) => boolean} */ (a, b) => {
70
107
  const aRoot = a.getRootNode(),
71
108
  bRoot = b.getRootNode();
72
109
  return aRoot === bRoot ? a.compareDocumentPosition(b) : rootArray.indexOf(aRoot) - rootArray.indexOf(bRoot);
73
110
  });
74
111
  }
75
112
 
113
+ /** 转换为数组 */
76
114
  toArray() {
77
115
  return [...this];
78
116
  }
79
117
 
80
- _filter(selector = '') {
81
- const arr = this.toArray().filter(ele => ele instanceof Token);
82
- if (selector) {
83
- return arr.filter(ele => ele.matches(selector));
84
- }
85
- return arr;
86
- }
87
-
88
- _find() {
89
- return super.find(ele => ele instanceof Token);
90
- }
91
-
92
- /** @param {(arr: Token[]) => (string|Token)[]} map */
93
- _create(map, filter = '') {
94
- const $arr = $(map(this._filter())),
95
- $filtered = filter ? $arr.filter(filter) : $arr;
96
- $filtered.prevObject = this;
97
- return $filtered;
98
- }
99
-
100
118
  /**
119
+ * 提取第n个元素
101
120
  * @template {unknown} T
102
- * @param {T} num
103
- * @returns {T extends number ? string|Token : (string|Token)[]}
121
+ * @param {T} n 序号
122
+ * @returns {T extends number ? AstNode : AstNode[]}
104
123
  */
105
- get(num) {
106
- if (typeof num === 'number') {
107
- return this.at(num);
124
+ get(n) {
125
+ if (Number.isInteger(n)) {
126
+ return this.array.at(n);
108
127
  }
109
- return this.toArray();
128
+ return n === undefined ? this.toArray() : this.typeError('get', 'Number');
110
129
  }
111
130
 
112
- /** @param {CollectionCallback<void, string|Token>} callback */
131
+ /**
132
+ * 循环
133
+ * @param {CollectionCallback<void, AstNode>} callback
134
+ */
113
135
  each(callback) {
114
136
  for (let i = 0; i < this.length; i++) {
115
- callback.call(this[i], i, this[i]);
137
+ const ele = this.array[i];
138
+ callback.call(ele, i, ele);
116
139
  }
117
140
  return this;
118
141
  }
119
142
 
120
- /** @param {CollectionCallback<any, string|Token>} callback */
143
+ /**
144
+ * map方法
145
+ * @param {CollectionCallback<*, AstNode>} callback
146
+ */
121
147
  map(callback) {
122
- const arr = this.toArray().map((ele, i) => callback.call(ele, i, ele));
148
+ const arr = this.array.map((ele, i) => callback.call(ele, i, ele));
123
149
  try {
124
- const $arr = $(arr);
125
- $arr.prevObject = this;
126
- return $arr;
150
+ return new TokenCollection(arr, this);
127
151
  } catch {
128
152
  return arr;
129
153
  }
130
154
  }
131
155
 
132
156
  /**
133
- * @param {number} start
134
- * @param {number} end
157
+ * 子序列
158
+ * @param {number} start 起点
159
+ * @param {number} end 终点
135
160
  */
136
161
  slice(start, end) {
137
- const $arr = $(this.toArray().slice(start, end));
138
- $arr.prevObject = this;
139
- return $arr;
162
+ return new TokenCollection(this.array.slice(start, end), this);
140
163
  }
141
164
 
165
+ /** 第一个元素 */
142
166
  first() {
143
167
  return this.slice(0, 1);
144
168
  }
145
169
 
170
+ /** 最后一个元素 */
146
171
  last() {
147
172
  return this.slice(-1);
148
173
  }
149
174
 
150
- /** @param {number} i */
175
+ /**
176
+ * 任一元素
177
+ * @param {number} i 序号
178
+ */
151
179
  eq(i) {
152
180
  return this.slice(i, i === -1 ? undefined : i + 1);
153
181
  }
154
182
 
155
183
  /** 使用空字符串join */
156
184
  toString() {
157
- return this.toArray().map(String).join('');
185
+ return this.array.map(String).join('');
158
186
  }
159
187
 
160
188
  /**
189
+ * 输出有效部分或设置文本
161
190
  * @template {unknown} T
162
- * @param {T} str
163
- * @returns {T extends string|Function ? this : string}
191
+ * @param {T} str 新文本
192
+ * @returns {T extends string|CollectionCallback<string, string> ? this : string}
164
193
  */
165
194
  text(str) {
166
- /** @type {(ele: Token, i: number, str: string) => string} */
167
- const callback = typeof str === 'function' ? str.call : () => str;
195
+ const /** @type {CollectionCallback<string, string> */ callback = typeof str === 'function' ? str : () => str;
168
196
  if (typeof str === 'string' || typeof str === 'function') {
169
197
  for (let i = 0; i < this.length; i++) {
170
- const ele = this[i];
198
+ const ele = this.array[i];
171
199
  if (ele instanceof Token) {
172
200
  try {
173
- ele.replaceChildren(callback(ele, i, ele.text()));
201
+ ele.replaceChildren(callback.call(ele, i, ele.text()));
174
202
  } catch {}
175
203
  }
176
204
  }
177
205
  return this;
178
206
  }
179
- return text(this.toArray());
207
+ return str === undefined ? text(this.toArray()) : this.typeError('text', 'String', 'Function');
180
208
  }
181
209
 
182
- /** @param {string|CollectionCallback<boolean, string|Token>|Token|Token[]} selector */
210
+ /**
211
+ * 判断是否存在元素满足选择器
212
+ * @param {string|AstNode|Iterable<AstNode>|CollectionCallback<boolean, AstNode>} selector
213
+ */
183
214
  is(selector) {
184
- if (typeof selector === 'function') {
185
- return this.some((ele, i) => selector.call(ele, i, ele));
186
- }
187
- const matches = matchesGenerator('is', selector);
188
- return this.some(matches);
215
+ return typeof selector === 'function'
216
+ ? this.array.some((ele, i) => selector.call(ele, i, ele))
217
+ : this.array.some(TokenCollection.#matchesGenerator('is', selector));
189
218
  }
190
219
 
191
- /** @param {string|CollectionCallback<boolean, string|Token>|Token|Token[]} selector */
220
+ /**
221
+ * 筛选满足选择器的元素
222
+ * @param {string|AstNode|Iterable<AstNode>|CollectionCallback<boolean, AstNode>} selector
223
+ */
192
224
  filter(selector) {
193
- const arr = this.toArray();
194
- let $arr;
195
- if (typeof selector === 'function') {
196
- $arr = $(arr.filter((ele, i) => selector.call(ele, i, ele)));
197
- } else {
198
- const matches = matchesGenerator('filter', selector);
199
- $arr = $(arr.filter(matches));
200
- }
201
- $arr.prevObject = this;
202
- return $arr;
225
+ return new TokenCollection(this.array.filter(
226
+ (ele, i) => typeof selector === 'function'
227
+ ? selector.call(ele, i, ele)
228
+ : TokenCollection.#matchesGenerator('filter', selector)(ele),
229
+ ), this);
203
230
  }
204
231
 
205
- /** @param {string|CollectionCallback<boolean, string|Token>|Token|Token[]} selector */
232
+ /**
233
+ * 筛选不满足选择器的元素
234
+ * @param {string|AstNode|Iterable<AstNode>|CollectionCallback<boolean, AstNode>} selector
235
+ */
206
236
  not(selector) {
207
- const arr = this.toArray();
208
- let $arr;
237
+ let /** @type {(ele: AstNode, i: number) => boolean} */ callback;
209
238
  if (typeof selector === 'function') {
210
- $arr = $(arr.filter((ele, i) => !selector.call(ele, i, ele)));
211
- } else if (typeof selector === 'string') {
212
- $arr = $(arr.filter(ele => ele instanceof Token && !ele.matches(selector)));
239
+ callback = /** @implements */ (ele, i) => !selector.call(ele, i, ele);
213
240
  } else {
214
- const matches = matchesGenerator('not', selector);
215
- $arr = $(arr.filter(ele => !matches(ele)));
241
+ const matches = TokenCollection.#matchesGenerator('not', selector);
242
+ callback = /** @implements */ ele => !matches(ele);
216
243
  }
217
- $arr.prevObject = this;
218
- return $arr;
244
+ return new TokenCollection(this.array.filter(callback), this);
219
245
  }
220
246
 
221
- /** @param {string|Token|Token[]} selector */
247
+ /**
248
+ * 搜索满足选择器的子节点
249
+ * @param {string|AstNode|Iterable<AstNode>} selector
250
+ */
222
251
  find(selector) {
223
- let /** @type {CollectionMap} */ map;
224
- if (typeof selector === 'string') {
225
- map = arr => arr.flatMap(token => token.querySelectorAll(selector));
226
- } else if (Array.isArray(selector)) {
227
- map = arr => [...selector].filter(ele => arr.some(token => token.contains(ele)));
228
- } else if (selector instanceof Token) {
229
- map = arr => arr.some(token => token.contains(selector)) ? [selector] : [];
252
+ let arr;
253
+ if (selector === undefined || typeof selector === 'string') {
254
+ arr = this.array.flatMap(token => token instanceof Token ? token.querySelectorAll(selector) : []);
255
+ } else if (selector?.[Symbol.iterator]) {
256
+ arr = [...selector].filter(ele => this.array.some(token => token.contains(ele)));
257
+ } else if (selector instanceof AstNode) {
258
+ arr = this.array.some(token => token.contains(selector)) ? [selector] : [];
230
259
  } else {
231
- this.typeError('find', 'String', 'Token', 'Array');
260
+ this.typeError('find', 'String', 'AstNode', 'Iterable');
232
261
  }
233
- return this._create(map);
262
+ return new TokenCollection(arr, this);
234
263
  }
235
264
 
236
- /** @param {string|Token} selector */
265
+ /**
266
+ * 是否存在满足条件的后代节点
267
+ * @param {string|AstNode} selector
268
+ */
237
269
  has(selector) {
238
- const arr = this._filter();
239
- if (typeof selector === 'string') {
240
- return arr.some(ele => ele.querySelector(selector));
241
- } else if (selector instanceof Token) {
242
- return arr.some(ele => ele.contains(selector));
270
+ if (selector === undefined || typeof selector === 'string') {
271
+ return this.array.some(ele => ele instanceof Token && ele.querySelector(selector));
243
272
  }
244
- return this.typeError('has', 'String', 'Token');
273
+ return selector instanceof AstNode
274
+ ? this.array.some(ele => ele.contains(selector))
275
+ : this.typeError('has', 'String', 'AstNode');
245
276
  }
246
277
 
247
- /** @param {string} selector */
278
+ /**
279
+ * 最近的祖先
280
+ * @param {string} selector
281
+ */
248
282
  closest(selector) {
249
- if (typeof selector !== 'string') {
250
- this.typeError('closest', 'String');
283
+ if (selector === undefined) {
284
+ return new TokenCollection(this.array.map(ele => ele.parentNode), this);
251
285
  }
252
- return this._create(arr => arr.map(ele => ele.closest(selector)));
286
+ return typeof selector === 'string'
287
+ ? new TokenCollection(this.#tokens.map(ele => ele.closest(selector)), this)
288
+ : this.typeError('closest', 'String');
253
289
  }
254
290
 
291
+ /** 第一个Token节点在父容器中的序号 */
255
292
  index() {
256
- const firstToken = this._find();
257
- if (!firstToken) {
258
- return -1;
293
+ const {array: [firstNode]} = this;
294
+ if (firstNode) {
295
+ const {parentNode} = firstNode;
296
+ return parentNode ? parentNode.childNodes.indexOf(firstNode) : 0;
259
297
  }
260
- const {parentNode} = firstToken;
261
- return parentNode ? parentNode.childNodes.indexOf(firstToken) : 0;
298
+ return -1;
262
299
  }
263
300
 
264
- /** @param {string|Token|(string|Token)[]} elements */
301
+ /**
302
+ * 添加元素
303
+ * @param {AstNode|Iterable<AstNode>} elements 新增的元素
304
+ */
265
305
  add(elements) {
266
- elements = Array.isArray(elements) ? elements : [elements];
267
- const $arr = $([...this, ...elements]);
268
- $arr.prevObject = this;
269
- return $arr;
306
+ return new TokenCollection([...this, ...Symbol.iterator in elements ? elements : [elements]], this);
270
307
  }
271
308
 
272
- /** @param {string} selector */
309
+ /**
310
+ * 添加prevObject
311
+ * @param {string} selector
312
+ */
273
313
  addBack(selector) {
274
314
  return this.add(selector ? this.prevObject.filter(selector) : this.prevObject);
275
315
  }
276
316
 
277
- parent(selector = '') {
278
- return this._create(arr => arr.map(ele => ele.parentNode), selector);
317
+ /**
318
+ * 带选择器筛选的map
319
+ * @param {string} method
320
+ * @param {(ele: AstNode) => Token} callback
321
+ * @param {string} selector
322
+ */
323
+ #map(method, callback, selector) {
324
+ return selector === undefined || typeof selector === 'string'
325
+ ? new TokenCollection(this.array.map(callback).filter(ele => ele.matches(selector)), this)
326
+ : this.typeError(method, 'String');
327
+ }
328
+
329
+ /**
330
+ * 带选择器筛选的flatMap
331
+ * @param {string} method
332
+ * @param {(ele: AstNode) => Token|Token[]} callback
333
+ * @param {string} selector
334
+ */
335
+ #flatMap(method, callback, selector) {
336
+ return selector === undefined || typeof selector === 'string'
337
+ ? new TokenCollection(this.array.flatMap(callback).filter(ele => ele.matches(selector)), this)
338
+ : this.typeError(method, 'String');
339
+ }
340
+
341
+ /**
342
+ * 父元素
343
+ * @param {string} selector
344
+ */
345
+ parent(selector) {
346
+ return this.#map('parent', ele => ele.parentNode, selector);
279
347
  }
280
348
 
281
- parents(selector = '') {
282
- return this._create(arr => arr.flatMap(ele => ele.getAncestors()), selector);
349
+ /**
350
+ * 祖先
351
+ * @param {string} selector
352
+ */
353
+ parents(selector) {
354
+ return this.#flatMap('parents', ele => ele.getAncestors(), selector);
283
355
  }
284
356
 
285
- next(selector = '') {
286
- return this._create(arr => arr.map(ele => ele.nextElementSibling), selector);
357
+ /**
358
+ * nextElementSibling
359
+ * @param {string} selector
360
+ */
361
+ next(selector) {
362
+ return this.#map('next', ele => ele.nextElementSibling, selector);
287
363
  }
288
364
 
289
- prev(selector = '') {
290
- return this._create(arr => arr.map(ele => ele.previousElementSibling), selector);
365
+ /**
366
+ * previousElementSibling
367
+ * @param {string} selector
368
+ */
369
+ prev(selector) {
370
+ return this.#map('prev', ele => ele.previousElementSibling, selector);
291
371
  }
292
372
 
293
373
  /**
294
- * @param {number|(i: number) => number} start
295
- * @param {number|(i: number) => number} count
374
+ * 筛选兄弟
375
+ * @param {string} method
376
+ * @param {(i: number) => number} start 起点
377
+ * @param {(i: number) => number} count 数量
378
+ * @param {string} selector
296
379
  */
297
- _siblings(start, count, selector = '') {
298
- return this._create(arr => arr.flatMap(ele => {
380
+ #siblings(method, start, count, selector) {
381
+ if (selector !== undefined && typeof selector !== 'string') {
382
+ this.typeError(method, 'String');
383
+ }
384
+ return new TokenCollection(this.array.flatMap(ele => {
299
385
  const {parentNode} = ele;
300
386
  if (!parentNode) {
301
- return undefined;
387
+ return [];
302
388
  }
303
- const {children} = parentNode,
304
- i = children.indexOf(ele);
305
- children.splice(
306
- typeof start === 'function' ? start(i) : start,
307
- typeof count === 'function' ? count(i) : count,
308
- );
309
- return children;
310
- }), selector);
389
+ const {childNodes} = parentNode,
390
+ i = childNodes.indexOf(ele);
391
+ childNodes.splice(start(i), count(i));
392
+ return childNodes;
393
+ }).filter(ele => ele instanceof Token && ele.matches(selector)), this);
311
394
  }
312
395
 
313
- nextAll(selector = '') {
314
- return this._siblings(0, i => i + 1, selector);
396
+ /**
397
+ * 所有在后的兄弟
398
+ * @param {string} selector
399
+ */
400
+ nextAll(selector) {
401
+ return this.#siblings('nextAll', () => 0, i => i + 1, selector);
315
402
  }
316
403
 
317
- prevAll(selector = '') {
318
- return this._siblings(i => i, Infinity, selector);
404
+ /**
405
+ * 所有在前的兄弟
406
+ * @param {string} selector
407
+ */
408
+ prevAll(selector) {
409
+ return this.#siblings('prevAll', i => i, () => Infinity, selector);
319
410
  }
320
411
 
321
- siblings(selector = '') {
322
- return this._siblings(i => i, 1, selector);
412
+ /**
413
+ * 所有在后的兄弟
414
+ * @param {string} selector
415
+ */
416
+ siblings(selector) {
417
+ return this.#siblings('siblings', i => i, () => 1, selector);
323
418
  }
324
419
 
325
420
  /**
421
+ * 直到选择器被满足
326
422
  * @param {'parents'|'nextAll'|'prevAll'} method
327
- * @param {string|Token|Token[]} selector
423
+ * @param {string|Token|Iterable<Token>} selector
424
+ * @param {string} filter 额外的筛选选择器
328
425
  */
329
- _until(method, selector, filter = '') {
330
- const matches = matchesGenerator(`${method.replace(/All$/u, '')}Until`, selector);
331
- return this._create(arr => arr.flatMap(ele => {
332
- const tokens = $(ele)[method]().toArray(),
333
- tokenArray = method === 'nextAll' ? tokens : tokens.reverse(),
334
- until = tokenArray.findIndex(end => matches(end));
335
- return tokenArray.slice(0, until);
336
- }), filter);
426
+ #until(method, selector, filter) {
427
+ const originalMethod = `${method.replace(/All$/u, '')}Until`;
428
+ if (filter !== undefined && typeof filter !== 'string') {
429
+ this.typeError(originalMethod, 'String');
430
+ }
431
+ const matches = TokenCollection.#matchesGenerator(originalMethod, selector);
432
+ return new TokenCollection(this.array.flatMap(ele => {
433
+ const /** @type {{array: Token[]}} */ {array} = $(ele)[method](),
434
+ until = array[method === 'nextAll' ? 'findIndex' : 'findLastIndex'](end => matches(end));
435
+ return method === 'nextAll' ? array.slice(0, until) : array.slice(until + 1);
436
+ }).filter(ele => ele.matches(filter)), this);
337
437
  }
338
438
 
339
- /** @param {string|Token|Token[]} selector */
340
- parentsUntil(selector, filter = '') {
341
- return this._until('parents', selector, filter);
439
+ /**
440
+ * 直到选择器被满足的祖先
441
+ * @param {string|Token|Iterable<Token>} selector
442
+ * @param {string} filter 额外的筛选选择器
443
+ */
444
+ parentsUntil(selector, filter) {
445
+ return this.#until('parents', selector, filter);
342
446
  }
343
447
 
344
- /** @param {string|Token|Token[]} selector */
345
- nextUntil(selector, filter = '') {
346
- return this._until('nextAll', selector, filter);
448
+ /**
449
+ * 直到选择器被满足的后方兄弟
450
+ * @param {string|Token|Iterable<Token>} selector
451
+ * @param {string} filter 额外的筛选选择器
452
+ */
453
+ nextUntil(selector, filter) {
454
+ return this.#until('nextAll', selector, filter);
347
455
  }
348
456
 
349
- /** @param {string|Token|Token[]} selector */
350
- prevUntil(selector, filter = '') {
351
- return this._until('prevAll', selector, filter);
457
+ /**
458
+ * 直到选择器被满足的前方兄弟
459
+ * @param {string|Token|Iterable<Token>} selector
460
+ * @param {string} filter 额外的筛选选择器
461
+ */
462
+ prevUntil(selector, filter) {
463
+ return this.#until('prevAll', selector, filter);
352
464
  }
353
465
 
354
- children(selector = '') {
355
- return this._create(arr => arr.flatMap(ele => ele.children), selector);
466
+ /**
467
+ * Token子节点
468
+ * @param {string} selector
469
+ */
470
+ children(selector) {
471
+ return selector === undefined || typeof selector === 'string'
472
+ ? new TokenCollection(this.#tokens.flatMap(ele => ele.children).filter(ele => ele.matches(selector)), this)
473
+ : this.typeError('children', 'String');
356
474
  }
357
475
 
476
+ /** 所有子节点 */
358
477
  contents() {
359
- return this._create(arr => arr.flatMap(ele => ele.childNodes));
478
+ return new TokenCollection(this.array.flatMap(ele => ele.childNodes), this);
360
479
  }
361
480
 
362
- /** @param {[string|Record<string, any>]} key */
481
+ /**
482
+ * 存取数据
483
+ * @param {string|Record<string, *>} key 键
484
+ * @param {*} value 值
485
+ */
363
486
  data(key, value) {
364
487
  if (value !== undefined && typeof key !== 'string') {
365
488
  this.typeError('data', 'String');
366
- } else if (value === undefined && typeof key !== 'object') {
367
- const data = $.dataStore.get(this._find());
489
+ } else if (value === undefined && !isPlainObject(key)) {
490
+ const data = cache.get(this.#firstToken);
368
491
  return key === undefined ? data : data?.[key];
369
492
  }
370
- for (const token of this._filter()) {
371
- if (!$.dataStore.has(token)) {
372
- $.dataStore.set(token, {});
493
+ for (const token of this.#tokens) {
494
+ if (!cache.has(token)) {
495
+ cache.set(token, {});
373
496
  }
374
- const data = $.dataStore.get(token);
497
+ const data = cache.get(token);
375
498
  if (typeof key === 'string') {
376
499
  data[key] = value;
377
500
  } else {
@@ -381,19 +504,22 @@ class TokenCollection extends Array {
381
504
  return this;
382
505
  }
383
506
 
384
- /** @param {string|string[]} name */
507
+ /**
508
+ * 删除数据
509
+ * @param {string|string[]} name 键
510
+ */
385
511
  removeData(name) {
386
512
  if (name !== undefined && typeof name !== 'string' && !Array.isArray(name)) {
387
513
  this.typeError('removeData', 'String', 'Array');
388
514
  }
389
515
  name = typeof name === 'string' ? name.split(/\s/u) : name;
390
- for (const token of this._filter()) {
391
- if (!$.dataStore.has(token)) {
516
+ for (const token of this.#tokens) {
517
+ if (!cache.has(token)) {
392
518
  continue;
393
519
  } else if (name === undefined) {
394
- $.dataStore.delete(token);
520
+ cache.delete(token);
395
521
  } else {
396
- const data = $.dataStore.get(token);
522
+ const data = cache.get(token);
397
523
  for (const key of name) {
398
524
  delete data[key];
399
525
  }
@@ -403,12 +529,14 @@ class TokenCollection extends Array {
403
529
  }
404
530
 
405
531
  /**
406
- * @param {string|Record<string, AstListener>} events
532
+ * 添加事件监听
533
+ * @param {string|Record<string, AstListener>} events 事件名
407
534
  * @param {string|AstListener} selector
408
- * @param {AstListener} handler
535
+ * @param {AstListener} handler 事件处理
536
+ * @param {boolean} once 是否一次性
409
537
  */
410
- _addEventListener(events, selector, handler, once = false) {
411
- if (typeof events !== 'string' && typeof events !== 'object') {
538
+ #addEventListener(events, selector, handler, once = false) {
539
+ if (typeof events !== 'string' && !isPlainObject(events)) {
412
540
  this.typeError(once ? 'once' : 'on', 'String', 'Object');
413
541
  } else if (typeof selector === 'function') {
414
542
  handler = selector;
@@ -417,59 +545,68 @@ class TokenCollection extends Array {
417
545
  const eventPair = typeof events === 'string'
418
546
  ? events.split(/\s/u).map(/** @returns {[string, AstListener]} */ event => [event, handler])
419
547
  : Object.entries(events);
420
- for (const token of this._filter(selector)) {
421
- for (const [event, listener] of eventPair) {
422
- token.addEventListener(event, listener, {once});
548
+ for (const token of this.#tokens) {
549
+ if (token.matches(selector)) {
550
+ for (const [event, listener] of eventPair) {
551
+ token.addEventListener(event, listener, {once});
552
+ }
423
553
  }
424
554
  }
425
555
  return this;
426
556
  }
427
557
 
428
558
  /**
429
- * @param {string|Record<string, AstListener>} events
559
+ * 添加事件监听
560
+ * @param {string|Record<string, AstListener>} events 事件名
430
561
  * @param {string|AstListener} selector
431
- * @param {AstListener} handler
562
+ * @param {AstListener} handler 事件处理
432
563
  */
433
564
  on(events, selector, handler) {
434
- return this._addEventListener(events, selector, handler);
565
+ return this.#addEventListener(events, selector, handler);
435
566
  }
436
567
 
437
568
  /**
438
- * @param {string|Record<string, AstListener>} events
569
+ * 添加一次性事件监听
570
+ * @param {string|Record<string, AstListener>} events 事件名
439
571
  * @param {string|AstListener} selector
440
- * @param {AstListener} handler
572
+ * @param {AstListener} handler 事件处理
441
573
  */
442
574
  one(events, selector, handler) {
443
- return this._addEventListener(events, selector, handler, true);
575
+ return this.#addEventListener(events, selector, handler, true);
444
576
  }
445
577
 
446
578
  /**
447
- * @param {string|Record<string, AstListener>|undefined} events
579
+ * 移除事件监听
580
+ * @param {string|Record<string, AstListener|undefined>|undefined} events 事件名
448
581
  * @param {string|AstListener} selector
449
- * @param {AstListener} handler
582
+ * @param {AstListener} handler 事件处理
450
583
  */
451
584
  off(events, selector, handler) {
452
- if (typeof events !== 'string' && typeof events !== 'object' && events !== undefined) {
585
+ if (events !== undefined && typeof events !== 'string' && !isPlainObject(events)) {
453
586
  this.typeError('off', 'String', 'Object');
587
+ } else if (typeof selector === 'function') {
588
+ handler = selector;
589
+ selector = undefined;
454
590
  }
455
- handler = typeof selector === 'function' ? selector : handler;
456
591
  let eventPair;
457
592
  if (events) {
458
593
  eventPair = typeof events === 'string'
459
594
  ? events.split(/\s/u).map(/** @returns {[string, AstListener]} */ event => [event, handler])
460
595
  : Object.entries(events);
461
596
  }
462
- for (const token of this._filter(selector)) {
463
- if (events === undefined) {
597
+ for (const token of this.#tokens) {
598
+ if (!token.matches(selector)) {
599
+ continue;
600
+ } else if (events === undefined) {
464
601
  token.removeAllEventListeners();
465
602
  } else {
466
603
  for (const [event, listener] of eventPair) {
467
- if (typeof event !== 'string' || typeof listener !== 'function' && listener !== undefined) {
468
- this.typeError('off', 'String', 'Function');
469
- } else if (listener) {
604
+ if (listener === undefined) {
605
+ token.removeAllEventListeners(event);
606
+ } else if (typeof listener === 'function') {
470
607
  token.removeEventListener(event, listener);
471
608
  } else {
472
- token.removeAllEventListeners(event);
609
+ this.typeError('off', 'String', 'Function');
473
610
  }
474
611
  }
475
612
  }
@@ -477,44 +614,52 @@ class TokenCollection extends Array {
477
614
  return this;
478
615
  }
479
616
 
480
- /** @param {string|Event} event */
617
+ /**
618
+ * 触发事件
619
+ * @param {string|Event} event 事件名
620
+ * @param {*} data 事件数据
621
+ */
481
622
  trigger(event, data) {
482
- for (const token of this._filter()) {
623
+ for (const token of this) {
483
624
  const e = typeof event === 'string' ? new Event(event, {bubbles: true}) : new Event(event.type, event);
484
625
  token.dispatchEvent(e, data);
485
626
  }
486
627
  return this;
487
628
  }
488
629
 
489
- /** @param {string|Event} event */
630
+ /**
631
+ * 伪装触发事件
632
+ * @param {string|Event} event 事件名
633
+ * @param {*} data 事件数据
634
+ */
490
635
  triggerHandler(event, data) {
491
- const firstToken = this._find();
492
- if (!firstToken) {
636
+ const {array: [firstNode]} = this;
637
+ if (!firstNode) {
493
638
  return undefined;
494
639
  }
495
- const e = typeof event === 'string' ? new Event(event) : event,
496
- listeners = firstToken.listEventListeners(typeof event === 'string' ? event : event.type);
640
+ const e = typeof event === 'string' ? new Event(event) : event;
497
641
  let result;
498
- for (const listener of listeners) {
642
+ for (const listener of firstNode.listEventListeners(e.type)) {
499
643
  result = listener(e, data);
500
644
  }
501
645
  return result;
502
646
  }
503
647
 
504
648
  /**
649
+ * 插入文档
505
650
  * @param {'append'|'prepend'|'before'|'after'|'replaceChildren'|'replaceWith'} method
506
- * @param {string|Token|CollectionCallback<string|Token|(string|Token)[], string>} content
507
- * @param {...string|Token|(string|Token)[]} additional
651
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
652
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
508
653
  */
509
- _insert(method, content, ...additional) {
654
+ #insert(method, content, ...additional) {
510
655
  if (typeof content === 'function') {
511
656
  for (let i = 0; i < this.length; i++) {
512
- const token = this[i];
657
+ const token = this.array[i];
513
658
  if (token instanceof Token) {
514
659
  const result = content.call(token, i, token.toString());
515
660
  if (typeof result === 'string' || result instanceof Token) {
516
661
  token[method](result);
517
- } else if (Array.isArray(result)) {
662
+ } else if (result?.[Symbol.iterator]) {
518
663
  token[method](...result);
519
664
  } else {
520
665
  this.typeError(method, 'String', 'Token');
@@ -524,7 +669,10 @@ class TokenCollection extends Array {
524
669
  } else {
525
670
  for (const token of this) {
526
671
  if (token instanceof Token) {
527
- token[method](...content, ...additional.flat());
672
+ token[method](
673
+ ...Symbol.iterator in content ? content : [content],
674
+ ...additional.flatMap(ele => Symbol.iterator in ele ? [...ele] : ele),
675
+ );
528
676
  }
529
677
  }
530
678
  }
@@ -532,170 +680,238 @@ class TokenCollection extends Array {
532
680
  }
533
681
 
534
682
  /**
535
- * @param {string|Token|CollectionCallback<string|Token|(string|Token)[], string>} content
536
- * @param {...string|Token|(string|Token)[]} additional
683
+ * 在末尾插入
684
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
685
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
537
686
  */
538
687
  append(content, ...additional) {
539
- return this._insert('append', content, ...additional);
688
+ return this.#insert('append', content, ...additional);
540
689
  }
541
690
 
542
691
  /**
543
- * @param {string|Token|CollectionCallback<string|Token|(string|Token)[], string>} content
544
- * @param {...string|Token|(string|Token)[]} additional
692
+ * 在开头插入
693
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
694
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
545
695
  */
546
696
  prepend(content, ...additional) {
547
- return this._insert('prepend', content, ...additional);
697
+ return this.#insert('prepend', content, ...additional);
548
698
  }
549
699
 
550
700
  /**
551
- * @param {string|Token|CollectionCallback<string|Token|(string|Token)[], string>} content
552
- * @param {...string|Token|(string|Token)[]} additional
701
+ * 在后方插入
702
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
703
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
553
704
  */
554
705
  before(content, ...additional) {
555
- return this._insert('before', content, ...additional);
706
+ return this.#insert('before', content, ...additional);
556
707
  }
557
708
 
558
709
  /**
559
- * @param {string|Token|CollectionCallback<string|Token|(string|Token)[], string>} content
560
- * @param {...string|Token|(string|Token)[]} additional
710
+ * 在前方插入
711
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
712
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
561
713
  */
562
714
  after(content, ...additional) {
563
- return this._insert('after', content, ...additional);
715
+ return this.#insert('after', content, ...additional);
564
716
  }
565
717
 
566
718
  /**
567
- * @template {unknown} T
568
- * @param {T} content
569
- * @returns {T extends string|Token|CollectionCallback<string|Token|(string|Token)[], string> ? this : string}
719
+ * 替换所有子节点
720
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
721
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
570
722
  */
571
723
  html(content) {
572
724
  if (content === undefined) {
573
725
  return this.toString();
574
726
  }
575
- return this._insert('replaceChildren', content);
727
+ return this.#insert('replaceChildren', content);
576
728
  }
577
729
 
578
- /** @param {string|Token|CollectionCallback<string|Token|(string|Token)[], string>} content */
730
+ /**
731
+ * 替换自身
732
+ * @param {AstNode|Iterable<AstNode>|CollectionCallback<AstNode|Iterable<AstNode>, string>} content 插入内容
733
+ * @param {...AstNode|Iterable<AstNode>} additional 更多插入内容
734
+ */
579
735
  replaceWith(content) {
580
- return this._insert('replaceWith', content);
736
+ return this.#insert('replaceWith', content);
581
737
  }
582
738
 
583
- remove(selector = '') {
739
+ /**
740
+ * 移除自身
741
+ * @param {string} selector
742
+ */
743
+ remove(selector) {
584
744
  this.removeData();
585
- for (const token of this._filter(selector)) {
586
- token.remove();
587
- token.removeAllEventListeners();
745
+ for (const token of this) {
746
+ if (token instanceof Token && token.matches(selector)) {
747
+ token.remove();
748
+ token.removeAllEventListeners();
749
+ }
588
750
  }
589
751
  return this;
590
752
  }
591
753
 
592
- detach(selector = '') {
593
- for (const token of this._filter(selector)) {
594
- token.remove();
754
+ /**
755
+ * 移除自身
756
+ * @param {string} selector
757
+ */
758
+ detach(selector) {
759
+ for (const token of this) {
760
+ if (token instanceof Token && token.matches(selector)) {
761
+ token.remove();
762
+ }
595
763
  }
596
764
  return this;
597
765
  }
598
766
 
767
+ /** 清空子节点 */
599
768
  empty() {
600
- for (const token of this) {
601
- if (token instanceof Token) {
602
- token.replaceChildren();
603
- }
769
+ for (const token of this.#tokens) {
770
+ token.replaceChildren();
604
771
  }
605
772
  return this;
606
773
  }
607
774
 
608
775
  /**
609
- * @param {'append'|'prepend'|'before'|'after'|'replaceWith'} method
610
- * @param {Token|Token[]} target
776
+ * 深拷贝
777
+ * @param {boolean} withData 是否复制数据
778
+ */
779
+ clone(withData) {
780
+ return new TokenCollection(this.#tokens.map(ele => {
781
+ const cloned = ele.cloneNode();
782
+ if (withData && cache.has(ele)) {
783
+ cache.set(cloned, structuredClone(cache.get(ele)));
784
+ }
785
+ return cloned;
786
+ }), this);
787
+ }
788
+
789
+ /**
790
+ * 插入到
791
+ * @param {string} method
792
+ * @param {'append'|'prepend'|'before'|'after'|'replaceWith'} elementMethod 对应的AstElement方法
793
+ * @param {Token|Iterable<Token>} target 目标位置
611
794
  */
612
- _insertAdjacent(method, target) {
795
+ #insertAdjacent(method, elementMethod, target) {
613
796
  if (target instanceof Token) {
614
- target[method](...this);
615
- } else if (Array.isArray(target)) {
797
+ target[elementMethod](...this);
798
+ } else if (target?.[Symbol.iterator]) {
616
799
  for (const token of target) {
617
800
  if (token instanceof Token) {
618
- token[method](...this);
801
+ token[elementMethod](...this);
619
802
  }
620
803
  }
621
804
  } else {
622
- this.typeError(method, 'Token', 'Array');
805
+ this.typeError(method, 'Token', 'Iterable');
623
806
  }
624
807
  return this;
625
808
  }
626
809
 
627
- /** @param {Token|Token[]} target */
810
+ /**
811
+ * 插入到末尾
812
+ * @param {Token|Iterable<Token>} target 目标位置
813
+ */
628
814
  appendTo(target) {
629
- return this._insertAdjacent('append', target);
815
+ return this.#insertAdjacent('appendTo', 'append', target);
630
816
  }
631
817
 
632
- /** @param {Token|Token[]} target */
818
+ /**
819
+ * 插入到开头
820
+ * @param {Token|Iterable<Token>} target 目标位置
821
+ */
633
822
  prependTo(target) {
634
- return this._insertAdjacent('prepend', target);
823
+ return this.#insertAdjacent('prependTo', 'prepend', target);
635
824
  }
636
825
 
637
- /** @param {Token|Token[]} target */
826
+ /**
827
+ * 插入到前方
828
+ * @param {Token|Iterable<Token>} target 目标位置
829
+ */
638
830
  insertBefore(target) {
639
- return this._insertAdjacent('before', target);
831
+ return this.#insertAdjacent('insertBefore', 'before', target);
640
832
  }
641
833
 
642
- /** @param {Token|Token[]} target */
834
+ /**
835
+ * 插入到后方
836
+ * @param {Token|Iterable<Token>} target 目标位置
837
+ */
643
838
  insertAfter(target) {
644
- return this._insertAdjacent('after', target);
839
+ return this.#insertAdjacent('insertAfter', 'after', target);
645
840
  }
646
841
 
647
- /** @param {Token|Token[]} target */
842
+ /**
843
+ * 替换全部
844
+ * @param {Token|Iterable<Token>} target 目标位置
845
+ */
648
846
  replaceAll(target) {
649
- return this._insertAdjacent('replaceWith', target);
847
+ return this.#insertAdjacent('replaceAll', 'replaceWith', target);
650
848
  }
651
849
 
652
- /** @param {string|string[]|CollectionCallback<string, string>} value */
850
+ /**
851
+ * 获取几何属性
852
+ * @param {string|string[]} key 属性键
853
+ */
854
+ css(key) {
855
+ const /** @type {Record<string, number>} */ style = this.#firstToken?.style;
856
+ if (typeof key === 'string') {
857
+ return style?.[key];
858
+ }
859
+ return Array.isArray(key)
860
+ ? Object.fromEntries(key.map(k => [k, style?.[k]]))
861
+ : this.typeError('css', 'String', 'Array');
862
+ }
863
+
864
+ /**
865
+ * 查询或修改值
866
+ * @param {string|boolean|(string|boolean)[]|CollectionCallback<string, string|boolean>} value 值
867
+ */
653
868
  val(value) {
654
869
  if (value === undefined) {
655
- const firstToken = this._find();
870
+ const /** @type {{getValue: () => string|booleand}} */ firstToken = this.#firstToken;
656
871
  return firstToken?.getValue && firstToken.getValue();
657
872
  }
658
- let /** @type {(i: number, token: Token) => string} */ toValue;
659
- if (typeof value === 'string') {
660
- toValue = () => value;
873
+ let /** @type {(i: number, token: {getValue: () => string|boolean}) => string|boolean} */ getValue;
874
+ if (typeof value === 'string' || typeof value === 'boolean') {
875
+ getValue = /** @implements */ () => value;
661
876
  } else if (typeof value === 'function') {
662
- toValue = (i, token) => value.call(token, i, token.getValue && token.getValue());
877
+ getValue = /** @implements */ (i, token) => value.call(token, i, token.getValue && token.getValue());
663
878
  } else if (Array.isArray(value)) {
664
- toValue = i => value[i];
879
+ getValue = /** @implements */ i => value[i];
665
880
  } else {
666
881
  this.typeError('val', 'String', 'Array', 'Function');
667
882
  }
668
883
  for (let i = 0; i < this.length; i++) {
669
- const token = this[i];
884
+ const /** @type {{setValue: (value: string|boolean) => void}} */ token = this.array[i];
670
885
  if (token instanceof Token && typeof token.setValue === 'function' && token.setValue.length === 1) {
671
- token.setValue(toValue(i, token));
886
+ token.setValue(getValue(i, token));
672
887
  }
673
888
  }
674
889
  return this;
675
890
  }
676
891
 
677
892
  /**
678
- * @param {'getAttr'|'getAttribute'} getter
679
- * @param {'setAttr'|'setAttribute'} setter
680
- * @param {string|Record<string, string>} name
681
- * @param {any|CollectionCallback<string, any>} value
893
+ * 查询或修改属性
894
+ * @param {'getAttr'|'getAttribute'} getter 属性getter
895
+ * @param {'setAttr'|'setAttribute'} setter 属性setter
896
+ * @param {string|Record<string, string|boolean>} name 属性名
897
+ * @param {string|boolean|CollectionCallback<string|boolean, string|boolean>} value 属性值
682
898
  */
683
- _attr(getter, setter, name, value) {
899
+ #attr(getter, setter, name, value) {
684
900
  if (typeof name === 'string' && value === undefined) {
685
- const firstToken = this._find();
901
+ const firstToken = this.#firstToken;
686
902
  return firstToken?.[getter] && firstToken[getter](name);
687
903
  }
688
904
  for (let i = 0; i < this.length; i++) {
689
- const token = this[i];
690
- if (token instanceof Token && typeof token[setter] === 'function') {
691
- if (typeof value === 'string') {
692
- token[setter](name, value);
693
- } else if (typeof value === 'function') {
905
+ const token = this.array[i];
906
+ if (token instanceof Token && token[setter]) {
907
+ if (typeof value === 'function') {
694
908
  token[setter](name, value.call(token, i, token[getter] && token[getter](name)));
695
- } else if (typeof name === 'object') {
909
+ } else if (isPlainObject(name)) {
696
910
  for (const [k, v] of Object.entries(name)) {
697
911
  token[setter](k, v);
698
912
  }
913
+ } else {
914
+ token[setter](name, value);
699
915
  }
700
916
  }
701
917
  }
@@ -703,216 +919,281 @@ class TokenCollection extends Array {
703
919
  }
704
920
 
705
921
  /**
706
- * @param {string|Record<string, string>} name
707
- * @param {string|CollectionCallback<string, string>} value
922
+ * 标签属性
923
+ * @param {string|Record<string, string|boolean>} name 属性名
924
+ * @param {string|boolean|CollectionCallback<string|boolean, string|boolean>} value 属性值
708
925
  */
709
926
  attr(name, value) {
710
- return this._attr('getAttr', 'setAttr', name, value);
927
+ return this.#attr('getAttr', 'setAttr', name, value);
711
928
  }
712
929
 
713
930
  /**
714
- * @param {string|Record<string, string>} name
715
- * @param {any|CollectionCallback<string, any>} value
931
+ * 节点属性
932
+ * @param {string|Record<string, string|boolean>} name 属性名
933
+ * @param {string|boolean|CollectionCallback<string|boolean, string|boolean>} value 属性值
716
934
  */
717
935
  prop(name, value) {
718
- return this._attr('getAttribute', 'setAttribute', name, value);
936
+ return this.#attr('getAttribute', 'setAttribute', name, value);
719
937
  }
720
938
 
721
939
  /**
940
+ * 移除属性
722
941
  * @param {'removeAttr'|'removeAttribute'} method
723
- * @param {string} name
942
+ * @param {string} name 属性名
724
943
  */
725
- _removeAttr(method, name) {
944
+ #removeAttr(method, name) {
726
945
  for (const token of this) {
727
- if (token instanceof Token && typeof token[method] === 'function') {
946
+ if (token instanceof Token && token[method]) {
728
947
  token[method](name);
729
948
  }
730
949
  }
731
950
  return this;
732
951
  }
733
952
 
734
- /** @param {string} name */
953
+ /**
954
+ * 标签属性
955
+ * @param {string} name 属性名
956
+ */
735
957
  removeAttr(name) {
736
- return this._removeAttr('removeAttr', name);
958
+ return this.#removeAttr('removeAttr', name);
737
959
  }
738
960
 
739
- /** @param {string} name */
961
+ /**
962
+ * 节点属性
963
+ * @param {string} name 属性名
964
+ */
740
965
  removeProp(name) {
741
- return this._removeAttr('removeAttribute', name);
966
+ return this.#removeAttr('removeAttribute', name);
742
967
  }
743
968
 
744
969
  /**
745
- * 出于实用角度,与jQuery的实现方式不同
746
- * @param {string[]|CollectionCallback<string[], undefined>} wrapper
970
+ * 添加class
971
+ * @this {TokenCollection & {array: AttributeToken[]}}
972
+ * @param {string|string[]|CollectionCallback<string|string[], string>} className 类名
747
973
  */
748
- wrapAll(wrapper) {
749
- if (typeof wrapper !== 'function' && !Array.isArray(wrapper)) {
750
- this.typeError('wrapAll', 'Array', 'Function');
974
+ addClass(className) {
975
+ /** @type {CollectionCallback<string|string[], string>} */
976
+ const callback = typeof className === 'function' ? className : () => className;
977
+ for (let i = 0; i < this.length; i++) {
978
+ const token = this.array[i],
979
+ {classList} = token;
980
+ if (classList) {
981
+ const newClasses = callback.call(token, i, token.className);
982
+ for (const newClass of Array.isArray(newClasses) ? newClasses : [newClasses]) {
983
+ classList.add(newClass);
984
+ }
985
+ token.className = [...classList].join(' ');
986
+ }
751
987
  }
752
- const firstToken = this._find(),
753
- error = new Error('wrapAll 的主体应为同一个父节点下的连续子节点!');
754
- if (!firstToken || !firstToken.parentNode) {
755
- throw error;
988
+ return this;
989
+ }
990
+
991
+ /**
992
+ * 移除class
993
+ * @this {TokenCollection & {array: AttributeToken[]}}
994
+ * @param {undefined|string|string[]|CollectionCallback<undefined|string|string[], string>} className 类名
995
+ */
996
+ removeClass(className) {
997
+ /** @type {CollectionCallback<undefined|string|string[], string>} */
998
+ const callback = typeof className === 'function' ? className : () => className;
999
+ for (let i = 0; i < this.length; i++) {
1000
+ const token = this.array[i],
1001
+ {classList} = token;
1002
+ if (classList) {
1003
+ const newClasses = callback.call(token, i, token.className);
1004
+ if (newClasses === undefined) {
1005
+ classList.clear();
1006
+ } else {
1007
+ for (const newClass of Array.isArray(newClasses) ? newClasses : [newClasses]) {
1008
+ classList.delete(newClass);
1009
+ }
1010
+ }
1011
+ token.className = [...classList].join(' ');
1012
+ }
756
1013
  }
757
- const {parentNode} = firstToken,
758
- {childNodes} = parentNode,
759
- i = childNodes.indexOf(firstToken),
760
- consecutiveSiblings = childNodes.slice(i, i + this.length);
761
- try {
762
- assert.deepStrictEqual(this.toArray(), consecutiveSiblings);
763
- } catch (e) {
764
- if (e instanceof assert.AssertionError) {
765
- throw error;
1014
+ return this;
1015
+ }
1016
+
1017
+ /**
1018
+ * 增减class
1019
+ * @this {TokenCollection & {array: AttributeToken[]}}
1020
+ * @param {string|string[]|CollectionCallback<string|string[], string>} className 类名
1021
+ * @param {boolean} state 是否增删
1022
+ */
1023
+ toggleClass(className, state) {
1024
+ if (typeof state === 'boolean') {
1025
+ return this[state ? 'addClass' : 'removeClass'](className);
1026
+ }
1027
+ /** @type {CollectionCallback<string|string[], string>} */
1028
+ const callback = typeof className === 'function' ? className : () => className;
1029
+ for (let i = 0; i < this.length; i++) {
1030
+ const token = this.array[i],
1031
+ {classList} = token;
1032
+ if (classList) {
1033
+ const newClasses = callback.call(token, i, token.className);
1034
+ for (const newClass of Array.isArray(newClasses) ? new Set(newClasses) : [newClasses]) {
1035
+ classList[classList.has(newClass) ? 'delete' : 'add'](newClass);
1036
+ }
1037
+ token.className = [...classList].join(' ');
766
1038
  }
767
1039
  }
768
- const ranges = parentNode.getAttribute('protectedChildren'),
769
- index = ranges.applyTo(childNodes).find(n => n >= i && n < i + this.length);
770
- if (index !== undefined) {
771
- throw new Error(`第 ${index} 个子节点受到保护!`);
1040
+ return this;
1041
+ }
1042
+
1043
+ /**
1044
+ * 是否带有某class
1045
+ * @this {{array: AttributeToken[]}}
1046
+ * @param {string} className 类名
1047
+ */
1048
+ hasClass(className) {
1049
+ return this.array.some(token => token.classList?.has(className));
1050
+ }
1051
+
1052
+ /**
1053
+ * 全包围
1054
+ * @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
1055
+ * @throws `Error` 不是连续的兄弟节点
1056
+ */
1057
+ wrapAll(wrapper) {
1058
+ if (typeof wrapper !== 'function' && !Array.isArray(wrapper)) {
1059
+ this.typeError('wrapAll', 'Array', 'Function');
772
1060
  }
773
- const [pre, post] = typeof wrapper === 'function' ? wrapper.call(firstToken) : wrapper,
774
- config = firstToken.getRootNode().getAttribute('config'),
775
- token = new Token(`${pre}${this.toString()}${post}`, config).parse();
776
- if (token.childNodes.length !== 1) {
777
- throw new RangeError(`非法的 wrapper:\n${noWrap(pre)}\n${noWrap(post)}`);
1061
+ const {array} = this,
1062
+ [firstNode] = array,
1063
+ /** @type {Token} */ ancestor = firstNode?.parentNode,
1064
+ error = new Error('wrapAll 的主体应为普通Token的连续子节点!');
1065
+ if (ancestor?.constructor !== Token) {
1066
+ throw error;
778
1067
  }
779
- for (let j = i + this.length - 1; j >= i; j--) {
780
- parentNode.removeAt(j);
1068
+ const {childNodes} = ancestor,
1069
+ i = childNodes.indexOf(firstNode),
1070
+ j = childNodes.findIndex(node => node.contains(array.at(-1)));
1071
+ if (j === -1 || childNodes.slice(i, j + 1).some(node => !array.includes(node))) {
1072
+ throw error;
781
1073
  }
782
- parentNode.insertAt(token.firstChild, i);
1074
+ const [pre, post] = typeof wrapper === 'function' ? wrapper.call(firstNode) : wrapper,
1075
+ config = ancestor.getAttribute('config'),
1076
+ include = ancestor.getAttribute('include'),
1077
+ token = new Token(`${pre}${String(childNodes.slice(i, j + 1))}${post}`, config).parse(undefined, include);
1078
+ this.detach();
1079
+ (childNodes[i - 1]?.after ?? ancestor.prepend)(...token.childNodes);
783
1080
  return this;
784
1081
  }
785
1082
 
786
1083
  /**
1084
+ * 局部包裹
787
1085
  * @param {'html'|'replaceWith'} method
788
- * @param {string[]|CollectionCallback<string[], undefined>} wrapper
1086
+ * @param {string} originalMethod 原方法
1087
+ * @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
1088
+ * @returns {this}
789
1089
  */
790
- _wrap(method, wrapper) {
1090
+ #wrap(method, originalMethod, wrapper) {
791
1091
  if (typeof wrapper !== 'function' && !Array.isArray(wrapper)) {
792
- this.typeError(method, 'Array', 'Function');
793
- }
794
- return this[method](
795
-
796
- /**
797
- * @this {string|Token}
798
- * @param {number} i
799
- * @param {string} string
800
- */
801
- function(i, string) {
802
- if (!(this instanceof Token)) {
803
- return string;
804
- }
805
- const [pre, post] = typeof wrapper === 'function' ? wrapper.call(this, i) : wrapper,
806
- config = this.getRootNode().getAttribute('config'),
807
- token = new Token(`${pre}${string}${post}`, config).parse();
808
- if (token.childNodes.length !== 1) {
809
- throw new RangeError(`非法的 wrapper:\n${noWrap(pre)}\n${noWrap(post)}`);
810
- }
811
- return token.firstChild;
812
- },
813
- );
1092
+ this.typeError(originalMethod, 'Array', 'Function');
1093
+ }
1094
+
1095
+ /**
1096
+ * @implements {CollectionCallback<AstNode|Iterable<AstNode>, string>}
1097
+ * @this {Token}
1098
+ * @param {number} i 序号
1099
+ * @param {string} string 原文本
1100
+ */
1101
+ const callback = function(i, string) {
1102
+ const [pre, post] = typeof wrapper === 'function' ? wrapper.call(this, i) : wrapper,
1103
+ config = this.getAttribute('config'),
1104
+ include = this.getAttribute('include');
1105
+ return new Token(`${pre}${string}${post}`, config).parse(undefined, include).childNodes;
1106
+ };
1107
+ return this[method](callback);
814
1108
  }
815
1109
 
816
- /** @param {string[]|CollectionCallback<string[], undefined>} wrapper */
1110
+ /**
1111
+ * 包裹内部
1112
+ * @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
1113
+ */
817
1114
  wrapInner(wrapper) {
818
- return this._wrap('html', wrapper);
1115
+ return this.#wrap('html', 'wrapInner', wrapper);
819
1116
  }
820
1117
 
821
- /** @param {string[]|CollectionCallback<string[], undefined>} wrapper */
1118
+ /**
1119
+ * 包裹自身
1120
+ * @param {string[]|CollectionCallback<string[], undefined>} wrapper 包裹
1121
+ */
822
1122
  wrap(wrapper) {
823
- return this._wrap('replaceWith', wrapper);
1123
+ return this.#wrap('replaceWith', 'wrap', wrapper);
824
1124
  }
825
1125
 
1126
+ /** 相对于文档的位置 */
826
1127
  offset() {
827
- const firstToken = this._find();
828
- if (!firstToken) {
829
- return undefined;
830
- }
831
- const {top, left} = firstToken.getBoundingClientRect();
832
- return {top, left};
1128
+ const offset = this.#firstToken?.getBoundingClientRect();
1129
+ return offset && {top: offset.top, left: offset.left};
833
1130
  }
834
1131
 
1132
+ /** 相对于父容器的位置 */
835
1133
  position() {
836
- const style = this._find()?.style;
1134
+ const style = this.#firstToken?.style;
837
1135
  return style && {top: style.top, left: style.left};
838
1136
  }
839
1137
 
1138
+ /** 高度 */
840
1139
  height() {
841
- return this._find()?.offsetHeight;
1140
+ return this.#firstToken?.offsetHeight;
842
1141
  }
843
1142
 
1143
+ /** 宽度 */
844
1144
  width() {
845
- return this._find()?.offsetWidth;
1145
+ return this.#firstToken?.offsetWidth;
1146
+ }
1147
+
1148
+ /** 内部高度 */
1149
+ innerHeight() {
1150
+ return this.#firstToken?.clientHeight;
1151
+ }
1152
+
1153
+ /** 内部宽度 */
1154
+ innerWidth() {
1155
+ return this.#firstToken?.clientWidth;
846
1156
  }
847
1157
  }
848
1158
 
849
- /** @param {AstText|Token|Iterable<string|Token>} tokens */
850
- const $ = tokens => {
851
- if (tokens instanceof AstText || tokens instanceof Token) {
852
- tokens = [tokens];
853
- }
854
- return new Proxy(new TokenCollection(...tokens), {
855
- /** @param {PropertyKey} prop */
856
- get(obj, prop) {
857
- if (prop === Symbol.iterator || typeof obj[prop] !== 'function'
858
- || !prop.startsWith('_') && Object.getOwnPropertyDescriptor(obj.constructor.prototype, prop)
859
- || !externalUse(prop, true)
860
- ) {
861
- return obj[prop];
862
- }
863
- return undefined;
864
- },
865
- set(obj, prop, val) {
866
- if (prop === 'prevObject' && (val === undefined || val instanceof TokenCollection)) {
867
- obj[prop] = val;
868
- return true;
869
- }
870
- return false;
871
- },
872
- });
873
- };
1159
+ /**
1160
+ * 代替TokenCollection构造器
1161
+ * @param {AstNode|Iterable<AstNode>} arr 节点数组
1162
+ */
1163
+ const $ = arr => new TokenCollection(arr instanceof AstNode ? [arr] : arr);
874
1164
  /* eslint-disable func-names */
875
1165
  $.hasData = /** @param {Token} element */ function hasData(element) {
876
- if (!(element instanceof Token)) {
877
- typeError(this, 'hasData', 'Token');
878
- }
879
- return this.dataStore.has(element);
1166
+ return element instanceof Token ? cache.has(element) : typeError(this, 'hasData', 'Token');
880
1167
  };
881
- $.data = /** @type {function(Token, string, any): any} */ function data(element, key, value) {
1168
+ $.data = /** @type {function(Token, string, *): *} */ function data(element, key, value) {
882
1169
  if (!(element instanceof Token)) {
883
1170
  typeError(this, 'data', 'Token');
884
1171
  } else if (key === undefined) {
885
- return this.dataStore.get(element);
1172
+ return cache.get(element);
886
1173
  } else if (typeof key !== 'string') {
887
1174
  typeError(this, 'data', 'String');
888
1175
  } else if (value === undefined) {
889
- return this.dataStore.get(element)?.[key];
890
- } else if (!this.dataStore.has(element)) {
891
- this.dataStore.set(element, {});
1176
+ return cache.get(element)?.[key];
1177
+ } else if (!cache.has(element)) {
1178
+ cache.set(element, {});
892
1179
  }
893
- this.dataStore.get(element)[key] = value;
1180
+ cache.get(element)[key] = value;
894
1181
  return value;
895
1182
  };
896
1183
  $.removeData = /** @type {function(Token, string): void} */ function removeData(element, name) {
897
1184
  if (!(element instanceof Token)) {
898
1185
  typeError(this, 'removeData', 'Token');
899
1186
  } else if (name === undefined) {
900
- this.dataStore.delete(element);
1187
+ cache.delete(element);
901
1188
  } else if (typeof name !== 'string') {
902
1189
  typeError(this, 'removeData', 'String');
903
- } else if (this.dataStore.has(element)) {
904
- const data = this.dataStore.get(element);
1190
+ } else if (cache.has(element)) {
1191
+ const data = cache.get(element);
905
1192
  delete data[name];
906
1193
  }
907
1194
  };
908
- Object.defineProperty($, 'dataStore', {value: dataStore});
909
- Object.defineProperty($, 'TokenCollection', {value: TokenCollection});
910
- Object.defineProperty($, 'reload', {
911
- value() {
912
- delete require.cache[__filename];
913
- const $$ = require('.');
914
- return $$;
915
- },
916
- });
1195
+ /* eslint-enable func-names */
1196
+ Object.defineProperty($, 'cache', {value: cache, enumerable: false, writable: false, configurable: false});
917
1197
 
1198
+ Parser.tool.$ = __filename;
918
1199
  module.exports = $;