wikiparser-node 1.9.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/config/.schema.json +5 -0
  2. package/config/enwiki.json +2 -1
  3. package/config/llwiki.json +2 -1
  4. package/config/moegirl.json +2 -1
  5. package/config/zhwiki.json +2 -1
  6. package/dist/addon/token.js +282 -198
  7. package/dist/addon/transclude.js +7 -7
  8. package/dist/base.d.ts +3 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +4 -4
  11. package/dist/lib/element.js +2 -2
  12. package/dist/lib/node.d.ts +6 -1
  13. package/dist/lib/node.js +10 -4
  14. package/dist/lib/text.d.ts +5 -2
  15. package/dist/lib/text.js +15 -5
  16. package/dist/lib/title.d.ts +2 -0
  17. package/dist/lib/title.js +8 -0
  18. package/dist/mixin/attributesParent.js +0 -3
  19. package/dist/mixin/hidden.d.ts +1 -0
  20. package/dist/mixin/hidden.js +5 -2
  21. package/dist/mixin/singleLine.js +2 -4
  22. package/dist/mixin/sol.d.ts +1 -0
  23. package/dist/mixin/sol.js +7 -7
  24. package/dist/mixin/syntax.js +0 -2
  25. package/dist/parser/list.js +8 -6
  26. package/dist/parser/selector.js +2 -1
  27. package/dist/parser/table.js +2 -2
  28. package/dist/src/arg.js +3 -3
  29. package/dist/src/attribute.js +25 -28
  30. package/dist/src/attributes.d.ts +0 -1
  31. package/dist/src/attributes.js +34 -7
  32. package/dist/src/converter.d.ts +2 -2
  33. package/dist/src/converter.js +13 -3
  34. package/dist/src/converterFlags.js +2 -2
  35. package/dist/src/converterRule.js +17 -13
  36. package/dist/src/extLink.js +7 -3
  37. package/dist/src/gallery.js +2 -2
  38. package/dist/src/heading.js +9 -4
  39. package/dist/src/hidden.js +1 -1
  40. package/dist/src/html.js +10 -2
  41. package/dist/src/imageParameter.js +2 -2
  42. package/dist/src/imagemap.js +2 -2
  43. package/dist/src/index.d.ts +11 -1
  44. package/dist/src/index.js +61 -12
  45. package/dist/src/link/base.d.ts +9 -0
  46. package/dist/src/link/base.js +48 -11
  47. package/dist/src/link/file.js +1 -1
  48. package/dist/src/link/index.d.ts +0 -12
  49. package/dist/src/link/index.js +8 -45
  50. package/dist/src/link/redirectTarget.d.ts +3 -12
  51. package/dist/src/link/redirectTarget.js +5 -31
  52. package/dist/src/magicLink.d.ts +3 -2
  53. package/dist/src/magicLink.js +45 -57
  54. package/dist/src/nowiki/base.js +1 -1
  55. package/dist/src/nowiki/comment.js +3 -3
  56. package/dist/src/nowiki/dd.js +64 -18
  57. package/dist/src/nowiki/doubleUnderscore.js +1 -1
  58. package/dist/src/nowiki/hr.js +6 -1
  59. package/dist/src/nowiki/list.d.ts +0 -11
  60. package/dist/src/nowiki/list.js +2 -48
  61. package/dist/src/nowiki/listBase.d.ts +12 -0
  62. package/dist/src/nowiki/listBase.js +67 -53
  63. package/dist/src/nowiki/noinclude.js +5 -1
  64. package/dist/src/nowiki/quote.js +5 -0
  65. package/dist/src/onlyinclude.js +2 -2
  66. package/dist/src/paramTag/index.js +2 -2
  67. package/dist/src/parameter.js +20 -13
  68. package/dist/src/redirect.js +7 -3
  69. package/dist/src/syntax.d.ts +1 -1
  70. package/dist/src/syntax.js +2 -2
  71. package/dist/src/table/index.js +11 -1
  72. package/dist/src/table/td.js +8 -2
  73. package/dist/src/table/trBase.js +15 -7
  74. package/dist/src/tagPair/ext.js +16 -0
  75. package/dist/src/tagPair/include.js +5 -1
  76. package/dist/src/tagPair/index.js +3 -3
  77. package/dist/src/transclude.js +20 -15
  78. package/dist/util/lint.js +1 -1
  79. package/dist/util/string.js +25 -1
  80. package/errors/README +2 -0
  81. package/package.json +1 -1
  82. package/printed/README +2 -0
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ declare interface Parser extends ParserBase {
6
6
  viewOnly: boolean;
7
7
  conversionTable: Map<string, string>;
8
8
  redirects: Map<string, string>;
9
+ templateDir?: string;
10
+ templates: Map<string, string>;
9
11
  warning: boolean;
10
12
  debugging: boolean;
11
13
  /**
package/dist/index.js CHANGED
@@ -13,12 +13,11 @@ const diff_1 = require("./util/diff");
13
13
  * @param file 文件名
14
14
  * @param dir 子路径
15
15
  */
16
- const rootRequire = (file, dir) => require(file.startsWith('/') ? file : `../${file.includes('/') ? '' : dir}${file}`);
16
+ const rootRequire = (file, dir) => require(path.isAbsolute(file) ? file : path.join('..', file.includes('/') ? '' : dir, file));
17
17
  /* NOT FOR BROWSER */
18
18
  const promises = [Promise.resolve()];
19
19
  let viewOnly = false;
20
20
  /* NOT FOR BROWSER END */
21
- // eslint-disable-next-line @typescript-eslint/no-redeclare
22
21
  const Parser = {
23
22
  config: 'default',
24
23
  i18n: undefined,
@@ -36,13 +35,14 @@ const Parser = {
36
35
  },
37
36
  conversionTable: new Map(),
38
37
  redirects: new Map(),
38
+ templates: new Map(),
39
39
  warning: true,
40
40
  debugging: false,
41
41
  /* NOT FOR BROWSER END */
42
42
  /** @implements */
43
43
  getConfig() {
44
44
  if (typeof this.config === 'string') {
45
- this.config = rootRequire(this.config, 'config/');
45
+ this.config = rootRequire(this.config, 'config');
46
46
  /* NOT FOR BROWSER */
47
47
  const { config: { conversionTable, redirects } } = this;
48
48
  if (conversionTable) {
@@ -62,7 +62,7 @@ const Parser = {
62
62
  /** @implements */
63
63
  msg(msg, arg = '') {
64
64
  if (typeof this.i18n === 'string') {
65
- this.i18n = rootRequire(this.i18n, 'i18n/');
65
+ this.i18n = rootRequire(this.i18n, 'i18n');
66
66
  return this.msg(msg, arg);
67
67
  }
68
68
  return msg && (this.i18n?.[msg] ?? msg).replace('$1', this.msg(arg));
@@ -254,8 +254,8 @@ class AstElement extends node_1.AstNode {
254
254
  throw new RangeError(`The child node at position ${i} is ${oldText.constructor.name}!`);
255
255
  }
256
256
  /** @private */
257
- toString(separator = '') {
258
- return this.childNodes.map(String).join(separator);
257
+ toString(skip, separator = '') {
258
+ return this.childNodes.map(child => child.toString(skip)).join(separator);
259
259
  }
260
260
  /** @private */
261
261
  lint(start = this.getAbsoluteIndex(), re) {
@@ -18,9 +18,9 @@ export declare abstract class AstNode implements AstNodeBase {
18
18
  #private;
19
19
  data?: string | undefined;
20
20
  readonly childNodes: readonly AstNodes[];
21
- /** 节点类型 */
22
21
  abstract get type(): TokenTypes | 'text';
23
22
  abstract set type(value: TokenTypes | 'text');
23
+ /** 可见部分 */
24
24
  text(): string;
25
25
  lint(): LintError[];
26
26
  print(): string;
@@ -82,6 +82,11 @@ export declare abstract class AstNode implements AstNodeBase {
82
82
  getRelativeIndex(j?: number): number;
83
83
  /** 获取当前节点的绝对位置 */
84
84
  getAbsoluteIndex(): number;
85
+ /**
86
+ * 是否是某种类型的节点
87
+ * @param type 节点类型
88
+ */
89
+ is<T extends Token>(type: string): this is T;
85
90
  /**
86
91
  * 是否是全同节点
87
92
  * @param node 待比较的节点
package/dist/lib/node.js CHANGED
@@ -4,7 +4,6 @@ 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");
8
7
  /**
9
8
  * 计算字符串的行列数
10
9
  * @param str 字符串
@@ -134,11 +133,11 @@ class AstNode {
134
133
  if (!parentNode || acceptable && !('QuoteToken' in acceptable)) {
135
134
  return { bold: false, italic: false };
136
135
  }
137
- const { childNodes, type } = parentNode, isQuote = (0, debug_1.isToken)('quote');
136
+ const { childNodes, type } = parentNode;
138
137
  let { bold = false, italic = false } = type === 'ext-link-text' && parentNode.parentNode || {};
139
138
  for (let i = childNodes.indexOf(this) - 1; i >= 0; i--) {
140
139
  const child = childNodes[i];
141
- if (isQuote(child)) {
140
+ if (child.is('quote')) {
142
141
  bold = child.bold !== bold;
143
142
  italic = child.italic !== italic;
144
143
  }
@@ -171,7 +170,7 @@ class AstNode {
171
170
  this.#parentNode = value;
172
171
  /* NOT FOR BROWSER */
173
172
  }
174
- else if (Object.prototype.hasOwnProperty.call(this, key)) {
173
+ else if (Object.hasOwn(this, key)) {
175
174
  const descriptor = Object.getOwnPropertyDescriptor(this, key);
176
175
  if (this.#optional.has(key)) {
177
176
  descriptor.enumerable = Boolean(value);
@@ -249,6 +248,13 @@ class AstNode {
249
248
  writable: false,
250
249
  });
251
250
  }
251
+ /**
252
+ * 是否是某种类型的节点
253
+ * @param type 节点类型
254
+ */
255
+ is(type) {
256
+ return this.type === type;
257
+ }
252
258
  /* NOT FOR BROWSER */
253
259
  /** @private */
254
260
  typeError(method, ...types) {
@@ -11,8 +11,6 @@ export declare class AstText extends AstNode {
11
11
  set length(n: number);
12
12
  /** @param text 包含文本 */
13
13
  constructor(text: string);
14
- /** 可见部分 */
15
- text(): string;
16
14
  /**
17
15
  * 替换字符串
18
16
  * @param text 替换的字符串
@@ -52,4 +50,9 @@ export declare class AstText extends AstNode {
52
50
  splitText(offset: number): AstText;
53
51
  /** 转义 `=` */
54
52
  escape(): void;
53
+ /**
54
+ * 生成HTML
55
+ * @param nowrap 是否不换行
56
+ */
57
+ toHtml(nowrap?: boolean): string;
55
58
  }
package/dist/lib/text.js CHANGED
@@ -107,12 +107,15 @@ class AstText extends node_1.AstNode {
107
107
  toString() {
108
108
  return this.data;
109
109
  }
110
- /** 可见部分 */
110
+ /** @private */
111
111
  text() {
112
112
  return this.data;
113
113
  }
114
114
  /** @private */
115
115
  lint(start = this.getAbsoluteIndex(), errorRegex) {
116
+ if (errorRegex === false) {
117
+ return [];
118
+ }
116
119
  const { data, parentNode, nextSibling, previousSibling } = this;
117
120
  if (!parentNode) {
118
121
  throw new Error('An isolated text node cannot be linted!');
@@ -152,9 +155,7 @@ class AstText extends node_1.AstNode {
152
155
  || char === '['
153
156
  && type === 'ext-link-text'
154
157
  && (/&(?:rbrack|#93|#x5[Dd];);/u.test(data.slice(index + 1))
155
- || nextType === 'ext'
156
- && nextName === 'nowiki'
157
- && nextSibling.innerText?.includes(']'))) {
158
+ || nextSibling?.is('ext') && nextName === 'nowiki' && nextSibling.innerText?.includes(']'))) {
158
159
  continue;
159
160
  }
160
161
  else if (char === ']' && (index || length > 1)) {
@@ -341,11 +342,20 @@ class AstText extends node_1.AstNode {
341
342
  if (i < this.length - 1) {
342
343
  this.splitText(i + 1);
343
344
  }
345
+ this.after(debug_1.Shadow.run(
344
346
  // @ts-expect-error abstract class
345
- this.after(new TranscludeToken('=', [], this.parentNode.getAttribute('config')));
347
+ () => new TranscludeToken('=', [], this.parentNode.getAttribute('config'))));
346
348
  this.#setData(this.data.slice(0, i));
347
349
  }
348
350
  }
351
+ /**
352
+ * 生成HTML
353
+ * @param nowrap 是否不换行
354
+ */
355
+ toHtml(nowrap) {
356
+ const { data } = this;
357
+ return (0, string_1.font)(this, (0, string_1.sanitize)(nowrap ? data.replace(/\n/gu, ' ') : data));
358
+ }
349
359
  }
350
360
  exports.AstText = AstText;
351
361
  constants_1.classes['AstText'] = __filename;
@@ -37,4 +37,6 @@ export declare class Title {
37
37
  toBasePage(): void;
38
38
  /** 转换为根页面 */
39
39
  toRootPage(): void;
40
+ /** 生成URL */
41
+ getUrl(): string;
40
42
  }
package/dist/lib/title.js CHANGED
@@ -21,6 +21,7 @@ class Title {
21
21
  encoded = false;
22
22
  /* NOT FOR BROWSER */
23
23
  #fragment;
24
+ #path;
24
25
  /** @private */
25
26
  conversionTable = new Map();
26
27
  /** @private */
@@ -138,6 +139,7 @@ class Title {
138
139
  redirects: { enumerable: false },
139
140
  });
140
141
  this.#namespaces = config.namespaces;
142
+ this.#path = config.articlePath || '/wiki/$1';
141
143
  }
142
144
  /** @private */
143
145
  toString() {
@@ -192,6 +194,12 @@ class Title {
192
194
  toRootPage() {
193
195
  this.main = this.main.replace(/\/.*/u, '');
194
196
  }
197
+ /** 生成URL */
198
+ getUrl() {
199
+ return this.#path.replace('$1', `${encodeURIComponent(this.title)}${this.fragment === undefined && this.#fragment === undefined
200
+ ? ''
201
+ : `#${encodeURIComponent(this.fragment ?? this.#fragment)}`}`);
202
+ }
195
203
  }
196
204
  exports.Title = Title;
197
205
  constants_1.classes['Title'] = __filename;
@@ -35,9 +35,6 @@ const attributesParent = (i = 0) => (constructor, _) => {
35
35
  get classList() {
36
36
  return this.#attributesChild.classList;
37
37
  }
38
- set classList(classList) {
39
- this.#attributesChild.classList = classList;
40
- }
41
38
  /** @implements */
42
39
  get id() {
43
40
  return this.#attributesChild.id;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * 解析后不可见的类
3
3
  * @param linter 是否覆写 lint 方法
4
+ * @param html 是否覆写 toHtml 方法
4
5
  * @param constructor 基类
5
6
  * @param _ context
6
7
  */
@@ -6,17 +6,17 @@ const constants_1 = require("../util/constants");
6
6
  /**
7
7
  * 解析后不可见的类
8
8
  * @param linter 是否覆写 lint 方法
9
+ * @param html 是否覆写 toHtml 方法
9
10
  * @param constructor 基类
10
11
  * @param _ context
11
12
  */
12
- const hiddenToken = (linter) => (constructor, _) => {
13
+ const hiddenToken = (linter = true, html = true) => (constructor, _) => {
13
14
  /** 解析后不可见的类 */
14
15
  class AnyHiddenToken extends constructor {
15
16
  /** 没有可见部分 */
16
17
  text() {
17
18
  return '';
18
19
  }
19
- /** @private */
20
20
  lint(start) {
21
21
  // @ts-expect-error private argument
22
22
  return linter ? [] : super.lint(start);
@@ -26,6 +26,9 @@ const hiddenToken = (linter) => (constructor, _) => {
26
26
  dispatchEvent() {
27
27
  //
28
28
  }
29
+ toHtml(nowrap) {
30
+ return html ? '' : super.toHtml(nowrap);
31
+ }
29
32
  }
30
33
  (0, debug_1.mixin)(AnyHiddenToken, constructor);
31
34
  return AnyHiddenToken;
@@ -11,11 +11,9 @@ const constants_1 = require("../util/constants");
11
11
  const singleLine = (constructor, _) => {
12
12
  /** 不可包含换行符的类 */
13
13
  class SingleLineToken extends constructor {
14
- /** @private */
15
- toString() {
16
- return super.toString().replace(/\n/gu, ' ');
14
+ toString(skip) {
15
+ return super.toString(skip).replace(/\n/gu, ' ');
17
16
  }
18
- /** @private */
19
17
  text() {
20
18
  return super.text().replace(/\n/gu, ' ');
21
19
  }
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * 只能位于行首的类
3
+ * @param self 是否允许同类节点相邻
3
4
  * @param constructor 基类
4
5
  * @param _ context
5
6
  */
package/dist/mixin/sol.js CHANGED
@@ -5,34 +5,34 @@ const debug_1 = require("../util/debug");
5
5
  const constants_1 = require("../util/constants");
6
6
  /**
7
7
  * 只能位于行首的类
8
+ * @param self 是否允许同类节点相邻
8
9
  * @param constructor 基类
9
10
  * @param _ context
10
11
  */
11
- const sol = (constructor, _) => {
12
+ const sol = (self) => (constructor, _) => {
12
13
  /** 只能位于行首的类 */
13
14
  class SolToken extends constructor {
14
15
  /** @implements */
15
16
  #prependNewLine() {
16
17
  const { previousVisibleSibling, parentNode, type } = this;
17
18
  if (previousVisibleSibling) {
18
- return previousVisibleSibling.toString().endsWith('\n') ? '' : '\n';
19
+ return self && previousVisibleSibling.type === type || previousVisibleSibling.toString().endsWith('\n')
20
+ ? ''
21
+ : '\n';
19
22
  }
20
23
  return parentNode?.type === 'root'
21
24
  || type !== 'heading' && parentNode?.type === 'ext-inner' && parentNode.name === 'poem'
22
25
  ? ''
23
26
  : '\n';
24
27
  }
25
- /** @private */
26
- toString() {
27
- return this.#prependNewLine() + super.toString();
28
+ toString(skip) {
29
+ return this.#prependNewLine() + super.toString(skip);
28
30
  }
29
- /** @private */
30
31
  getAttribute(key) {
31
32
  return key === 'padding'
32
33
  ? this.#prependNewLine().length + super.getAttribute('padding')
33
34
  : super.getAttribute(key);
34
35
  }
35
- /** @private */
36
36
  text() {
37
37
  return this.#prependNewLine() + super.text();
38
38
  }
@@ -21,7 +21,6 @@ const syntax = (pattern) => (constructor, _) => {
21
21
  }
22
22
  this.seal('pattern', true);
23
23
  }
24
- /** @private */
25
24
  afterBuild() {
26
25
  super.afterBuild();
27
26
  const /** @implements */ syntaxListener = (e, data) => {
@@ -32,7 +31,6 @@ const syntax = (pattern) => (constructor, _) => {
32
31
  };
33
32
  this.addEventListener(['remove', 'insert', 'replace', 'text'], syntaxListener);
34
33
  }
35
- /** @private */
36
34
  replaceChildren(...elements) {
37
35
  if (debug_1.Shadow.running || this.pattern.test((0, string_1.text)(elements))) {
38
36
  debug_1.Shadow.run(() => {
@@ -11,18 +11,20 @@ const dd_1 = require("../src/nowiki/dd");
11
11
  * @param accum
12
12
  */
13
13
  const parseList = (wikitext, config, accum) => {
14
- const mt = /^((?:\0\d+c\x7F)*)([;:*#]+)/u.exec(wikitext);
14
+ const mt = /^((?:\0\d+c\x7F)*)([;:*#]+\s*)/u.exec(wikitext);
15
15
  if (!mt) {
16
16
  return wikitext;
17
17
  }
18
- const [total, comment, prefix] = mt;
19
- let text = `${comment}\0${accum.length}d\x7F${wikitext.slice(total.length)}`, dt = prefix.split(';').length - 1;
20
- // @ts-expect-error abstract class
21
- new list_1.ListToken(prefix, config, accum);
18
+ const [total, comment, prefix] = mt, parts = prefix.split(/(?=;)/u);
19
+ let text = comment + parts.map((_, i) => `\0${accum.length + i}d\x7F`).join('') + wikitext.slice(total.length), dt = parts.length - (parts[0].startsWith(';') ? 0 : 1);
20
+ for (const part of parts) {
21
+ // @ts-expect-error abstract class
22
+ new list_1.ListToken(part, config, accum);
23
+ }
22
24
  if (!dt) {
23
25
  return text;
24
26
  }
25
- const { html: [normalTags] } = config, fullRegex = /:+|-\{|\0\d+[xq]\x7F/gu;
27
+ const { html: [normalTags] } = config, fullRegex = /:+\s*|-\{|\0\d+[xq]\x7F/gu;
26
28
  let regex = fullRegex, ex = regex.exec(text), lt = 0, lb = false, li = false, lc = 0;
27
29
  /**
28
30
  * 创建`DdToken`
@@ -128,6 +128,7 @@ const matches = (token, step) => {
128
128
  return token.text().trim() === '';
129
129
  case ':any-link':
130
130
  return type === 'link'
131
+ || type === 'redirect-target'
131
132
  || type === 'free-ext-link'
132
133
  || type === 'magic-link'
133
134
  || type === 'ext-link'
@@ -320,7 +321,7 @@ const checkToken = (selector) => (token) => {
320
321
  }
321
322
  }
322
323
  }
323
- step.push(...pieces.filter(piece => piece.startsWith('#')).map(piece => desanitize(piece)));
324
+ step.push(...pieces.filter(piece => piece.startsWith('#')).map(desanitize));
324
325
  };
325
326
  /**
326
327
  * 检查是否需要通用选择器
@@ -68,8 +68,8 @@ const parseTable = ({ firstChild: { data }, type, name }, config, accum) => {
68
68
  out += `\n${outLine}`;
69
69
  continue;
70
70
  }
71
- // eslint-disable-next-line @stylistic/operator-linebreak
72
- const matches = /^(?:(\|\}|\0\d+!\x7F\}|\0\d+\}\x7F)|(\|-+|\0\d+!\x7F-+|\0\d+-\x7F-*)(?!-)|(!|(?:\||\0\d+!\x7F)\+?))(.*)$/u
71
+ const matches = // eslint-disable-line @stylistic/operator-linebreak
72
+ /^(?:(\|\}|\0\d+!\x7F\}|\0\d+\}\x7F)|(\|-+|\0\d+!\x7F-+|\0\d+-\x7F-*)(?!-)|(!|(?:\||\0\d+!\x7F)\+?))(.*)$/u
73
73
  .exec(line);
74
74
  if (!matches) {
75
75
  push(`\n${outLine}`, top);
package/dist/src/arg.js CHANGED
@@ -55,8 +55,8 @@ class ArgToken extends index_2.Token {
55
55
  this.protectChildren(0);
56
56
  }
57
57
  /** @private */
58
- toString() {
59
- return `{{{${super.toString('|')}}}}`;
58
+ toString(skip) {
59
+ return `{{{${super.toString(skip, '|')}}}}`;
60
60
  }
61
61
  /** @private */
62
62
  text() {
@@ -133,7 +133,7 @@ class ArgToken extends index_2.Token {
133
133
  }
134
134
  /** 设置name */
135
135
  #setName() {
136
- this.setAttribute('name', this.firstChild.text().trim());
136
+ this.setAttribute('name', this.firstChild.toString(true).trim());
137
137
  }
138
138
  /** @private */
139
139
  afterBuild() {
@@ -99,7 +99,6 @@ const commonHtmlAttrs = new Set([
99
99
  img: new Set(['alt', 'src', 'width', 'height', 'srcset']),
100
100
  font: new Set(['size', 'color', 'face']),
101
101
  hr: widthAttrs,
102
- rt: new Set(['rbspan']),
103
102
  data: new Set(['value']),
104
103
  time: new Set(['datetime']),
105
104
  meta: new Set(['itemprop', 'content']),
@@ -262,9 +261,7 @@ let AttributeToken = (() => {
262
261
  * @param quotes 引号
263
262
  */
264
263
  constructor(type, tag, key, equal = '', value, quotes = [], config = index_1.default.getConfig(), accum = []) {
265
- const keyToken = new atom_1.AtomToken(key, 'attr-key', config, accum, {
266
- [type === 'ext-attr' ? 'AstText' : 'Stage-1']: ':', ArgToken: ':', TranscludeToken: ':',
267
- });
264
+ const keyToken = new atom_1.AtomToken(key, 'attr-key', config, accum, type === 'ext-attr' ? { AstText: ':' } : { 'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '' });
268
265
  let valueToken;
269
266
  if (key === 'title' || tag === 'img' && key === 'alt') {
270
267
  valueToken = new index_2.Token(value, config, accum, {
@@ -273,23 +270,14 @@ let AttributeToken = (() => {
273
270
  valueToken.type = 'attr-value';
274
271
  valueToken.setAttribute('stage', constants_1.MAX_STAGE - 1);
275
272
  }
276
- else if (tag === 'gallery' && key === 'caption') {
277
- const newConfig = {
278
- ...config,
279
- excludes: [...config.excludes, 'quote', 'extLink', 'magicLink', 'list'],
280
- };
281
- valueToken = new index_2.Token(value, newConfig, accum, {
282
- AstText: ':', LinkToken: ':', FileToken: ':', CategoryToken: ':', ConverterToken: ':',
283
- });
284
- valueToken.type = 'attr-value';
285
- valueToken.setAttribute('stage', 5);
286
- }
287
- else if (tag === 'choose' && (key === 'before' || key === 'after')) {
273
+ else if (tag === 'gallery' && key === 'caption'
274
+ || tag === 'choose' && (key === 'before' || key === 'after')) {
288
275
  const newConfig = {
289
276
  ...config,
290
277
  excludes: [...config.excludes, 'heading', 'html', 'table', 'hr', 'list'],
291
278
  };
292
279
  valueToken = new index_2.Token(value, newConfig, accum, {
280
+ AstText: ':',
293
281
  ArgToken: ':',
294
282
  TranscludeToken: ':',
295
283
  LinkToken: ':',
@@ -324,13 +312,13 @@ let AttributeToken = (() => {
324
312
  if (this.parentNode) {
325
313
  this.#tag = this.parentNode.name;
326
314
  }
327
- this.setAttribute('name', this.firstChild.text().trim().toLowerCase());
315
+ this.setAttribute('name', this.firstChild.toString(true).trim().toLowerCase());
328
316
  super.afterBuild();
329
317
  }
330
318
  /** @private */
331
- toString() {
319
+ toString(skip) {
332
320
  const [quoteStart = '', quoteEnd = ''] = this.#quotes;
333
- return this.#equal ? super.toString(this.#equal + quoteStart) + quoteEnd : this.firstChild.toString();
321
+ return this.#equal ? super.toString(skip, this.#equal + quoteStart) + quoteEnd : this.firstChild.toString(skip);
334
322
  }
335
323
  /** @private */
336
324
  text() {
@@ -378,7 +366,7 @@ let AttributeToken = (() => {
378
366
  else if (name === 'style' && typeof value === 'string' && insecureStyle.test(value)) {
379
367
  errors.push((0, lint_1.generateForChild)(lastChild, rect, 'insecure-style', 'insecure style'));
380
368
  }
381
- else if (name === 'tabindex' && typeof value === 'string' && value.trim() !== '0') {
369
+ else if (name === 'tabindex' && typeof value === 'string' && value !== '0') {
382
370
  const e = (0, lint_1.generateForChild)(lastChild, rect, 'illegal-attr', 'nonzero tabindex');
383
371
  e.suggestions = [
384
372
  {
@@ -398,14 +386,7 @@ let AttributeToken = (() => {
398
386
  }
399
387
  /** 获取属性值 */
400
388
  getValue() {
401
- if (this.#equal) {
402
- const value = this.lastChild.text();
403
- if (this.#quotes[1]) {
404
- return value;
405
- }
406
- return value[this.#quotes[0] ? 'trimEnd' : 'trim']();
407
- }
408
- return this.type === 'ext-attr' || '';
389
+ return this.#equal ? this.lastChild.text().trim() : this.type === 'ext-attr' || '';
409
390
  }
410
391
  /** @private */
411
392
  print() {
@@ -479,6 +460,22 @@ let AttributeToken = (() => {
479
460
  const config = this.getAttribute('config'), { childNodes } = index_1.default.parse(key, this.getAttribute('include'), stages[this.type] + 1, config);
480
461
  this.firstChild.replaceChildren(...childNodes);
481
462
  }
463
+ /** @private */
464
+ toHtml() {
465
+ const { type, name, tag, lastChild } = this;
466
+ if (type === 'ext-attr' && !(tag in htmlAttrs)
467
+ || !htmlAttrs[tag]?.has(name) && (tag === 'meta' || tag === 'link' || !commonHtmlAttrs.has(name))) {
468
+ return '';
469
+ }
470
+ let value = lastChild.toHtml().trim();
471
+ if (name === 'style' && insecureStyle.test(value) || name === 'tabindex' && value !== '0') {
472
+ return '';
473
+ }
474
+ else if (name === 'id') {
475
+ value = value.replace(/\s+/gu, '_');
476
+ }
477
+ return `${name}="${value.replace(/["\n]/gu, p => p === '"' ? '&quot;' : '&#10;')}"`;
478
+ }
482
479
  };
483
480
  return AttributeToken = _classThis;
484
481
  })();
@@ -30,7 +30,6 @@ export declare abstract class AttributesToken extends Token {
30
30
  set className(className: string);
31
31
  /** 以Set表示的class属性 */
32
32
  get classList(): Set<string>;
33
- set classList(classList: Set<string>);
34
33
  /** id属性 */
35
34
  get id(): string;
36
35
  set id(id: string);
@@ -30,6 +30,7 @@ const toDirty = (type) => `${toAttributeType(type)}-dirty`;
30
30
  */
31
31
  class AttributesToken extends index_2.Token {
32
32
  #type;
33
+ #classList;
33
34
  /* NOT FOR BROWSER END */
34
35
  get type() {
35
36
  return this.#type;
@@ -53,10 +54,26 @@ class AttributesToken extends index_2.Token {
53
54
  }
54
55
  /** 以Set表示的class属性 */
55
56
  get classList() {
56
- return new Set(this.className.split(/\s/u));
57
- }
58
- set classList(classList) {
59
- this.setAttr('class', [...classList].join(' '));
57
+ if (!this.#classList) {
58
+ this.#classList = new Set(this.className.split(/\s/u));
59
+ /**
60
+ * 更新classList
61
+ * @param prop 方法名
62
+ */
63
+ const factory = (prop) => ({
64
+ value: /** @ignore */ (...args) => {
65
+ const result = Set.prototype[prop].apply(this.#classList, args);
66
+ this.setAttr('class', [...this.#classList].join(' '));
67
+ return result;
68
+ },
69
+ });
70
+ Object.defineProperties(this.#classList, {
71
+ add: factory('add'),
72
+ delete: factory('delete'),
73
+ clear: factory('clear'),
74
+ });
75
+ }
76
+ return this.#classList;
60
77
  }
61
78
  /** id属性 */
62
79
  get id() {
@@ -175,7 +192,7 @@ class AttributesToken extends index_2.Token {
175
192
  duplicated.add(name);
176
193
  attrs.get(name).push(attr);
177
194
  }
178
- else if (name !== 'class') {
195
+ else {
179
196
  attrs.set(name, [attr]);
180
197
  }
181
198
  }
@@ -329,11 +346,11 @@ class AttributesToken extends index_2.Token {
329
346
  return str && type !== 'table-attrs' && !leadingRegex[type].test(str) ? ' ' : '';
330
347
  }
331
348
  /** @private */
332
- toString() {
349
+ toString(skip) {
333
350
  if (this.type === 'table-attrs') {
334
351
  (0, string_1.normalizeSpace)(this);
335
352
  }
336
- const str = super.toString();
353
+ const str = super.toString(skip);
337
354
  return this.#leadingSpace(str) + str;
338
355
  }
339
356
  /** @private */
@@ -350,6 +367,16 @@ class AttributesToken extends index_2.Token {
350
367
  const str = (0, string_1.text)(this.childNodes.filter(child => child instanceof attribute_1.AttributeToken), ' ');
351
368
  return this.#leadingSpace(str) + str;
352
369
  }
370
+ /** @private */
371
+ toHtml() {
372
+ const map = new Map();
373
+ for (const child of this.childNodes) {
374
+ if (child instanceof attribute_1.AttributeToken) {
375
+ map.set(child.name, child);
376
+ }
377
+ }
378
+ return ` ${(0, string_1.html)([...map.values()], ' ')}`;
379
+ }
353
380
  }
354
381
  exports.AttributesToken = AttributesToken;
355
382
  constants_1.classes['AttributesToken'] = __filename;
@@ -10,10 +10,10 @@ export interface ConverterToken extends FlagsParentBase {
10
10
  * @classdesc `{childNodes: [ConverterFlagsToken, ...ConverterRuleToken]}`
11
11
  */
12
12
  export declare abstract class ConverterToken extends Token {
13
- readonly childNodes: readonly [ConverterFlagsToken, ...ConverterRuleToken[]];
13
+ readonly childNodes: readonly [ConverterFlagsToken, ConverterRuleToken, ...ConverterRuleToken[]];
14
14
  abstract get firstChild(): ConverterFlagsToken;
15
15
  abstract get lastChild(): ConverterFlagsToken | ConverterRuleToken;
16
- abstract get children(): [ConverterFlagsToken, ...ConverterRuleToken[]];
16
+ abstract get children(): [ConverterFlagsToken, ConverterRuleToken, ...ConverterRuleToken[]];
17
17
  abstract get firstElementChild(): ConverterFlagsToken;
18
18
  abstract get lastElementChild(): ConverterFlagsToken | ConverterRuleToken;
19
19
  get type(): 'converter';