bbcode-compiler 0.1.9 → 0.1.11

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 (73) hide show
  1. package/dist/index.d.ts +1 -16
  2. package/dist/index.js +866 -1038
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.umd.cjs +914 -1063
  5. package/dist/index.umd.cjs.map +1 -1
  6. package/dist/src/generateHtml.d.ts +2 -0
  7. package/dist/src/generateHtml.d.ts.map +1 -0
  8. package/dist/{generator → src/generator}/Generator.d.ts +2 -3
  9. package/dist/src/generator/Generator.d.ts.map +1 -0
  10. package/dist/{generator → src/generator}/transforms/Transform.d.ts +1 -2
  11. package/dist/src/generator/transforms/Transform.d.ts.map +1 -0
  12. package/dist/src/generator/transforms/htmlTransforms.d.ts +3 -0
  13. package/dist/src/generator/transforms/htmlTransforms.d.ts.map +1 -0
  14. package/dist/{generator → src/generator}/utils/getTagImmediateAttrVal.d.ts +1 -2
  15. package/dist/src/generator/utils/getTagImmediateAttrVal.d.ts.map +1 -0
  16. package/dist/{generator → src/generator}/utils/getTagImmediateText.d.ts +1 -2
  17. package/dist/src/generator/utils/getTagImmediateText.d.ts.map +1 -0
  18. package/dist/{generator → src/generator}/utils/getWidthHeightAttr.d.ts +1 -2
  19. package/dist/src/generator/utils/getWidthHeightAttr.d.ts.map +1 -0
  20. package/dist/src/generator/utils/isDangerousUrl.d.ts.map +1 -0
  21. package/dist/{generator → src/generator}/utils/isOrderedList.d.ts +1 -2
  22. package/dist/src/generator/utils/isOrderedList.d.ts.map +1 -0
  23. package/dist/src/index.d.ts +16 -0
  24. package/dist/src/index.d.ts.map +1 -0
  25. package/dist/{lexer → src/lexer}/Lexer.d.ts +1 -2
  26. package/dist/src/lexer/Lexer.d.ts.map +1 -0
  27. package/dist/{lexer → src/lexer}/Token.d.ts +1 -2
  28. package/dist/src/lexer/Token.d.ts.map +1 -0
  29. package/dist/src/lexer/TokenType.d.ts +5 -0
  30. package/dist/src/lexer/TokenType.d.ts.map +1 -0
  31. package/dist/{parser → src/parser}/AstNode.d.ts +10 -18
  32. package/dist/src/parser/AstNode.d.ts.map +1 -0
  33. package/dist/{parser → src/parser}/Parser.d.ts +3 -4
  34. package/dist/src/parser/Parser.d.ts.map +1 -0
  35. package/dist/src/parser/nodeIsType.d.ts +13 -0
  36. package/dist/src/parser/nodeIsType.d.ts.map +1 -0
  37. package/package.json +81 -82
  38. package/src/generateHtml.ts +4 -4
  39. package/src/generator/Generator.ts +7 -7
  40. package/src/generator/transforms/Transform.ts +1 -1
  41. package/src/generator/transforms/htmlTransforms.ts +6 -6
  42. package/src/generator/utils/getTagImmediateAttrVal.ts +1 -1
  43. package/src/generator/utils/getTagImmediateText.ts +4 -4
  44. package/src/generator/utils/getWidthHeightAttr.ts +1 -1
  45. package/src/generator/utils/isOrderedList.ts +1 -1
  46. package/src/index.ts +15 -15
  47. package/src/lexer/Lexer.ts +9 -9
  48. package/src/lexer/Token.ts +12 -12
  49. package/src/lexer/TokenType.ts +39 -40
  50. package/src/parser/AstNode.ts +30 -29
  51. package/src/parser/Parser.ts +28 -28
  52. package/src/parser/nodeIsType.ts +8 -8
  53. package/dist/generateHtml.d.ts +0 -2
  54. package/dist/generateHtml.d.ts.map +0 -1
  55. package/dist/generator/Generator.d.ts.map +0 -1
  56. package/dist/generator/transforms/Transform.d.ts.map +0 -1
  57. package/dist/generator/transforms/htmlTransforms.d.ts +0 -4
  58. package/dist/generator/transforms/htmlTransforms.d.ts.map +0 -1
  59. package/dist/generator/utils/getTagImmediateAttrVal.d.ts.map +0 -1
  60. package/dist/generator/utils/getTagImmediateText.d.ts.map +0 -1
  61. package/dist/generator/utils/getWidthHeightAttr.d.ts.map +0 -1
  62. package/dist/generator/utils/isDangerousUrl.d.ts.map +0 -1
  63. package/dist/generator/utils/isOrderedList.d.ts.map +0 -1
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/lexer/Lexer.d.ts.map +0 -1
  66. package/dist/lexer/Token.d.ts.map +0 -1
  67. package/dist/lexer/TokenType.d.ts +0 -17
  68. package/dist/lexer/TokenType.d.ts.map +0 -1
  69. package/dist/parser/AstNode.d.ts.map +0 -1
  70. package/dist/parser/Parser.d.ts.map +0 -1
  71. package/dist/parser/nodeIsType.d.ts +0 -14
  72. package/dist/parser/nodeIsType.d.ts.map +0 -1
  73. /package/dist/{generator → src/generator}/utils/isDangerousUrl.d.ts +0 -0
package/dist/index.js CHANGED
@@ -1,1061 +1,889 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1
+ //#region src/parser/nodeIsType.ts
4
2
  function nodeIsType(node, nodeType) {
5
- return node.nodeType === nodeType;
3
+ return node.nodeType === nodeType;
6
4
  }
7
- var AstNodeType = /* @__PURE__ */ ((AstNodeType2) => {
8
- AstNodeType2["RootNode"] = "RootNode";
9
- AstNodeType2["TextNode"] = "TextNode";
10
- AstNodeType2["LinebreakNode"] = "LinebreakNode";
11
- AstNodeType2["TagNode"] = "TagNode";
12
- AstNodeType2["StartTagNode"] = "StartTagNode";
13
- AstNodeType2["EndTagNode"] = "EndTagNode";
14
- AstNodeType2["AttrNode"] = "AttrNode";
15
- return AstNodeType2;
16
- })(AstNodeType || {});
17
- class AstNode {
18
- constructor(children = []) {
19
- this.children = children;
20
- }
21
- addChild(node) {
22
- this.children.push(node);
23
- }
24
- isValid() {
25
- for (const child of this.children) {
26
- if (!child.isValid()) {
27
- return false;
28
- }
29
- }
30
- return true;
31
- }
32
- toShortString() {
33
- return this.nodeType;
34
- }
35
- // For debugging purposes only
36
- // Pretty-prints AST
37
- toString(depth = 0) {
38
- let s = " ".repeat(depth * 2) + this.toShortString();
39
- for (const child of this.children) {
40
- s += "\n" + child.toString(depth + 1);
41
- }
42
- return s;
43
- }
44
- toJSON() {
45
- const json = {
46
- type: this.nodeType
47
- };
48
- if (this.children.length > 0) {
49
- json.children = this.children.map((child) => child.toJSON());
50
- }
51
- return json;
52
- }
53
- }
54
- class RootNode extends AstNode {
55
- constructor() {
56
- super(...arguments);
57
- __publicField(this, "nodeType", "RootNode");
58
- }
59
- isValid() {
60
- for (const child of this.children) {
61
- if (child.nodeType !== "TagNode" && child.nodeType !== "TextNode" && child.nodeType !== "LinebreakNode") {
62
- return false;
63
- }
64
- }
65
- return super.isValid() && this.children.length > 0;
66
- }
67
- }
68
- class TextNode extends AstNode {
69
- constructor(str) {
70
- super();
71
- __publicField(this, "nodeType", "TextNode");
72
- __publicField(this, "str");
73
- this.str = str;
74
- }
75
- isValid() {
76
- return super.isValid() && this.children.length === 0;
77
- }
78
- toShortString() {
79
- return `${super.toShortString()} "${this.str}"`;
80
- }
81
- toJSON() {
82
- const json = super.toJSON();
83
- json.data = {
84
- str: this.str
85
- };
86
- return json;
87
- }
88
- }
89
- class LinebreakNode extends AstNode {
90
- constructor() {
91
- super(...arguments);
92
- __publicField(this, "nodeType", "LinebreakNode");
93
- }
94
- toShortString() {
95
- return `${super.toShortString()} "\\n"`;
96
- }
97
- }
98
- const _AttrNode = class _AttrNode extends AstNode {
99
- constructor() {
100
- super(...arguments);
101
- __publicField(this, "nodeType", "AttrNode");
102
- }
103
- get key() {
104
- switch (this.children.length) {
105
- case 1: {
106
- return _AttrNode.DEFAULT_KEY;
107
- }
108
- case 2: {
109
- if (!nodeIsType(
110
- this.children[0],
111
- "TextNode"
112
- /* TextNode */
113
- )) {
114
- throw new Error("Invalid TextNode");
115
- }
116
- return this.children[0].str.trim();
117
- }
118
- }
119
- throw new Error("Invalid AttrNode");
120
- }
121
- get val() {
122
- switch (this.children.length) {
123
- case 1: {
124
- if (!nodeIsType(
125
- this.children[0],
126
- "TextNode"
127
- /* TextNode */
128
- )) {
129
- throw new Error("Invalid TextNode");
130
- }
131
- return this.children[0].str.trim();
132
- }
133
- case 2: {
134
- if (!nodeIsType(
135
- this.children[1],
136
- "TextNode"
137
- /* TextNode */
138
- )) {
139
- throw new Error("Invalid TextNode");
140
- }
141
- return this.children[1].str.trim();
142
- }
143
- }
144
- throw new Error("Invalid AttrNode");
145
- }
146
- isValid() {
147
- return super.isValid() && (this.children.length >= 1 && this.children.length <= 2);
148
- }
149
- toShortString() {
150
- let s = super.toShortString();
151
- switch (this.children.length) {
152
- case 1: {
153
- s += ` VAL="${this.val}"`;
154
- break;
155
- }
156
- case 2: {
157
- s += ` KEY="${this.key}" VAL="${this.val}"`;
158
- break;
159
- }
160
- }
161
- return s;
162
- }
163
- toJSON() {
164
- const json = {
165
- type: this.nodeType
166
- };
167
- switch (this.children.length) {
168
- case 1: {
169
- json.data = {
170
- key: this.key
171
- };
172
- break;
173
- }
174
- case 2: {
175
- json.data = {
176
- key: this.key,
177
- val: this.val
178
- };
179
- break;
180
- }
181
- }
182
- return json;
183
- }
5
+ //#endregion
6
+ //#region src/parser/AstNode.ts
7
+ /**
8
+
9
+ Haven't formally verified this grammar but it should be LL(2)
10
+
11
+ The root's intermediate state has StartTag/EndTag because it's easier to first parse them as independant nodes
12
+ than to parse a StartTag and find the matching EndTag since we can only lookahead by 1 token
13
+
14
+ Trying to lookahead by 4 tokens after each advancement to determine the end of the sub-root will greatly affect performance
15
+ 1 "["
16
+ 2 "/"
17
+ 3 "LABEL"
18
+ 4 "]"
19
+
20
+ ---
21
+
22
+ Root <- (Text | Linebreak | Tag)*
23
+
24
+ Text <-
25
+ | {XSS Characters}.
26
+ | STR.
27
+
28
+ Linebreak <-
29
+ | LINEBREAK.
30
+
31
+ Tag <- StartTag Root EndTag
32
+ StartTag <- L_BRACKET Text Attr* R_BRACKET
33
+ EndTag <- L_BRACKET BACKSLASH Text R_BRACKET
34
+
35
+ Attr <-
36
+ | STR EQUALS STR
37
+ | EQUALS STR
38
+ | STR
39
+
40
+ */
41
+ var AstNode = class {
42
+ children;
43
+ constructor(children = new Array()) {
44
+ this.children = children;
45
+ }
46
+ addChild(node) {
47
+ this.children.push(node);
48
+ }
49
+ isValid() {
50
+ for (const child of this.children) if (!child.isValid()) return false;
51
+ return true;
52
+ }
53
+ toShortString() {
54
+ return this.nodeType;
55
+ }
56
+ toString(depth = 0) {
57
+ let s = " ".repeat(depth * 2) + this.toShortString();
58
+ for (const child of this.children) s += "\n" + child.toString(depth + 1);
59
+ return s;
60
+ }
61
+ toJSON() {
62
+ const json = { type: this.nodeType };
63
+ if (this.children.length > 0) json.children = this.children.map((child) => child.toJSON());
64
+ return json;
65
+ }
184
66
  };
185
- __publicField(_AttrNode, "DEFAULT_KEY", "default");
186
- let AttrNode = _AttrNode;
187
- class StartTagNode extends AstNode {
188
- constructor(tagName, ogTag, attrNodes = []) {
189
- super(attrNodes);
190
- __publicField(this, "nodeType", "StartTagNode");
191
- __publicField(this, "tagName");
192
- __publicField(this, "ogTag");
193
- this.tagName = tagName.toLowerCase();
194
- this.ogTag = ogTag;
195
- }
196
- isValid() {
197
- for (const child of this.children) {
198
- if (child.nodeType !== "AttrNode") {
199
- return false;
200
- }
201
- }
202
- return super.isValid();
203
- }
204
- toShortString() {
205
- return `${super.toShortString()} ${this.ogTag}`;
206
- }
207
- toJSON() {
208
- const json = super.toJSON();
209
- json.data = {
210
- tag: this.tagName
211
- };
212
- return json;
213
- }
214
- }
215
- class EndTagNode extends AstNode {
216
- constructor(tagName, ogTag) {
217
- super();
218
- __publicField(this, "nodeType", "EndTagNode");
219
- __publicField(this, "tagName");
220
- __publicField(this, "ogTag");
221
- this.tagName = tagName;
222
- this.ogTag = ogTag;
223
- }
224
- isValid() {
225
- return super.isValid() && this.children.length === 0;
226
- }
227
- toShortString() {
228
- return `${super.toShortString()} ${this.ogTag}`;
229
- }
230
- toJSON() {
231
- const json = super.toJSON();
232
- json.data = {
233
- tag: this.tagName
234
- };
235
- return json;
236
- }
237
- }
238
- class TagNode extends AstNode {
239
- constructor(startTag, endTag) {
240
- super();
241
- __publicField(this, "nodeType", "TagNode");
242
- __publicField(this, "_startTag");
243
- __publicField(this, "_endTag");
244
- this._startTag = startTag;
245
- this._endTag = endTag;
246
- }
247
- get tagName() {
248
- return this._startTag.tagName;
249
- }
250
- get attributes() {
251
- return this._startTag.children;
252
- }
253
- get ogStartTag() {
254
- return this._startTag.ogTag;
255
- }
256
- get ogEndTag() {
257
- if (!this._endTag) {
258
- return "";
259
- }
260
- if (nodeIsType(
261
- this._endTag,
262
- "LinebreakNode"
263
- /* LinebreakNode */
264
- )) {
265
- return "\n";
266
- } else {
267
- return this._endTag.ogTag;
268
- }
269
- }
270
- isValid() {
271
- var _a;
272
- if (this._endTag && nodeIsType(
273
- this._endTag,
274
- "EndTagNode"
275
- /* EndTagNode */
276
- ) && this._startTag.tagName !== this._endTag.tagName) {
277
- return false;
278
- }
279
- if (this.children.length === 1 && this.children[0].nodeType !== "RootNode") {
280
- return false;
281
- }
282
- if (this.children.length > 2) {
283
- return false;
284
- }
285
- return super.isValid() && this._startTag.isValid() && (((_a = this._endTag) == null ? void 0 : _a.isValid()) ?? true);
286
- }
287
- toString(depth = 0) {
288
- let s = " ".repeat(depth * 2) + this.toShortString() + ` [${this.tagName}]`;
289
- for (const attrNode of this._startTag.children) {
290
- s += "\n" + attrNode.toString(depth + 1);
291
- }
292
- for (const child of this.children) {
293
- s += "\n" + child.toString(depth + 1);
294
- }
295
- return s;
296
- }
297
- toJSON() {
298
- const json = super.toJSON();
299
- json.data = {
300
- startTag: this._startTag.toJSON()
301
- };
302
- if (this._endTag) {
303
- json.data.endTag = this._endTag.toJSON();
304
- }
305
- return json;
306
- }
307
- }
67
+ var RootNode = class extends AstNode {
68
+ nodeType = "RootNode";
69
+ isValid() {
70
+ for (const child of this.children) if (child.nodeType !== "TagNode" && child.nodeType !== "TextNode" && child.nodeType !== "LinebreakNode") return false;
71
+ return super.isValid() && this.children.length > 0;
72
+ }
73
+ };
74
+ var TextNode = class extends AstNode {
75
+ nodeType = "TextNode";
76
+ str;
77
+ constructor(str) {
78
+ super();
79
+ this.str = str;
80
+ }
81
+ isValid() {
82
+ return super.isValid() && this.children.length === 0;
83
+ }
84
+ toShortString() {
85
+ return `${super.toShortString()} "${this.str}"`;
86
+ }
87
+ toJSON() {
88
+ const json = super.toJSON();
89
+ json.data = { str: this.str };
90
+ return json;
91
+ }
92
+ };
93
+ var LinebreakNode = class extends AstNode {
94
+ nodeType = "LinebreakNode";
95
+ toShortString() {
96
+ return `${super.toShortString()} "\\n"`;
97
+ }
98
+ };
99
+ var AttrNode = class AttrNode extends AstNode {
100
+ nodeType = "AttrNode";
101
+ static DEFAULT_KEY = "default";
102
+ get key() {
103
+ switch (this.children.length) {
104
+ case 1: return AttrNode.DEFAULT_KEY;
105
+ case 2:
106
+ if (!nodeIsType(this.children[0], "TextNode")) throw new Error("Invalid TextNode");
107
+ return this.children[0].str.trim();
108
+ }
109
+ throw new Error("Invalid AttrNode");
110
+ }
111
+ get val() {
112
+ switch (this.children.length) {
113
+ case 1:
114
+ if (!nodeIsType(this.children[0], "TextNode")) throw new Error("Invalid TextNode");
115
+ return this.children[0].str.trim();
116
+ case 2:
117
+ if (!nodeIsType(this.children[1], "TextNode")) throw new Error("Invalid TextNode");
118
+ return this.children[1].str.trim();
119
+ }
120
+ throw new Error("Invalid AttrNode");
121
+ }
122
+ isValid() {
123
+ return super.isValid() && this.children.length >= 1 && this.children.length <= 2;
124
+ }
125
+ toShortString() {
126
+ let s = super.toShortString();
127
+ switch (this.children.length) {
128
+ case 1:
129
+ s += ` VAL="${this.val}"`;
130
+ break;
131
+ case 2:
132
+ s += ` KEY="${this.key}" VAL="${this.val}"`;
133
+ break;
134
+ }
135
+ return s;
136
+ }
137
+ toJSON() {
138
+ const json = { type: this.nodeType };
139
+ switch (this.children.length) {
140
+ case 1:
141
+ json.data = { key: this.key };
142
+ break;
143
+ case 2:
144
+ json.data = {
145
+ key: this.key,
146
+ val: this.val
147
+ };
148
+ break;
149
+ }
150
+ return json;
151
+ }
152
+ };
153
+ var StartTagNode = class extends AstNode {
154
+ nodeType = "StartTagNode";
155
+ tagName;
156
+ ogTag;
157
+ constructor(tagName, ogTag, attrNodes = []) {
158
+ super(attrNodes);
159
+ this.tagName = tagName.toLowerCase();
160
+ this.ogTag = ogTag;
161
+ }
162
+ isValid() {
163
+ for (const child of this.children) if (child.nodeType !== "AttrNode") return false;
164
+ return super.isValid();
165
+ }
166
+ toShortString() {
167
+ return `${super.toShortString()} ${this.ogTag}`;
168
+ }
169
+ toJSON() {
170
+ const json = super.toJSON();
171
+ json.data = { tag: this.tagName };
172
+ return json;
173
+ }
174
+ };
175
+ var EndTagNode = class extends AstNode {
176
+ nodeType = "EndTagNode";
177
+ tagName;
178
+ ogTag;
179
+ constructor(tagName, ogTag) {
180
+ super();
181
+ this.tagName = tagName;
182
+ this.ogTag = ogTag;
183
+ }
184
+ isValid() {
185
+ return super.isValid() && this.children.length === 0;
186
+ }
187
+ toShortString() {
188
+ return `${super.toShortString()} ${this.ogTag}`;
189
+ }
190
+ toJSON() {
191
+ const json = super.toJSON();
192
+ json.data = { tag: this.tagName };
193
+ return json;
194
+ }
195
+ };
196
+ var TagNode = class extends AstNode {
197
+ nodeType = "TagNode";
198
+ _startTag;
199
+ _endTag;
200
+ constructor(startTag, endTag) {
201
+ super();
202
+ this._startTag = startTag;
203
+ this._endTag = endTag;
204
+ }
205
+ get tagName() {
206
+ return this._startTag.tagName;
207
+ }
208
+ get attributes() {
209
+ return this._startTag.children;
210
+ }
211
+ get ogStartTag() {
212
+ return this._startTag.ogTag;
213
+ }
214
+ get ogEndTag() {
215
+ if (!this._endTag) return "";
216
+ if (nodeIsType(this._endTag, "LinebreakNode")) return "\n";
217
+ else return this._endTag.ogTag;
218
+ }
219
+ isValid() {
220
+ if (this._endTag && nodeIsType(this._endTag, "EndTagNode") && this._startTag.tagName !== this._endTag.tagName) return false;
221
+ if (this.children.length === 1 && this.children[0].nodeType !== "RootNode") return false;
222
+ if (this.children.length > 2) return false;
223
+ return super.isValid() && this._startTag.isValid() && (this._endTag?.isValid() ?? true);
224
+ }
225
+ toString(depth = 0) {
226
+ let s = " ".repeat(depth * 2) + this.toShortString() + ` [${this.tagName}]`;
227
+ for (const attrNode of this._startTag.children) s += "\n" + attrNode.toString(depth + 1);
228
+ for (const child of this.children) s += "\n" + child.toString(depth + 1);
229
+ return s;
230
+ }
231
+ toJSON() {
232
+ const json = super.toJSON();
233
+ json.data = { startTag: this._startTag.toJSON() };
234
+ if (this._endTag) json.data.endTag = this._endTag.toJSON();
235
+ return json;
236
+ }
237
+ };
238
+ //#endregion
239
+ //#region src/generator/utils/getTagImmediateAttrVal.ts
240
+ /**
241
+ * Gets the text of the immediate attribute of the current TagNode
242
+ *
243
+ * [url=https://en.wikipedia.org]English Wikipedia[/url]
244
+ *
245
+ * TagNode [url]
246
+ * AttrNode VAL="https://en.wikipedia.org" (returns this string)
247
+ * TextNode "https://en.wikipedia.org"
248
+ * RootNode
249
+ * TextNode "English Wikipedia"
250
+ */
308
251
  function getTagImmediateAttrVal(tagNode) {
309
- if (tagNode.attributes.length !== 1) {
310
- return void 0;
311
- }
312
- const attrNode = tagNode.attributes[0];
313
- return attrNode.val;
252
+ if (tagNode.attributes.length !== 1) return;
253
+ return tagNode.attributes[0].val;
314
254
  }
255
+ //#endregion
256
+ //#region src/generator/utils/getTagImmediateText.ts
257
+ /**
258
+ * Gets the text of the immediate descendant of the current TagNode
259
+ *
260
+ * [url]https://en.wikipedia.org[/url]
261
+ *
262
+ * TagNode [url]
263
+ * RootNode
264
+ * TextNode "https://en.wikipedia.org" (returns this string)
265
+ */
315
266
  function getTagImmediateText(tagNode) {
316
- if (tagNode.children.length !== 1) {
317
- return void 0;
318
- }
319
- const child = tagNode.children[0];
320
- if (!nodeIsType(child, AstNodeType.RootNode)) {
321
- return void 0;
322
- }
323
- if (child.children.length !== 1) {
324
- return void 0;
325
- }
326
- const textNode = child.children[0];
327
- if (!nodeIsType(textNode, AstNodeType.TextNode)) {
328
- return void 0;
329
- }
330
- return textNode.str;
267
+ if (tagNode.children.length !== 1) return;
268
+ const child = tagNode.children[0];
269
+ if (!nodeIsType(child, "RootNode")) return;
270
+ if (child.children.length !== 1) return;
271
+ const textNode = child.children[0];
272
+ if (!nodeIsType(textNode, "TextNode")) return;
273
+ return textNode.str;
331
274
  }
275
+ //#endregion
276
+ //#region src/generator/utils/getWidthHeightAttr.ts
277
+ /**
278
+ * Gets the width/height attributes of the TagNode if they exist
279
+ *
280
+ * [img 500x300]https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png[/img]
281
+ *
282
+ * RootNode
283
+ * TagNode [img] (returns width:500, height:300)
284
+ * AttrNode VAL="500x300"
285
+ * TextNode " 500x300"
286
+ * RootNode
287
+ * TextNode "https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png"
288
+ *
289
+ * [img width=500 height=300]https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png[/img]
290
+ *
291
+ * RootNode
292
+ * TagNode [img] (returns width:500, height:300)
293
+ * AttrNode KEY="width" VAL="500"
294
+ * TextNode " width"
295
+ * TextNode "500"
296
+ * AttrNode KEY="height" VAL="300"
297
+ * TextNode " height"
298
+ * TextNode "300"
299
+ * RootNode
300
+ * TextNode "https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png
301
+ */
332
302
  function getWidthHeightAttr(tagNode) {
333
- let width;
334
- let height;
335
- for (const child of tagNode.attributes) {
336
- if (child.key === "width") {
337
- width = child.val;
338
- }
339
- if (child.key === "height") {
340
- height = child.val;
341
- }
342
- const matches = /(\d+)x(\d+)/.exec(child.val);
343
- if (matches) {
344
- width = matches[1];
345
- height = matches[2];
346
- }
347
- }
348
- return {
349
- width,
350
- height
351
- };
303
+ let width;
304
+ let height;
305
+ for (const child of tagNode.attributes) {
306
+ if (child.key === "width") width = child.val;
307
+ if (child.key === "height") height = child.val;
308
+ const matches = /(\d+)x(\d+)/.exec(child.val);
309
+ if (matches) {
310
+ width = matches[1];
311
+ height = matches[2];
312
+ }
313
+ }
314
+ return {
315
+ width,
316
+ height
317
+ };
352
318
  }
353
- const dangerousUriRe = /^(vbscript|javascript|file|data):/;
354
- const safeDataUriRe = /^data:image\/(gif|png|jpeg|webp);/;
319
+ //#endregion
320
+ //#region src/generator/utils/isDangerousUrl.ts
321
+ var dangerousUriRe = /^(vbscript|javascript|file|data):/;
322
+ var safeDataUriRe = /^data:image\/(gif|png|jpeg|webp);/;
355
323
  function isDangerousUrl(url) {
356
- const normalizedUrl = url.trim().toLowerCase();
357
- if (!dangerousUriRe.test(normalizedUrl)) {
358
- return false;
359
- }
360
- if (safeDataUriRe.test(normalizedUrl)) {
361
- return false;
362
- }
363
- return true;
324
+ const normalizedUrl = url.trim().toLowerCase();
325
+ if (!dangerousUriRe.test(normalizedUrl)) return false;
326
+ if (safeDataUriRe.test(normalizedUrl)) return false;
327
+ return true;
364
328
  }
329
+ //#endregion
330
+ //#region src/generator/utils/isOrderedList.ts
331
+ /**
332
+ * Determines if the StartTag has an attribute of "1" to indicate that it's an ordered list
333
+ *
334
+ * [list=1]
335
+ *
336
+ * TagNode [list]
337
+ * AttrNode VAL="1"
338
+ * TextNode "1"
339
+ * RootNode
340
+ * TagNode [*]
341
+ * RootNode
342
+ * TextNode "Entry 1"
343
+ * TagNode [*]
344
+ * RootNode
345
+ * TextNode "Entry 2"
346
+ */
365
347
  function isOrderedList(node) {
366
- for (const child of node.attributes) {
367
- const val = child.val;
368
- if (val === "1") {
369
- return true;
370
- }
371
- }
372
- return false;
348
+ for (const child of node.attributes) if (child.val === "1") return true;
349
+ return false;
373
350
  }
374
- const htmlTransforms = [
375
- {
376
- name: "b",
377
- start: () => {
378
- return "<strong>";
379
- },
380
- end: () => {
381
- return "</strong>";
382
- }
383
- },
384
- {
385
- name: "i",
386
- start: () => {
387
- return "<em>";
388
- },
389
- end: () => {
390
- return "</em>";
391
- }
392
- },
393
- {
394
- name: "u",
395
- start: () => {
396
- return "<ins>";
397
- },
398
- end: () => {
399
- return "</ins>";
400
- }
401
- },
402
- {
403
- name: "s",
404
- start: () => {
405
- return "<del>";
406
- },
407
- end: () => {
408
- return "</del>";
409
- }
410
- },
411
- {
412
- name: "style",
413
- start: (tagNode) => {
414
- let style = "";
415
- for (const child of tagNode.attributes) {
416
- switch (child.key) {
417
- case "color": {
418
- style += `color:${child.val};`;
419
- continue;
420
- }
421
- case "size": {
422
- if (/^\d+$/.test(child.val)) {
423
- style += `font-size:${child.val}%;`;
424
- } else {
425
- style += `font-size:${child.val};`;
426
- }
427
- continue;
428
- }
429
- }
430
- }
431
- return `<span style="${style}">`;
432
- },
433
- end: () => {
434
- return "</span>";
435
- }
436
- },
437
- {
438
- name: "color",
439
- start: (tagNode) => {
440
- const color = getTagImmediateAttrVal(tagNode);
441
- return `<span style="color:${color};">`;
442
- },
443
- end: () => {
444
- return "</span>";
445
- }
446
- },
447
- {
448
- name: "hr",
449
- isStandalone: true,
450
- start: () => {
451
- return "<hr />";
452
- }
453
- },
454
- {
455
- name: "br",
456
- isStandalone: true,
457
- start: () => {
458
- return "<br />";
459
- }
460
- },
461
- {
462
- name: "list",
463
- start: (tagNode) => {
464
- return isOrderedList(tagNode) ? "<ol>" : "<ul>";
465
- },
466
- end: (tagNode) => {
467
- return isOrderedList(tagNode) ? "</ol>" : "</ul>";
468
- }
469
- },
470
- {
471
- name: "*",
472
- isLinebreakTerminated: true,
473
- start: () => {
474
- return "<li>";
475
- },
476
- end: () => {
477
- return "</li>";
478
- }
479
- },
480
- {
481
- name: "img",
482
- skipChildren: true,
483
- start: (tagNode) => {
484
- const src = getTagImmediateText(tagNode);
485
- if (!src) {
486
- return false;
487
- }
488
- if (isDangerousUrl(src)) {
489
- return false;
490
- }
491
- const { width, height } = getWidthHeightAttr(tagNode);
492
- let str = `<img src="${src}"`;
493
- if (width) {
494
- str += ` width="${width}"`;
495
- }
496
- if (height) {
497
- str += ` height="${height}"`;
498
- }
499
- str += ">";
500
- return str;
501
- }
502
- },
503
- {
504
- name: "url",
505
- start: (tagNode) => {
506
- const href = getTagImmediateAttrVal(tagNode) ?? getTagImmediateText(tagNode);
507
- if (!href) {
508
- return false;
509
- }
510
- if (isDangerousUrl(href)) {
511
- return false;
512
- }
513
- return `<a href="${href}">`;
514
- },
515
- end: () => {
516
- return "</a>";
517
- }
518
- },
519
- {
520
- name: "quote",
521
- start: (tagNode) => {
522
- const author = getTagImmediateAttrVal(tagNode);
523
- return author ? `<blockquote><strong>${author}</strong>` : "<blockquote>";
524
- },
525
- end: () => {
526
- return "</blockquote>";
527
- }
528
- },
529
- {
530
- name: "table",
531
- start: () => {
532
- return "<table>";
533
- },
534
- end: () => {
535
- return "</table>";
536
- }
537
- },
538
- {
539
- name: "tr",
540
- start: () => {
541
- return "<tr>";
542
- },
543
- end: () => {
544
- return "</tr>";
545
- }
546
- },
547
- {
548
- name: "td",
549
- start: () => {
550
- return "<td>";
551
- },
552
- end: () => {
553
- return "</td>";
554
- }
555
- },
556
- {
557
- name: "code",
558
- start: () => {
559
- return "<code>";
560
- },
561
- end: () => {
562
- return "</code>";
563
- }
564
- }
351
+ //#endregion
352
+ //#region src/generator/transforms/htmlTransforms.ts
353
+ var htmlTransforms = [
354
+ {
355
+ name: "b",
356
+ start: () => {
357
+ return "<strong>";
358
+ },
359
+ end: () => {
360
+ return "</strong>";
361
+ }
362
+ },
363
+ {
364
+ name: "i",
365
+ start: () => {
366
+ return "<em>";
367
+ },
368
+ end: () => {
369
+ return "</em>";
370
+ }
371
+ },
372
+ {
373
+ name: "u",
374
+ start: () => {
375
+ return "<ins>";
376
+ },
377
+ end: () => {
378
+ return "</ins>";
379
+ }
380
+ },
381
+ {
382
+ name: "s",
383
+ start: () => {
384
+ return "<del>";
385
+ },
386
+ end: () => {
387
+ return "</del>";
388
+ }
389
+ },
390
+ {
391
+ name: "style",
392
+ start: (tagNode) => {
393
+ let style = "";
394
+ for (const child of tagNode.attributes) switch (child.key) {
395
+ case "color":
396
+ style += `color:${child.val};`;
397
+ continue;
398
+ case "size":
399
+ if (/^\d+$/.test(child.val)) style += `font-size:${child.val}%;`;
400
+ else style += `font-size:${child.val};`;
401
+ continue;
402
+ }
403
+ return `<span style="${style}">`;
404
+ },
405
+ end: () => {
406
+ return "</span>";
407
+ }
408
+ },
409
+ {
410
+ name: "color",
411
+ start: (tagNode) => {
412
+ return `<span style="color:${getTagImmediateAttrVal(tagNode)};">`;
413
+ },
414
+ end: () => {
415
+ return "</span>";
416
+ }
417
+ },
418
+ {
419
+ name: "hr",
420
+ isStandalone: true,
421
+ start: () => {
422
+ return "<hr />";
423
+ }
424
+ },
425
+ {
426
+ name: "br",
427
+ isStandalone: true,
428
+ start: () => {
429
+ return "<br />";
430
+ }
431
+ },
432
+ {
433
+ name: "list",
434
+ start: (tagNode) => {
435
+ return isOrderedList(tagNode) ? "<ol>" : "<ul>";
436
+ },
437
+ end: (tagNode) => {
438
+ return isOrderedList(tagNode) ? "</ol>" : "</ul>";
439
+ }
440
+ },
441
+ {
442
+ name: "*",
443
+ isLinebreakTerminated: true,
444
+ start: () => {
445
+ return "<li>";
446
+ },
447
+ end: () => {
448
+ return "</li>";
449
+ }
450
+ },
451
+ {
452
+ name: "img",
453
+ skipChildren: true,
454
+ start: (tagNode) => {
455
+ const src = getTagImmediateText(tagNode);
456
+ if (!src) return false;
457
+ if (isDangerousUrl(src)) return false;
458
+ const { width, height } = getWidthHeightAttr(tagNode);
459
+ let str = `<img src="${src}"`;
460
+ if (width) str += ` width="${width}"`;
461
+ if (height) str += ` height="${height}"`;
462
+ str += ">";
463
+ return str;
464
+ }
465
+ },
466
+ {
467
+ name: "url",
468
+ start: (tagNode) => {
469
+ const href = getTagImmediateAttrVal(tagNode) ?? getTagImmediateText(tagNode);
470
+ if (!href) return false;
471
+ if (isDangerousUrl(href)) return false;
472
+ return `<a href="${href}">`;
473
+ },
474
+ end: () => {
475
+ return "</a>";
476
+ }
477
+ },
478
+ {
479
+ name: "quote",
480
+ start: (tagNode) => {
481
+ const author = getTagImmediateAttrVal(tagNode);
482
+ return author ? `<blockquote><strong>${author}</strong>` : "<blockquote>";
483
+ },
484
+ end: () => {
485
+ return "</blockquote>";
486
+ }
487
+ },
488
+ {
489
+ name: "table",
490
+ start: () => {
491
+ return "<table>";
492
+ },
493
+ end: () => {
494
+ return "</table>";
495
+ }
496
+ },
497
+ {
498
+ name: "tr",
499
+ start: () => {
500
+ return "<tr>";
501
+ },
502
+ end: () => {
503
+ return "</tr>";
504
+ }
505
+ },
506
+ {
507
+ name: "td",
508
+ start: () => {
509
+ return "<td>";
510
+ },
511
+ end: () => {
512
+ return "</td>";
513
+ }
514
+ },
515
+ {
516
+ name: "code",
517
+ start: () => {
518
+ return "<code>";
519
+ },
520
+ end: () => {
521
+ return "</code>";
522
+ }
523
+ }
565
524
  ];
566
- class Generator {
567
- constructor(transforms = htmlTransforms) {
568
- __publicField(this, "transforms");
569
- this.transforms = new Map(transforms.map((transform) => [transform.name, transform]));
570
- }
571
- generate(root) {
572
- const stringify = (node) => {
573
- var _a;
574
- let output = "";
575
- if (nodeIsType(node, AstNodeType.TagNode)) {
576
- const tagName = node.tagName;
577
- const transform = this.transforms.get(tagName);
578
- if (!transform) {
579
- throw new Error(`Unrecognized bbcode ${node.tagName}`);
580
- }
581
- const renderedStartTag = transform.start(node);
582
- const renderedEndTag = ((_a = transform.end) == null ? void 0 : _a.call(transform, node)) ?? "";
583
- const isInvalidTag = renderedStartTag === false;
584
- if (isInvalidTag) {
585
- output += node.ogStartTag;
586
- } else {
587
- output += renderedStartTag;
588
- }
589
- if (!transform.skipChildren || isInvalidTag) {
590
- for (const child of node.children) {
591
- output += stringify(child);
592
- }
593
- }
594
- if (isInvalidTag) {
595
- output += node.ogEndTag;
596
- } else {
597
- output += renderedEndTag;
598
- }
599
- } else if (nodeIsType(node, AstNodeType.TextNode)) {
600
- output += node.str;
601
- } else if (nodeIsType(node, AstNodeType.LinebreakNode)) {
602
- output += "\n";
603
- } else {
604
- for (const child of node.children) {
605
- output += stringify(child);
606
- }
607
- }
608
- return output;
609
- };
610
- return stringify(root);
611
- }
612
- }
613
- var TokenType = /* @__PURE__ */ ((TokenType2) => {
614
- TokenType2[TokenType2["STR"] = 0] = "STR";
615
- TokenType2[TokenType2["LINEBREAK"] = 1] = "LINEBREAK";
616
- TokenType2[TokenType2["L_BRACKET"] = 2] = "L_BRACKET";
617
- TokenType2[TokenType2["R_BRACKET"] = 3] = "R_BRACKET";
618
- TokenType2[TokenType2["BACKSLASH"] = 4] = "BACKSLASH";
619
- TokenType2[TokenType2["EQUALS"] = 5] = "EQUALS";
620
- TokenType2[TokenType2["XSS_AMP"] = 6] = "XSS_AMP";
621
- TokenType2[TokenType2["XSS_LT"] = 7] = "XSS_LT";
622
- TokenType2[TokenType2["XSS_GT"] = 8] = "XSS_GT";
623
- TokenType2[TokenType2["XSS_D_QUOTE"] = 9] = "XSS_D_QUOTE";
624
- TokenType2[TokenType2["XSS_S_QUOTE"] = 10] = "XSS_S_QUOTE";
625
- return TokenType2;
626
- })(TokenType || {});
525
+ //#endregion
526
+ //#region src/generator/Generator.ts
527
+ var Generator = class {
528
+ transforms;
529
+ constructor(transforms = htmlTransforms) {
530
+ this.transforms = new Map(transforms.map((transform) => [transform.name, transform]));
531
+ }
532
+ generate(root) {
533
+ const stringify = (node) => {
534
+ let output = "";
535
+ if (nodeIsType(node, "TagNode")) {
536
+ const tagName = node.tagName;
537
+ const transform = this.transforms.get(tagName);
538
+ if (!transform) throw new Error(`Unrecognized bbcode ${node.tagName}`);
539
+ const renderedStartTag = transform.start(node);
540
+ const renderedEndTag = transform.end?.(node) ?? "";
541
+ const isInvalidTag = renderedStartTag === false;
542
+ if (isInvalidTag) output += node.ogStartTag;
543
+ else output += renderedStartTag;
544
+ if (!transform.skipChildren || isInvalidTag) for (const child of node.children) output += stringify(child);
545
+ if (isInvalidTag) output += node.ogEndTag;
546
+ else output += renderedEndTag;
547
+ } else if (nodeIsType(node, "TextNode")) output += node.str;
548
+ else if (nodeIsType(node, "LinebreakNode")) output += "\n";
549
+ else for (const child of node.children) output += stringify(child);
550
+ return output;
551
+ };
552
+ return stringify(root);
553
+ }
554
+ };
555
+ //#endregion
556
+ //#region src/lexer/TokenType.ts
627
557
  function tokenTypeToString(tokenType) {
628
- switch (tokenType) {
629
- case 0:
630
- return "STR";
631
- case 1:
632
- return "LINEBREAK";
633
- case 2:
634
- return "L_BRACKET";
635
- case 3:
636
- return "R_BRACKET";
637
- case 4:
638
- return "BACKSLASH";
639
- case 5:
640
- return "EQUALS";
641
- case 6:
642
- return "XSS_AMP";
643
- case 7:
644
- return "XSS_LT";
645
- case 8:
646
- return "XSS_GT";
647
- case 9:
648
- return "XSS_D_QUOTE";
649
- case 10:
650
- return "XSS_S_QUOTE";
651
- }
558
+ switch (tokenType) {
559
+ case "STR": return "STR";
560
+ case "LINEBREAK": return "LINEBREAK";
561
+ case "L_BRACKET": return "L_BRACKET";
562
+ case "R_BRACKET": return "R_BRACKET";
563
+ case "BACKSLASH": return "BACKSLASH";
564
+ case "EQUALS": return "EQUALS";
565
+ case "XSS_AMP": return "XSS_AMP";
566
+ case "XSS_LT": return "XSS_LT";
567
+ case "XSS_GT": return "XSS_GT";
568
+ case "XSS_D_QUOTE": return "XSS_D_QUOTE";
569
+ case "XSS_S_QUOTE": return "XSS_S_QUOTE";
570
+ }
652
571
  }
653
572
  function isStringToken(tokenType) {
654
- switch (tokenType) {
655
- case 6:
656
- case 7:
657
- case 8:
658
- case 9:
659
- case 10:
660
- case 0: {
661
- return true;
662
- }
663
- }
664
- return false;
573
+ switch (tokenType) {
574
+ case "XSS_AMP":
575
+ case "XSS_LT":
576
+ case "XSS_GT":
577
+ case "XSS_D_QUOTE":
578
+ case "XSS_S_QUOTE":
579
+ case "STR": return true;
580
+ }
581
+ return false;
665
582
  }
666
- const symbolTable = {
667
- "\n": 1,
668
- "[": 2,
669
- "]": 3,
670
- "/": 4,
671
- "=": 5,
672
- "&": 6,
673
- "<": 7,
674
- ">": 8,
675
- '"': 9,
676
- "'": 10
677
- /* XSS_S_QUOTE */
583
+ var symbolTable = {
584
+ "\n": "LINEBREAK",
585
+ "[": "L_BRACKET",
586
+ "]": "R_BRACKET",
587
+ "/": "BACKSLASH",
588
+ "=": "EQUALS",
589
+ "&": "XSS_AMP",
590
+ "<": "XSS_LT",
591
+ ">": "XSS_GT",
592
+ "\"": "XSS_D_QUOTE",
593
+ "'": "XSS_S_QUOTE"
678
594
  };
679
- class Lexer {
680
- tokenize(input) {
681
- const tokens = new Array();
682
- const re = /\n|\[\/|\[(\w+|\*)|\]|=|&|<|>|'|"/g;
683
- let offset = 0;
684
- while (true) {
685
- const match = re.exec(input);
686
- if (!match) {
687
- break;
688
- }
689
- const length2 = match.index - offset;
690
- if (length2 > 0) {
691
- tokens.push({
692
- type: TokenType.STR,
693
- offset,
694
- length: length2
695
- });
696
- }
697
- offset = match.index;
698
- if (match[0] === "[/") {
699
- tokens.push({
700
- type: TokenType.L_BRACKET,
701
- offset,
702
- length: 1
703
- });
704
- offset += 1;
705
- tokens.push({
706
- type: TokenType.BACKSLASH,
707
- offset,
708
- length: 1
709
- });
710
- offset += 1;
711
- } else if (match[0].startsWith("[")) {
712
- tokens.push({
713
- type: TokenType.L_BRACKET,
714
- offset,
715
- length: 1
716
- });
717
- offset += 1;
718
- const length3 = match[0].length - 1;
719
- tokens.push({
720
- type: TokenType.STR,
721
- offset,
722
- length: length3
723
- });
724
- offset += length3;
725
- } else {
726
- tokens.push({
727
- type: symbolTable[match[0]] ?? TokenType.STR,
728
- offset,
729
- length: 1
730
- });
731
- offset += 1;
732
- }
733
- }
734
- const length = input.length - offset;
735
- if (length > 0) {
736
- tokens.push({
737
- type: TokenType.STR,
738
- offset,
739
- length
740
- });
741
- }
742
- return tokens;
743
- }
744
- }
595
+ //#endregion
596
+ //#region src/lexer/Lexer.ts
597
+ var Lexer = class {
598
+ tokenize(input) {
599
+ const tokens = new Array();
600
+ const re = /\n|\[\/|\[(\w+|\*)|\]|=|&|<|>|'|"/g;
601
+ let offset = 0;
602
+ while (true) {
603
+ const match = re.exec(input);
604
+ if (!match) break;
605
+ const length = match.index - offset;
606
+ if (length > 0) tokens.push({
607
+ type: "STR",
608
+ offset,
609
+ length
610
+ });
611
+ offset = match.index;
612
+ if (match[0] === "[/") {
613
+ tokens.push({
614
+ type: "L_BRACKET",
615
+ offset,
616
+ length: 1
617
+ });
618
+ offset += 1;
619
+ tokens.push({
620
+ type: "BACKSLASH",
621
+ offset,
622
+ length: 1
623
+ });
624
+ offset += 1;
625
+ } else if (match[0].startsWith("[")) {
626
+ tokens.push({
627
+ type: "L_BRACKET",
628
+ offset,
629
+ length: 1
630
+ });
631
+ offset += 1;
632
+ const length = match[0].length - 1;
633
+ tokens.push({
634
+ type: "STR",
635
+ offset,
636
+ length
637
+ });
638
+ offset += length;
639
+ } else {
640
+ tokens.push({
641
+ type: symbolTable[match[0]] ?? "STR",
642
+ offset,
643
+ length: 1
644
+ });
645
+ offset += 1;
646
+ }
647
+ }
648
+ const length = input.length - offset;
649
+ if (length > 0) tokens.push({
650
+ type: "STR",
651
+ offset,
652
+ length
653
+ });
654
+ return tokens;
655
+ }
656
+ };
657
+ //#endregion
658
+ //#region src/lexer/Token.ts
745
659
  function stringifyTokens(ogText, tokens) {
746
- let s = "";
747
- for (const token of tokens) {
748
- switch (token.type) {
749
- case TokenType.STR: {
750
- s += ogText.substring(token.offset, token.offset + token.length);
751
- break;
752
- }
753
- case TokenType.LINEBREAK: {
754
- s += "\n";
755
- break;
756
- }
757
- case TokenType.L_BRACKET: {
758
- s += "[";
759
- break;
760
- }
761
- case TokenType.R_BRACKET: {
762
- s += "]";
763
- break;
764
- }
765
- case TokenType.BACKSLASH: {
766
- s += "/";
767
- break;
768
- }
769
- case TokenType.EQUALS: {
770
- s += "=";
771
- break;
772
- }
773
- case TokenType.XSS_AMP: {
774
- s += "&amp;";
775
- break;
776
- }
777
- case TokenType.XSS_LT: {
778
- s += "&lt;";
779
- break;
780
- }
781
- case TokenType.XSS_GT: {
782
- s += "&gt;";
783
- break;
784
- }
785
- case TokenType.XSS_D_QUOTE: {
786
- s += "&quot;";
787
- break;
788
- }
789
- case TokenType.XSS_S_QUOTE: {
790
- s += "&#x27;";
791
- break;
792
- }
793
- }
794
- }
795
- return s;
796
- }
797
- class Parser {
798
- constructor(transforms = htmlTransforms) {
799
- __publicField(this, "tags");
800
- __publicField(this, "linebreakTerminatedTags");
801
- __publicField(this, "standaloneTags");
802
- this.tags = new Set(transforms.map((transform) => transform.name));
803
- this.linebreakTerminatedTags = new Set(transforms.filter((transform) => transform.isLinebreakTerminated).map((transform) => transform.name.toLowerCase()));
804
- this.standaloneTags = new Set(transforms.filter((transform) => transform.isStandalone).map((transform) => transform.name.toLowerCase()));
805
- }
806
- parse(ogText, tokens) {
807
- let idx = 0;
808
- const parseLabel = () => {
809
- const slice = tokens.slice(idx, idx + 1);
810
- const label = stringifyTokens(ogText, slice);
811
- idx += 1;
812
- return label.toLowerCase();
813
- };
814
- const parseText = (endOnQuotes = false, endOnSpace = false) => {
815
- const startIdx = idx;
816
- while (idx < tokens.length) {
817
- if (!isStringToken(tokens[idx].type)) {
818
- break;
819
- }
820
- if (endOnQuotes && (tokens[idx].type === TokenType.XSS_S_QUOTE || tokens[idx].type === TokenType.XSS_D_QUOTE)) {
821
- break;
822
- }
823
- if (endOnSpace && !endOnQuotes) {
824
- const origStr = stringifyTokens(ogText, [tokens[idx]]);
825
- const spaceIdx = origStr.indexOf(" ");
826
- if (spaceIdx >= 0) {
827
- const oldToken = {
828
- type: TokenType.STR,
829
- offset: tokens[idx].offset,
830
- length: spaceIdx
831
- };
832
- const newToken = {
833
- type: TokenType.STR,
834
- offset: tokens[idx].offset + spaceIdx,
835
- length: tokens[idx].length - spaceIdx
836
- };
837
- tokens.splice(idx + 0, 1, oldToken);
838
- tokens.splice(idx + 1, 0, newToken);
839
- idx += 1;
840
- break;
841
- }
842
- }
843
- idx += 1;
844
- }
845
- const slice = tokens.slice(startIdx, idx);
846
- const str = stringifyTokens(ogText, slice);
847
- return new TextNode(str);
848
- };
849
- const parseAttr = () => {
850
- if (idx + 1 >= tokens.length) {
851
- return null;
852
- }
853
- const attrNode = new AttrNode();
854
- if (tokens[idx].type === TokenType.EQUALS && isStringToken(tokens[idx + 1].type)) {
855
- idx += 1;
856
- const openedWithQuotes = tokens[idx].type === TokenType.XSS_S_QUOTE || tokens[idx].type === TokenType.XSS_D_QUOTE;
857
- if (openedWithQuotes) {
858
- idx += 1;
859
- }
860
- const valNode = parseText(openedWithQuotes, true);
861
- attrNode.addChild(valNode);
862
- if (openedWithQuotes) {
863
- if (tokens[idx].type !== TokenType.XSS_S_QUOTE && tokens[idx].type !== TokenType.XSS_D_QUOTE) {
864
- return null;
865
- }
866
- idx += 1;
867
- }
868
- } else if (isStringToken(tokens[idx].type) && tokens[idx + 1].type === TokenType.EQUALS && (idx + 2 < tokens.length && isStringToken(tokens[idx + 2].type))) {
869
- const keyNode = parseText();
870
- attrNode.addChild(keyNode);
871
- idx += 1;
872
- const openedWithQuotes = tokens[idx].type === TokenType.XSS_S_QUOTE || tokens[idx].type === TokenType.XSS_D_QUOTE;
873
- if (openedWithQuotes) {
874
- idx += 1;
875
- }
876
- const valNode = parseText(openedWithQuotes, true);
877
- if (openedWithQuotes) {
878
- if (tokens[idx].type !== TokenType.XSS_S_QUOTE && tokens[idx].type !== TokenType.XSS_D_QUOTE) {
879
- return null;
880
- }
881
- idx += 1;
882
- }
883
- attrNode.addChild(valNode);
884
- } else if (isStringToken(tokens[idx].type) && tokens[idx + 1].type !== TokenType.EQUALS) {
885
- const valNode = parseText();
886
- attrNode.addChild(valNode);
887
- } else {
888
- return null;
889
- }
890
- return attrNode;
891
- };
892
- const parseTag = () => {
893
- if (idx + 1 >= tokens.length) {
894
- return null;
895
- }
896
- if (tokens[idx].type !== TokenType.L_BRACKET) {
897
- return null;
898
- }
899
- if (isStringToken(tokens[idx + 1].type)) {
900
- const startIdx = idx;
901
- idx += 1;
902
- const labelText = parseLabel();
903
- if (!this.tags.has(labelText)) {
904
- return null;
905
- }
906
- const attrNodes = new Array();
907
- while (true) {
908
- const attrNode = parseAttr();
909
- if (attrNode === null) {
910
- break;
911
- }
912
- attrNodes.push(attrNode);
913
- }
914
- if (tokens[idx].type !== TokenType.R_BRACKET) {
915
- return null;
916
- }
917
- idx += 1;
918
- const slice = tokens.slice(startIdx, idx);
919
- const ogTag = stringifyTokens(ogText, slice);
920
- const startTagNode = new StartTagNode(labelText, ogTag, attrNodes);
921
- return startTagNode;
922
- }
923
- if (tokens[idx + 1].type === TokenType.BACKSLASH) {
924
- const startIdx = idx;
925
- idx += 1;
926
- idx += 1;
927
- const labelText = parseLabel();
928
- if (!this.tags.has(labelText)) {
929
- return null;
930
- }
931
- if (tokens[idx].type !== TokenType.R_BRACKET) {
932
- return null;
933
- }
934
- idx += 1;
935
- const slice = tokens.slice(startIdx, idx);
936
- const ogTag = stringifyTokens(ogText, slice);
937
- const endTagNode = new EndTagNode(labelText, ogTag);
938
- return endTagNode;
939
- }
940
- return null;
941
- };
942
- const parseRoot = () => {
943
- const root2 = new RootNode();
944
- while (idx < tokens.length) {
945
- if (tokens[idx].type === TokenType.L_BRACKET) {
946
- const startIdx = idx;
947
- const tagNode = parseTag();
948
- if (tagNode !== null) {
949
- root2.addChild(tagNode);
950
- } else {
951
- const invalidTokens = tokens.slice(startIdx, idx);
952
- const str = stringifyTokens(ogText, invalidTokens);
953
- const textNode = new TextNode(str);
954
- root2.addChild(textNode);
955
- }
956
- } else if (tokens[idx].type === TokenType.LINEBREAK) {
957
- idx += 1;
958
- root2.addChild(new LinebreakNode());
959
- } else {
960
- const startIdx = idx;
961
- while (idx < tokens.length && tokens[idx].type !== TokenType.L_BRACKET && tokens[idx].type !== TokenType.LINEBREAK) {
962
- idx += 1;
963
- }
964
- const slice = tokens.slice(startIdx, idx);
965
- const str = stringifyTokens(ogText, slice);
966
- root2.addChild(new TextNode(str));
967
- }
968
- }
969
- return root2;
970
- };
971
- let root = parseRoot();
972
- root = this.matchTagNodes(root);
973
- return root;
974
- }
975
- // ------------------------------------------------------------------------
976
- // Post Parsing Transforms
977
- // ------------------------------------------------------------------------
978
- matchTagNodes(rootNode) {
979
- const transformedRoot = new RootNode();
980
- for (let i = 0; i < rootNode.children.length; i++) {
981
- const child = rootNode.children[i];
982
- if (nodeIsType(child, AstNodeType.StartTagNode)) {
983
- const endTag = this.findMatchingEndTag(rootNode.children, i, child.tagName);
984
- const isStandalone = this.standaloneTags.has(child.tagName);
985
- if (endTag || isStandalone) {
986
- const tagNode = new TagNode(child, endTag == null ? void 0 : endTag.node);
987
- transformedRoot.addChild(tagNode);
988
- if (endTag) {
989
- const subRoot = new RootNode(rootNode.children.slice(i + 1, endTag.idx));
990
- i = endTag.idx;
991
- const transformedSubRoot = this.matchTagNodes(subRoot);
992
- tagNode.addChild(transformedSubRoot);
993
- }
994
- } else {
995
- transformedRoot.addChild(new TextNode(child.ogTag));
996
- }
997
- } else if (nodeIsType(child, AstNodeType.EndTagNode)) {
998
- transformedRoot.addChild(new TextNode(child.ogTag));
999
- } else if (nodeIsType(child, AstNodeType.TextNode)) {
1000
- transformedRoot.addChild(child);
1001
- } else if (nodeIsType(child, AstNodeType.LinebreakNode)) {
1002
- transformedRoot.addChild(child);
1003
- } else {
1004
- throw new Error("Unexpected child of RootNode");
1005
- }
1006
- }
1007
- return transformedRoot;
1008
- }
1009
- findMatchingEndTag(siblings, startIdx, tagName) {
1010
- if (this.standaloneTags.has(tagName)) {
1011
- return null;
1012
- }
1013
- for (let i = startIdx; i < siblings.length; i++) {
1014
- const sibling = siblings[i];
1015
- const isEndTag = nodeIsType(sibling, AstNodeType.LinebreakNode) && this.linebreakTerminatedTags.has(tagName) || nodeIsType(sibling, AstNodeType.EndTagNode) && sibling.tagName === tagName;
1016
- if (isEndTag) {
1017
- return {
1018
- idx: i,
1019
- node: sibling
1020
- };
1021
- }
1022
- }
1023
- return null;
1024
- }
660
+ let s = "";
661
+ for (const token of tokens) switch (token.type) {
662
+ case "STR":
663
+ s += ogText.substring(token.offset, token.offset + token.length);
664
+ break;
665
+ case "LINEBREAK":
666
+ s += "\n";
667
+ break;
668
+ case "L_BRACKET":
669
+ s += "[";
670
+ break;
671
+ case "R_BRACKET":
672
+ s += "]";
673
+ break;
674
+ case "BACKSLASH":
675
+ s += "/";
676
+ break;
677
+ case "EQUALS":
678
+ s += "=";
679
+ break;
680
+ case "XSS_AMP":
681
+ s += "&amp;";
682
+ break;
683
+ case "XSS_LT":
684
+ s += "&lt;";
685
+ break;
686
+ case "XSS_GT":
687
+ s += "&gt;";
688
+ break;
689
+ case "XSS_D_QUOTE":
690
+ s += "&quot;";
691
+ break;
692
+ case "XSS_S_QUOTE":
693
+ s += "&#x27;";
694
+ break;
695
+ }
696
+ return s;
1025
697
  }
698
+ //#endregion
699
+ //#region src/parser/Parser.ts
700
+ var Parser = class {
701
+ tags;
702
+ linebreakTerminatedTags;
703
+ standaloneTags;
704
+ constructor(transforms = htmlTransforms) {
705
+ this.tags = new Set(transforms.map((transform) => transform.name));
706
+ this.linebreakTerminatedTags = new Set(transforms.filter((transform) => transform.isLinebreakTerminated).map((transform) => transform.name.toLowerCase()));
707
+ this.standaloneTags = new Set(transforms.filter((transform) => transform.isStandalone).map((transform) => transform.name.toLowerCase()));
708
+ }
709
+ parse(ogText, tokens) {
710
+ let idx = 0;
711
+ const parseLabel = () => {
712
+ const label = stringifyTokens(ogText, tokens.slice(idx, idx + 1));
713
+ idx += 1;
714
+ return label.toLowerCase();
715
+ };
716
+ const parseText = (endOnQuotes = false, endOnSpace = false) => {
717
+ const startIdx = idx;
718
+ while (idx < tokens.length) {
719
+ if (!isStringToken(tokens[idx].type)) break;
720
+ if (endOnQuotes && (tokens[idx].type === "XSS_S_QUOTE" || tokens[idx].type === "XSS_D_QUOTE")) break;
721
+ /**
722
+ * SPECIAL CASE:
723
+ * If we encounter a space, then we must split the current token into 2 tokens and only consume the first part
724
+ *
725
+ * a b -> a b
726
+ * | | |
727
+ * | | idx (new)
728
+ * | |
729
+ * idx consumed
730
+ *
731
+ * Note: We only handle endOnSpace special case when we don't expect the current text to endOnQuotes
732
+ * If it endOnQuotes, then it implies that it opened with quotes (and thus we need an enclosing/matching quote)
733
+ */
734
+ if (endOnSpace && !endOnQuotes) {
735
+ const spaceIdx = stringifyTokens(ogText, [tokens[idx]]).indexOf(" ");
736
+ if (spaceIdx >= 0) {
737
+ const oldToken = {
738
+ type: "STR",
739
+ offset: tokens[idx].offset,
740
+ length: spaceIdx
741
+ };
742
+ const newToken = {
743
+ type: "STR",
744
+ offset: tokens[idx].offset + spaceIdx,
745
+ length: tokens[idx].length - spaceIdx
746
+ };
747
+ tokens.splice(idx + 0, 1, oldToken);
748
+ tokens.splice(idx + 1, 0, newToken);
749
+ idx += 1;
750
+ break;
751
+ }
752
+ }
753
+ idx += 1;
754
+ }
755
+ return new TextNode(stringifyTokens(ogText, tokens.slice(startIdx, idx)));
756
+ };
757
+ const parseAttr = () => {
758
+ if (idx + 1 >= tokens.length) return null;
759
+ const attrNode = new AttrNode();
760
+ if (tokens[idx].type === "EQUALS" && isStringToken(tokens[idx + 1].type)) {
761
+ idx += 1;
762
+ const openedWithQuotes = tokens[idx].type === "XSS_S_QUOTE" || tokens[idx].type === "XSS_D_QUOTE";
763
+ if (openedWithQuotes) idx += 1;
764
+ const valNode = parseText(openedWithQuotes, true);
765
+ attrNode.addChild(valNode);
766
+ if (openedWithQuotes) {
767
+ if (tokens[idx].type !== "XSS_S_QUOTE" && tokens[idx].type !== "XSS_D_QUOTE") return null;
768
+ idx += 1;
769
+ }
770
+ } else if (isStringToken(tokens[idx].type) && tokens[idx + 1].type === "EQUALS" && idx + 2 < tokens.length && isStringToken(tokens[idx + 2].type)) {
771
+ const keyNode = parseText();
772
+ attrNode.addChild(keyNode);
773
+ idx += 1;
774
+ const openedWithQuotes = tokens[idx].type === "XSS_S_QUOTE" || tokens[idx].type === "XSS_D_QUOTE";
775
+ if (openedWithQuotes) idx += 1;
776
+ const valNode = parseText(openedWithQuotes, true);
777
+ if (openedWithQuotes) {
778
+ if (tokens[idx].type !== "XSS_S_QUOTE" && tokens[idx].type !== "XSS_D_QUOTE") return null;
779
+ idx += 1;
780
+ }
781
+ attrNode.addChild(valNode);
782
+ } else if (isStringToken(tokens[idx].type) && tokens[idx + 1].type !== "EQUALS") {
783
+ const valNode = parseText();
784
+ attrNode.addChild(valNode);
785
+ } else return null;
786
+ return attrNode;
787
+ };
788
+ const parseTag = () => {
789
+ if (idx + 1 >= tokens.length) return null;
790
+ if (tokens[idx].type !== "L_BRACKET") return null;
791
+ if (isStringToken(tokens[idx + 1].type)) {
792
+ const startIdx = idx;
793
+ idx += 1;
794
+ const labelText = parseLabel();
795
+ if (!this.tags.has(labelText)) return null;
796
+ const attrNodes = new Array();
797
+ while (true) {
798
+ const attrNode = parseAttr();
799
+ if (attrNode === null) break;
800
+ attrNodes.push(attrNode);
801
+ }
802
+ if (tokens[idx].type !== "R_BRACKET") return null;
803
+ idx += 1;
804
+ return new StartTagNode(labelText, stringifyTokens(ogText, tokens.slice(startIdx, idx)), attrNodes);
805
+ }
806
+ if (tokens[idx + 1].type === "BACKSLASH") {
807
+ const startIdx = idx;
808
+ idx += 1;
809
+ idx += 1;
810
+ const labelText = parseLabel();
811
+ if (!this.tags.has(labelText)) return null;
812
+ if (tokens[idx].type !== "R_BRACKET") return null;
813
+ idx += 1;
814
+ return new EndTagNode(labelText, stringifyTokens(ogText, tokens.slice(startIdx, idx)));
815
+ }
816
+ return null;
817
+ };
818
+ const parseRoot = () => {
819
+ const root = new RootNode();
820
+ while (idx < tokens.length) if (tokens[idx].type === "L_BRACKET") {
821
+ const startIdx = idx;
822
+ const tagNode = parseTag();
823
+ if (tagNode !== null) root.addChild(tagNode);
824
+ else {
825
+ const textNode = new TextNode(stringifyTokens(ogText, tokens.slice(startIdx, idx)));
826
+ root.addChild(textNode);
827
+ }
828
+ } else if (tokens[idx].type === "LINEBREAK") {
829
+ idx += 1;
830
+ root.addChild(new LinebreakNode());
831
+ } else {
832
+ const startIdx = idx;
833
+ while (idx < tokens.length && tokens[idx].type !== "L_BRACKET" && tokens[idx].type !== "LINEBREAK") idx += 1;
834
+ const str = stringifyTokens(ogText, tokens.slice(startIdx, idx));
835
+ root.addChild(new TextNode(str));
836
+ }
837
+ return root;
838
+ };
839
+ let root = parseRoot();
840
+ root = this.matchTagNodes(root);
841
+ return root;
842
+ }
843
+ matchTagNodes(rootNode) {
844
+ const transformedRoot = new RootNode();
845
+ for (let i = 0; i < rootNode.children.length; i++) {
846
+ const child = rootNode.children[i];
847
+ if (nodeIsType(child, "StartTagNode")) {
848
+ const endTag = this.findMatchingEndTag(rootNode.children, i, child.tagName);
849
+ const isStandalone = this.standaloneTags.has(child.tagName);
850
+ if (endTag || isStandalone) {
851
+ const tagNode = new TagNode(child, endTag?.node);
852
+ transformedRoot.addChild(tagNode);
853
+ if (endTag) {
854
+ const subRoot = new RootNode(rootNode.children.slice(i + 1, endTag.idx));
855
+ i = endTag.idx;
856
+ const transformedSubRoot = this.matchTagNodes(subRoot);
857
+ tagNode.addChild(transformedSubRoot);
858
+ }
859
+ } else transformedRoot.addChild(new TextNode(child.ogTag));
860
+ } else if (nodeIsType(child, "EndTagNode")) transformedRoot.addChild(new TextNode(child.ogTag));
861
+ else if (nodeIsType(child, "TextNode")) transformedRoot.addChild(child);
862
+ else if (nodeIsType(child, "LinebreakNode")) transformedRoot.addChild(child);
863
+ else throw new Error("Unexpected child of RootNode");
864
+ }
865
+ return transformedRoot;
866
+ }
867
+ findMatchingEndTag(siblings, startIdx, tagName) {
868
+ if (this.standaloneTags.has(tagName)) return null;
869
+ for (let i = startIdx; i < siblings.length; i++) {
870
+ const sibling = siblings[i];
871
+ if (nodeIsType(sibling, "LinebreakNode") && this.linebreakTerminatedTags.has(tagName) || nodeIsType(sibling, "EndTagNode") && sibling.tagName === tagName) return {
872
+ idx: i,
873
+ node: sibling
874
+ };
875
+ }
876
+ return null;
877
+ }
878
+ };
879
+ //#endregion
880
+ //#region src/generateHtml.ts
1026
881
  function generateHtml(input, transforms = htmlTransforms) {
1027
- const lexer = new Lexer();
1028
- const tokens = lexer.tokenize(input);
1029
- const parser = new Parser(transforms);
1030
- const root = parser.parse(input, tokens);
1031
- const generator = new Generator(transforms);
1032
- return generator.generate(root);
882
+ const tokens = new Lexer().tokenize(input);
883
+ const root = new Parser(transforms).parse(input, tokens);
884
+ return new Generator(transforms).generate(root);
1033
885
  }
1034
- export {
1035
- AstNode,
1036
- AstNodeType,
1037
- AttrNode,
1038
- EndTagNode,
1039
- Generator,
1040
- Lexer,
1041
- LinebreakNode,
1042
- Parser,
1043
- RootNode,
1044
- StartTagNode,
1045
- TagNode,
1046
- TextNode,
1047
- TokenType,
1048
- generateHtml,
1049
- getTagImmediateAttrVal,
1050
- getTagImmediateText,
1051
- getWidthHeightAttr,
1052
- htmlTransforms,
1053
- isDangerousUrl,
1054
- isOrderedList,
1055
- isStringToken,
1056
- nodeIsType,
1057
- stringifyTokens,
1058
- symbolTable,
1059
- tokenTypeToString
1060
- };
1061
- //# sourceMappingURL=index.js.map
886
+ //#endregion
887
+ export { AstNode, AttrNode, EndTagNode, Generator, Lexer, LinebreakNode, Parser, RootNode, StartTagNode, TagNode, TextNode, generateHtml, getTagImmediateAttrVal, getTagImmediateText, getWidthHeightAttr, htmlTransforms, isDangerousUrl, isOrderedList, isStringToken, nodeIsType, stringifyTokens, symbolTable, tokenTypeToString };
888
+
889
+ //# sourceMappingURL=index.js.map