@m2c2kit/build-helpers 0.3.2 → 0.3.4

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.
package/dist/index.js CHANGED
@@ -22,6 +22,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
22
22
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23
23
  PERFORMANCE OF THIS SOFTWARE.
24
24
  ***************************************************************************** */
25
+ /* global Reflect, Promise */
26
+
25
27
 
26
28
  function __awaiter(thisArg, _arguments, P, generator) {
27
29
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -51,6 +53,7 @@ var xmlDecodeTree = new Uint16Array(
51
53
  var _a;
52
54
  const decodeMap = new Map([
53
55
  [0, 65533],
56
+ // C1 Unicode control character reference replacements
54
57
  [128, 8364],
55
58
  [130, 8218],
56
59
  [131, 402],
@@ -79,6 +82,9 @@ const decodeMap = new Map([
79
82
  [158, 382],
80
83
  [159, 376],
81
84
  ]);
85
+ /**
86
+ * Polyfill for `String.fromCodePoint`. It is used to create a string from a Unicode code point.
87
+ */
82
88
  const fromCodePoint =
83
89
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, node/no-unsupported-features/es-builtins
84
90
  (_a = String.fromCodePoint) !== null && _a !== void 0 ? _a : function (codePoint) {
@@ -91,6 +97,11 @@ const fromCodePoint =
91
97
  output += String.fromCharCode(codePoint);
92
98
  return output;
93
99
  };
100
+ /**
101
+ * Replace the given code point with a replacement character if it is a
102
+ * surrogate or is outside the valid range. Otherwise return the code
103
+ * point unchanged.
104
+ */
94
105
  function replaceCodePoint(codePoint) {
95
106
  var _a;
96
107
  if ((codePoint >= 0xd800 && codePoint <= 0xdfff) || codePoint > 0x10ffff) {
@@ -103,20 +114,419 @@ var CharCodes$1;
103
114
  (function (CharCodes) {
104
115
  CharCodes[CharCodes["NUM"] = 35] = "NUM";
105
116
  CharCodes[CharCodes["SEMI"] = 59] = "SEMI";
117
+ CharCodes[CharCodes["EQUALS"] = 61] = "EQUALS";
106
118
  CharCodes[CharCodes["ZERO"] = 48] = "ZERO";
107
119
  CharCodes[CharCodes["NINE"] = 57] = "NINE";
108
120
  CharCodes[CharCodes["LOWER_A"] = 97] = "LOWER_A";
109
121
  CharCodes[CharCodes["LOWER_F"] = 102] = "LOWER_F";
110
122
  CharCodes[CharCodes["LOWER_X"] = 120] = "LOWER_X";
111
- /** Bit that needs to be set to convert an upper case ASCII character to lower case */
112
- CharCodes[CharCodes["To_LOWER_BIT"] = 32] = "To_LOWER_BIT";
123
+ CharCodes[CharCodes["LOWER_Z"] = 122] = "LOWER_Z";
124
+ CharCodes[CharCodes["UPPER_A"] = 65] = "UPPER_A";
125
+ CharCodes[CharCodes["UPPER_F"] = 70] = "UPPER_F";
126
+ CharCodes[CharCodes["UPPER_Z"] = 90] = "UPPER_Z";
113
127
  })(CharCodes$1 || (CharCodes$1 = {}));
128
+ /** Bit that needs to be set to convert an upper case ASCII character to lower case */
129
+ const TO_LOWER_BIT = 0b100000;
114
130
  var BinTrieFlags;
115
131
  (function (BinTrieFlags) {
116
132
  BinTrieFlags[BinTrieFlags["VALUE_LENGTH"] = 49152] = "VALUE_LENGTH";
117
133
  BinTrieFlags[BinTrieFlags["BRANCH_LENGTH"] = 16256] = "BRANCH_LENGTH";
118
134
  BinTrieFlags[BinTrieFlags["JUMP_TABLE"] = 127] = "JUMP_TABLE";
119
135
  })(BinTrieFlags || (BinTrieFlags = {}));
136
+ function isNumber(code) {
137
+ return code >= CharCodes$1.ZERO && code <= CharCodes$1.NINE;
138
+ }
139
+ function isHexadecimalCharacter(code) {
140
+ return ((code >= CharCodes$1.UPPER_A && code <= CharCodes$1.UPPER_F) ||
141
+ (code >= CharCodes$1.LOWER_A && code <= CharCodes$1.LOWER_F));
142
+ }
143
+ function isAsciiAlphaNumeric(code) {
144
+ return ((code >= CharCodes$1.UPPER_A && code <= CharCodes$1.UPPER_Z) ||
145
+ (code >= CharCodes$1.LOWER_A && code <= CharCodes$1.LOWER_Z) ||
146
+ isNumber(code));
147
+ }
148
+ /**
149
+ * Checks if the given character is a valid end character for an entity in an attribute.
150
+ *
151
+ * Attribute values that aren't terminated properly aren't parsed, and shouldn't lead to a parser error.
152
+ * See the example in https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
153
+ */
154
+ function isEntityInAttributeInvalidEnd(code) {
155
+ return code === CharCodes$1.EQUALS || isAsciiAlphaNumeric(code);
156
+ }
157
+ var EntityDecoderState;
158
+ (function (EntityDecoderState) {
159
+ EntityDecoderState[EntityDecoderState["EntityStart"] = 0] = "EntityStart";
160
+ EntityDecoderState[EntityDecoderState["NumericStart"] = 1] = "NumericStart";
161
+ EntityDecoderState[EntityDecoderState["NumericDecimal"] = 2] = "NumericDecimal";
162
+ EntityDecoderState[EntityDecoderState["NumericHex"] = 3] = "NumericHex";
163
+ EntityDecoderState[EntityDecoderState["NamedEntity"] = 4] = "NamedEntity";
164
+ })(EntityDecoderState || (EntityDecoderState = {}));
165
+ var DecodingMode;
166
+ (function (DecodingMode) {
167
+ /** Entities in text nodes that can end with any character. */
168
+ DecodingMode[DecodingMode["Legacy"] = 0] = "Legacy";
169
+ /** Only allow entities terminated with a semicolon. */
170
+ DecodingMode[DecodingMode["Strict"] = 1] = "Strict";
171
+ /** Entities in attributes have limitations on ending characters. */
172
+ DecodingMode[DecodingMode["Attribute"] = 2] = "Attribute";
173
+ })(DecodingMode || (DecodingMode = {}));
174
+ /**
175
+ * Token decoder with support of writing partial entities.
176
+ */
177
+ class EntityDecoder {
178
+ constructor(
179
+ /** The tree used to decode entities. */
180
+ decodeTree,
181
+ /**
182
+ * The function that is called when a codepoint is decoded.
183
+ *
184
+ * For multi-byte named entities, this will be called multiple times,
185
+ * with the second codepoint, and the same `consumed` value.
186
+ *
187
+ * @param codepoint The decoded codepoint.
188
+ * @param consumed The number of bytes consumed by the decoder.
189
+ */
190
+ emitCodePoint,
191
+ /** An object that is used to produce errors. */
192
+ errors) {
193
+ this.decodeTree = decodeTree;
194
+ this.emitCodePoint = emitCodePoint;
195
+ this.errors = errors;
196
+ /** The current state of the decoder. */
197
+ this.state = EntityDecoderState.EntityStart;
198
+ /** Characters that were consumed while parsing an entity. */
199
+ this.consumed = 1;
200
+ /**
201
+ * The result of the entity.
202
+ *
203
+ * Either the result index of a numeric entity, or the codepoint of a
204
+ * numeric entity.
205
+ */
206
+ this.result = 0;
207
+ /** The current index in the decode tree. */
208
+ this.treeIndex = 0;
209
+ /** The number of characters that were consumed in excess. */
210
+ this.excess = 1;
211
+ /** The mode in which the decoder is operating. */
212
+ this.decodeMode = DecodingMode.Strict;
213
+ }
214
+ /** Resets the instance to make it reusable. */
215
+ startEntity(decodeMode) {
216
+ this.decodeMode = decodeMode;
217
+ this.state = EntityDecoderState.EntityStart;
218
+ this.result = 0;
219
+ this.treeIndex = 0;
220
+ this.excess = 1;
221
+ this.consumed = 1;
222
+ }
223
+ /**
224
+ * Write an entity to the decoder. This can be called multiple times with partial entities.
225
+ * If the entity is incomplete, the decoder will return -1.
226
+ *
227
+ * Mirrors the implementation of `getDecoder`, but with the ability to stop decoding if the
228
+ * entity is incomplete, and resume when the next string is written.
229
+ *
230
+ * @param string The string containing the entity (or a continuation of the entity).
231
+ * @param offset The offset at which the entity begins. Should be 0 if this is not the first call.
232
+ * @returns The number of characters that were consumed, or -1 if the entity is incomplete.
233
+ */
234
+ write(str, offset) {
235
+ switch (this.state) {
236
+ case EntityDecoderState.EntityStart: {
237
+ if (str.charCodeAt(offset) === CharCodes$1.NUM) {
238
+ this.state = EntityDecoderState.NumericStart;
239
+ this.consumed += 1;
240
+ return this.stateNumericStart(str, offset + 1);
241
+ }
242
+ this.state = EntityDecoderState.NamedEntity;
243
+ return this.stateNamedEntity(str, offset);
244
+ }
245
+ case EntityDecoderState.NumericStart: {
246
+ return this.stateNumericStart(str, offset);
247
+ }
248
+ case EntityDecoderState.NumericDecimal: {
249
+ return this.stateNumericDecimal(str, offset);
250
+ }
251
+ case EntityDecoderState.NumericHex: {
252
+ return this.stateNumericHex(str, offset);
253
+ }
254
+ case EntityDecoderState.NamedEntity: {
255
+ return this.stateNamedEntity(str, offset);
256
+ }
257
+ }
258
+ }
259
+ /**
260
+ * Switches between the numeric decimal and hexadecimal states.
261
+ *
262
+ * Equivalent to the `Numeric character reference state` in the HTML spec.
263
+ *
264
+ * @param str The string containing the entity (or a continuation of the entity).
265
+ * @param offset The current offset.
266
+ * @returns The number of characters that were consumed, or -1 if the entity is incomplete.
267
+ */
268
+ stateNumericStart(str, offset) {
269
+ if (offset >= str.length) {
270
+ return -1;
271
+ }
272
+ if ((str.charCodeAt(offset) | TO_LOWER_BIT) === CharCodes$1.LOWER_X) {
273
+ this.state = EntityDecoderState.NumericHex;
274
+ this.consumed += 1;
275
+ return this.stateNumericHex(str, offset + 1);
276
+ }
277
+ this.state = EntityDecoderState.NumericDecimal;
278
+ return this.stateNumericDecimal(str, offset);
279
+ }
280
+ addToNumericResult(str, start, end, base) {
281
+ if (start !== end) {
282
+ const digitCount = end - start;
283
+ this.result =
284
+ this.result * Math.pow(base, digitCount) +
285
+ parseInt(str.substr(start, digitCount), base);
286
+ this.consumed += digitCount;
287
+ }
288
+ }
289
+ /**
290
+ * Parses a hexadecimal numeric entity.
291
+ *
292
+ * Equivalent to the `Hexademical character reference state` in the HTML spec.
293
+ *
294
+ * @param str The string containing the entity (or a continuation of the entity).
295
+ * @param offset The current offset.
296
+ * @returns The number of characters that were consumed, or -1 if the entity is incomplete.
297
+ */
298
+ stateNumericHex(str, offset) {
299
+ const startIdx = offset;
300
+ while (offset < str.length) {
301
+ const char = str.charCodeAt(offset);
302
+ if (isNumber(char) || isHexadecimalCharacter(char)) {
303
+ offset += 1;
304
+ }
305
+ else {
306
+ this.addToNumericResult(str, startIdx, offset, 16);
307
+ return this.emitNumericEntity(char, 3);
308
+ }
309
+ }
310
+ this.addToNumericResult(str, startIdx, offset, 16);
311
+ return -1;
312
+ }
313
+ /**
314
+ * Parses a decimal numeric entity.
315
+ *
316
+ * Equivalent to the `Decimal character reference state` in the HTML spec.
317
+ *
318
+ * @param str The string containing the entity (or a continuation of the entity).
319
+ * @param offset The current offset.
320
+ * @returns The number of characters that were consumed, or -1 if the entity is incomplete.
321
+ */
322
+ stateNumericDecimal(str, offset) {
323
+ const startIdx = offset;
324
+ while (offset < str.length) {
325
+ const char = str.charCodeAt(offset);
326
+ if (isNumber(char)) {
327
+ offset += 1;
328
+ }
329
+ else {
330
+ this.addToNumericResult(str, startIdx, offset, 10);
331
+ return this.emitNumericEntity(char, 2);
332
+ }
333
+ }
334
+ this.addToNumericResult(str, startIdx, offset, 10);
335
+ return -1;
336
+ }
337
+ /**
338
+ * Validate and emit a numeric entity.
339
+ *
340
+ * Implements the logic from the `Hexademical character reference start
341
+ * state` and `Numeric character reference end state` in the HTML spec.
342
+ *
343
+ * @param lastCp The last code point of the entity. Used to see if the
344
+ * entity was terminated with a semicolon.
345
+ * @param expectedLength The minimum number of characters that should be
346
+ * consumed. Used to validate that at least one digit
347
+ * was consumed.
348
+ * @returns The number of characters that were consumed.
349
+ */
350
+ emitNumericEntity(lastCp, expectedLength) {
351
+ var _a;
352
+ // Ensure we consumed at least one digit.
353
+ if (this.consumed <= expectedLength) {
354
+ (_a = this.errors) === null || _a === void 0 ? void 0 : _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
355
+ return 0;
356
+ }
357
+ // Figure out if this is a legit end of the entity
358
+ if (lastCp === CharCodes$1.SEMI) {
359
+ this.consumed += 1;
360
+ }
361
+ else if (this.decodeMode === DecodingMode.Strict) {
362
+ return 0;
363
+ }
364
+ this.emitCodePoint(replaceCodePoint(this.result), this.consumed);
365
+ if (this.errors) {
366
+ if (lastCp !== CharCodes$1.SEMI) {
367
+ this.errors.missingSemicolonAfterCharacterReference();
368
+ }
369
+ this.errors.validateNumericCharacterReference(this.result);
370
+ }
371
+ return this.consumed;
372
+ }
373
+ /**
374
+ * Parses a named entity.
375
+ *
376
+ * Equivalent to the `Named character reference state` in the HTML spec.
377
+ *
378
+ * @param str The string containing the entity (or a continuation of the entity).
379
+ * @param offset The current offset.
380
+ * @returns The number of characters that were consumed, or -1 if the entity is incomplete.
381
+ */
382
+ stateNamedEntity(str, offset) {
383
+ const { decodeTree } = this;
384
+ let current = decodeTree[this.treeIndex];
385
+ // The mask is the number of bytes of the value, including the current byte.
386
+ let valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
387
+ for (; offset < str.length; offset++, this.excess++) {
388
+ const char = str.charCodeAt(offset);
389
+ this.treeIndex = determineBranch(decodeTree, current, this.treeIndex + Math.max(1, valueLength), char);
390
+ if (this.treeIndex < 0) {
391
+ return this.result === 0 ||
392
+ // If we are parsing an attribute
393
+ (this.decodeMode === DecodingMode.Attribute &&
394
+ // We shouldn't have consumed any characters after the entity,
395
+ (valueLength === 0 ||
396
+ // And there should be no invalid characters.
397
+ isEntityInAttributeInvalidEnd(char)))
398
+ ? 0
399
+ : this.emitNotTerminatedNamedEntity();
400
+ }
401
+ current = decodeTree[this.treeIndex];
402
+ valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
403
+ // If the branch is a value, store it and continue
404
+ if (valueLength !== 0) {
405
+ // If the entity is terminated by a semicolon, we are done.
406
+ if (char === CharCodes$1.SEMI) {
407
+ return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
408
+ }
409
+ // If we encounter a non-terminated (legacy) entity while parsing strictly, then ignore it.
410
+ if (this.decodeMode !== DecodingMode.Strict) {
411
+ this.result = this.treeIndex;
412
+ this.consumed += this.excess;
413
+ this.excess = 0;
414
+ }
415
+ }
416
+ }
417
+ return -1;
418
+ }
419
+ /**
420
+ * Emit a named entity that was not terminated with a semicolon.
421
+ *
422
+ * @returns The number of characters consumed.
423
+ */
424
+ emitNotTerminatedNamedEntity() {
425
+ var _a;
426
+ const { result, decodeTree } = this;
427
+ const valueLength = (decodeTree[result] & BinTrieFlags.VALUE_LENGTH) >> 14;
428
+ this.emitNamedEntityData(result, valueLength, this.consumed);
429
+ (_a = this.errors) === null || _a === void 0 ? void 0 : _a.missingSemicolonAfterCharacterReference();
430
+ return this.consumed;
431
+ }
432
+ /**
433
+ * Emit a named entity.
434
+ *
435
+ * @param result The index of the entity in the decode tree.
436
+ * @param valueLength The number of bytes in the entity.
437
+ * @param consumed The number of characters consumed.
438
+ *
439
+ * @returns The number of characters consumed.
440
+ */
441
+ emitNamedEntityData(result, valueLength, consumed) {
442
+ const { decodeTree } = this;
443
+ this.emitCodePoint(valueLength === 1
444
+ ? decodeTree[result] & ~BinTrieFlags.VALUE_LENGTH
445
+ : decodeTree[result + 1], consumed);
446
+ if (valueLength === 3) {
447
+ // For multi-byte values, we need to emit the second byte.
448
+ this.emitCodePoint(decodeTree[result + 2], consumed);
449
+ }
450
+ return consumed;
451
+ }
452
+ /**
453
+ * Signal to the parser that the end of the input was reached.
454
+ *
455
+ * Remaining data will be emitted and relevant errors will be produced.
456
+ *
457
+ * @returns The number of characters consumed.
458
+ */
459
+ end() {
460
+ var _a;
461
+ switch (this.state) {
462
+ case EntityDecoderState.NamedEntity: {
463
+ // Emit a named entity if we have one.
464
+ return this.result !== 0 &&
465
+ (this.decodeMode !== DecodingMode.Attribute ||
466
+ this.result === this.treeIndex)
467
+ ? this.emitNotTerminatedNamedEntity()
468
+ : 0;
469
+ }
470
+ // Otherwise, emit a numeric entity if we have one.
471
+ case EntityDecoderState.NumericDecimal: {
472
+ return this.emitNumericEntity(0, 2);
473
+ }
474
+ case EntityDecoderState.NumericHex: {
475
+ return this.emitNumericEntity(0, 3);
476
+ }
477
+ case EntityDecoderState.NumericStart: {
478
+ (_a = this.errors) === null || _a === void 0 ? void 0 : _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
479
+ return 0;
480
+ }
481
+ case EntityDecoderState.EntityStart: {
482
+ // Return 0 if we have no entity.
483
+ return 0;
484
+ }
485
+ }
486
+ }
487
+ }
488
+ /**
489
+ * Creates a function that decodes entities in a string.
490
+ *
491
+ * @param decodeTree The decode tree.
492
+ * @returns A function that decodes entities in a string.
493
+ */
494
+ function getDecoder(decodeTree) {
495
+ let ret = "";
496
+ const decoder = new EntityDecoder(decodeTree, (str) => (ret += fromCodePoint(str)));
497
+ return function decodeWithTrie(str, decodeMode) {
498
+ let lastIndex = 0;
499
+ let offset = 0;
500
+ while ((offset = str.indexOf("&", offset)) >= 0) {
501
+ ret += str.slice(lastIndex, offset);
502
+ decoder.startEntity(decodeMode);
503
+ const len = decoder.write(str,
504
+ // Skip the "&"
505
+ offset + 1);
506
+ if (len < 0) {
507
+ lastIndex = offset + decoder.end();
508
+ break;
509
+ }
510
+ lastIndex = offset + len;
511
+ // If `len` is 0, skip the current `&` and continue.
512
+ offset = len === 0 ? lastIndex + 1 : lastIndex;
513
+ }
514
+ const result = ret + str.slice(lastIndex);
515
+ // Make sure we don't keep a reference to the final string.
516
+ ret = "";
517
+ return result;
518
+ };
519
+ }
520
+ /**
521
+ * Determines the branch of the current node that is taken given the current
522
+ * character. This function is used to traverse the trie.
523
+ *
524
+ * @param decodeTree The trie.
525
+ * @param current The current node.
526
+ * @param nodeIdx The index right after the current node and its value.
527
+ * @param char The current character.
528
+ * @returns The index of the next node, or -1 if no branch is taken.
529
+ */
120
530
  function determineBranch(decodeTree, current, nodeIdx, char) {
121
531
  const branchCount = (current & BinTrieFlags.BRANCH_LENGTH) >> 7;
122
532
  const jumpOffset = current & BinTrieFlags.JUMP_TABLE;
@@ -150,6 +560,8 @@ function determineBranch(decodeTree, current, nodeIdx, char) {
150
560
  }
151
561
  return -1;
152
562
  }
563
+ getDecoder(htmlDecodeTree);
564
+ getDecoder(xmlDecodeTree);
153
565
 
154
566
  var CharCodes;
155
567
  (function (CharCodes) {
@@ -213,11 +625,7 @@ var State$1;
213
625
  State[State["BeforeSpecialS"] = 22] = "BeforeSpecialS";
214
626
  State[State["SpecialStartSequence"] = 23] = "SpecialStartSequence";
215
627
  State[State["InSpecialTag"] = 24] = "InSpecialTag";
216
- State[State["BeforeEntity"] = 25] = "BeforeEntity";
217
- State[State["BeforeNumericEntity"] = 26] = "BeforeNumericEntity";
218
- State[State["InNamedEntity"] = 27] = "InNamedEntity";
219
- State[State["InNumericEntity"] = 28] = "InNumericEntity";
220
- State[State["InHexEntity"] = 29] = "InHexEntity";
628
+ State[State["InEntity"] = 25] = "InEntity";
221
629
  })(State$1 || (State$1 = {}));
222
630
  function isWhitespace$1(c) {
223
631
  return (c === CharCodes.Space ||
@@ -229,17 +637,10 @@ function isWhitespace$1(c) {
229
637
  function isEndOfTagSection(c) {
230
638
  return c === CharCodes.Slash || c === CharCodes.Gt || isWhitespace$1(c);
231
639
  }
232
- function isNumber(c) {
233
- return c >= CharCodes.Zero && c <= CharCodes.Nine;
234
- }
235
640
  function isASCIIAlpha(c) {
236
641
  return ((c >= CharCodes.LowerA && c <= CharCodes.LowerZ) ||
237
642
  (c >= CharCodes.UpperA && c <= CharCodes.UpperZ));
238
643
  }
239
- function isHexDigit$1(c) {
240
- return ((c >= CharCodes.UpperA && c <= CharCodes.UpperF) ||
241
- (c >= CharCodes.LowerA && c <= CharCodes.LowerF));
242
- }
243
644
  var QuoteType;
244
645
  (function (QuoteType) {
245
646
  QuoteType[QuoteType["NoValue"] = 0] = "NoValue";
@@ -272,6 +673,8 @@ class Tokenizer {
272
673
  this.sectionStart = 0;
273
674
  /** The index within the buffer that we are currently looking at. */
274
675
  this.index = 0;
676
+ /** The start of the last entity. */
677
+ this.entityStart = 0;
275
678
  /** Some behavior, eg. when decoding entities, is done while we are in another state. This keeps track of the other state type. */
276
679
  this.baseState = State$1.Text;
277
680
  /** For special parsing behavior inside of script and style tags. */
@@ -282,14 +685,9 @@ class Tokenizer {
282
685
  this.offset = 0;
283
686
  this.currentSequence = undefined;
284
687
  this.sequenceIndex = 0;
285
- this.trieIndex = 0;
286
- this.trieCurrent = 0;
287
- /** For named entities, the index of the value. For numeric entities, the code point. */
288
- this.entityResult = 0;
289
- this.entityExcess = 0;
290
688
  this.xmlMode = xmlMode;
291
689
  this.decodeEntities = decodeEntities;
292
- this.entityTrie = xmlMode ? xmlDecodeTree : htmlDecodeTree;
690
+ this.entityDecoder = new EntityDecoder(xmlMode ? xmlDecodeTree : htmlDecodeTree, (cp, consumed) => this.emitCodePoint(cp, consumed));
293
691
  }
294
692
  reset() {
295
693
  this.state = State$1.Text;
@@ -319,18 +717,6 @@ class Tokenizer {
319
717
  this.parse();
320
718
  }
321
719
  }
322
- /**
323
- * The current index within all of the written data.
324
- */
325
- getIndex() {
326
- return this.index;
327
- }
328
- /**
329
- * The start of the current section.
330
- */
331
- getSectionStart() {
332
- return this.sectionStart;
333
- }
334
720
  stateText(c) {
335
721
  if (c === CharCodes.Lt ||
336
722
  (!this.decodeEntities && this.fastForwardTo(CharCodes.Lt))) {
@@ -341,7 +727,7 @@ class Tokenizer {
341
727
  this.sectionStart = this.index;
342
728
  }
343
729
  else if (this.decodeEntities && c === CharCodes.Amp) {
344
- this.state = State$1.BeforeEntity;
730
+ this.startEntity();
345
731
  }
346
732
  }
347
733
  stateSpecialStartSequence(c) {
@@ -388,7 +774,7 @@ class Tokenizer {
388
774
  if (this.currentSequence === Sequences.TitleEnd) {
389
775
  // We have to parse entities in <title> tags.
390
776
  if (this.decodeEntities && c === CharCodes.Amp) {
391
- this.state = State$1.BeforeEntity;
777
+ this.startEntity();
392
778
  }
393
779
  }
394
780
  else if (this.fastForwardTo(CharCodes.Lt)) {
@@ -547,7 +933,6 @@ class Tokenizer {
547
933
  // Skip everything until ">"
548
934
  if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
549
935
  this.state = State$1.Text;
550
- this.baseState = State$1.Text;
551
936
  this.sectionStart = this.index + 1;
552
937
  }
553
938
  }
@@ -561,7 +946,6 @@ class Tokenizer {
561
946
  else {
562
947
  this.state = State$1.Text;
563
948
  }
564
- this.baseState = this.state;
565
949
  this.sectionStart = this.index + 1;
566
950
  }
567
951
  else if (c === CharCodes.Slash) {
@@ -576,7 +960,6 @@ class Tokenizer {
576
960
  if (c === CharCodes.Gt) {
577
961
  this.cbs.onselfclosingtag(this.index);
578
962
  this.state = State$1.Text;
579
- this.baseState = State$1.Text;
580
963
  this.sectionStart = this.index + 1;
581
964
  this.isSpecial = false; // Reset special state, in case of self-closing special tags
582
965
  }
@@ -634,8 +1017,7 @@ class Tokenizer {
634
1017
  this.state = State$1.BeforeAttributeName;
635
1018
  }
636
1019
  else if (this.decodeEntities && c === CharCodes.Amp) {
637
- this.baseState = this.state;
638
- this.state = State$1.BeforeEntity;
1020
+ this.startEntity();
639
1021
  }
640
1022
  }
641
1023
  stateInAttributeValueDoubleQuotes(c) {
@@ -653,8 +1035,7 @@ class Tokenizer {
653
1035
  this.stateBeforeAttributeName(c);
654
1036
  }
655
1037
  else if (this.decodeEntities && c === CharCodes.Amp) {
656
- this.baseState = this.state;
657
- this.state = State$1.BeforeEntity;
1038
+ this.startEntity();
658
1039
  }
659
1040
  }
660
1041
  stateBeforeDeclaration(c) {
@@ -715,147 +1096,30 @@ class Tokenizer {
715
1096
  this.stateInTagName(c); // Consume the token again
716
1097
  }
717
1098
  }
718
- stateBeforeEntity(c) {
719
- // Start excess with 1 to include the '&'
720
- this.entityExcess = 1;
721
- this.entityResult = 0;
722
- if (c === CharCodes.Number) {
723
- this.state = State$1.BeforeNumericEntity;
724
- }
725
- else if (c === CharCodes.Amp) ;
726
- else {
727
- this.trieIndex = 0;
728
- this.trieCurrent = this.entityTrie[0];
729
- this.state = State$1.InNamedEntity;
730
- this.stateInNamedEntity(c);
731
- }
732
- }
733
- stateInNamedEntity(c) {
734
- this.entityExcess += 1;
735
- this.trieIndex = determineBranch(this.entityTrie, this.trieCurrent, this.trieIndex + 1, c);
736
- if (this.trieIndex < 0) {
737
- this.emitNamedEntity();
738
- this.index--;
739
- return;
740
- }
741
- this.trieCurrent = this.entityTrie[this.trieIndex];
742
- const masked = this.trieCurrent & BinTrieFlags.VALUE_LENGTH;
743
- // If the branch is a value, store it and continue
744
- if (masked) {
745
- // The mask is the number of bytes of the value, including the current byte.
746
- const valueLength = (masked >> 14) - 1;
747
- // If we have a legacy entity while parsing strictly, just skip the number of bytes
748
- if (!this.allowLegacyEntity() && c !== CharCodes.Semi) {
749
- this.trieIndex += valueLength;
750
- }
751
- else {
752
- // Add 1 as we have already incremented the excess
753
- const entityStart = this.index - this.entityExcess + 1;
754
- if (entityStart > this.sectionStart) {
755
- this.emitPartial(this.sectionStart, entityStart);
756
- }
757
- // If this is a surrogate pair, consume the next two bytes
758
- this.entityResult = this.trieIndex;
759
- this.trieIndex += valueLength;
760
- this.entityExcess = 0;
761
- this.sectionStart = this.index + 1;
762
- if (valueLength === 0) {
763
- this.emitNamedEntity();
764
- }
1099
+ startEntity() {
1100
+ this.baseState = this.state;
1101
+ this.state = State$1.InEntity;
1102
+ this.entityStart = this.index;
1103
+ this.entityDecoder.startEntity(this.xmlMode
1104
+ ? DecodingMode.Strict
1105
+ : this.baseState === State$1.Text ||
1106
+ this.baseState === State$1.InSpecialTag
1107
+ ? DecodingMode.Legacy
1108
+ : DecodingMode.Attribute);
1109
+ }
1110
+ stateInEntity() {
1111
+ const length = this.entityDecoder.write(this.buffer, this.index - this.offset);
1112
+ // If `length` is positive, we are done with the entity.
1113
+ if (length >= 0) {
1114
+ this.state = this.baseState;
1115
+ if (length === 0) {
1116
+ this.index = this.entityStart;
765
1117
  }
766
1118
  }
767
- }
768
- emitNamedEntity() {
769
- this.state = this.baseState;
770
- if (this.entityResult === 0) {
771
- return;
772
- }
773
- const valueLength = (this.entityTrie[this.entityResult] & BinTrieFlags.VALUE_LENGTH) >>
774
- 14;
775
- switch (valueLength) {
776
- case 1: {
777
- this.emitCodePoint(this.entityTrie[this.entityResult] &
778
- ~BinTrieFlags.VALUE_LENGTH);
779
- break;
780
- }
781
- case 2: {
782
- this.emitCodePoint(this.entityTrie[this.entityResult + 1]);
783
- break;
784
- }
785
- case 3: {
786
- this.emitCodePoint(this.entityTrie[this.entityResult + 1]);
787
- this.emitCodePoint(this.entityTrie[this.entityResult + 2]);
788
- }
789
- }
790
- }
791
- stateBeforeNumericEntity(c) {
792
- if ((c | 0x20) === CharCodes.LowerX) {
793
- this.entityExcess++;
794
- this.state = State$1.InHexEntity;
795
- }
796
1119
  else {
797
- this.state = State$1.InNumericEntity;
798
- this.stateInNumericEntity(c);
799
- }
800
- }
801
- emitNumericEntity(strict) {
802
- const entityStart = this.index - this.entityExcess - 1;
803
- const numberStart = entityStart + 2 + Number(this.state === State$1.InHexEntity);
804
- if (numberStart !== this.index) {
805
- // Emit leading data if any
806
- if (entityStart > this.sectionStart) {
807
- this.emitPartial(this.sectionStart, entityStart);
808
- }
809
- this.sectionStart = this.index + Number(strict);
810
- this.emitCodePoint(replaceCodePoint(this.entityResult));
1120
+ // Mark buffer as consumed.
1121
+ this.index = this.offset + this.buffer.length - 1;
811
1122
  }
812
- this.state = this.baseState;
813
- }
814
- stateInNumericEntity(c) {
815
- if (c === CharCodes.Semi) {
816
- this.emitNumericEntity(true);
817
- }
818
- else if (isNumber(c)) {
819
- this.entityResult = this.entityResult * 10 + (c - CharCodes.Zero);
820
- this.entityExcess++;
821
- }
822
- else {
823
- if (this.allowLegacyEntity()) {
824
- this.emitNumericEntity(false);
825
- }
826
- else {
827
- this.state = this.baseState;
828
- }
829
- this.index--;
830
- }
831
- }
832
- stateInHexEntity(c) {
833
- if (c === CharCodes.Semi) {
834
- this.emitNumericEntity(true);
835
- }
836
- else if (isNumber(c)) {
837
- this.entityResult = this.entityResult * 16 + (c - CharCodes.Zero);
838
- this.entityExcess++;
839
- }
840
- else if (isHexDigit$1(c)) {
841
- this.entityResult =
842
- this.entityResult * 16 + ((c | 0x20) - CharCodes.LowerA + 10);
843
- this.entityExcess++;
844
- }
845
- else {
846
- if (this.allowLegacyEntity()) {
847
- this.emitNumericEntity(false);
848
- }
849
- else {
850
- this.state = this.baseState;
851
- }
852
- this.index--;
853
- }
854
- }
855
- allowLegacyEntity() {
856
- return (!this.xmlMode &&
857
- (this.baseState === State$1.Text ||
858
- this.baseState === State$1.InSpecialTag));
859
1123
  }
860
1124
  /**
861
1125
  * Remove data that has already been consumed from the buffer.
@@ -984,44 +1248,30 @@ class Tokenizer {
984
1248
  this.stateInProcessingInstruction(c);
985
1249
  break;
986
1250
  }
987
- case State$1.InNamedEntity: {
988
- this.stateInNamedEntity(c);
989
- break;
990
- }
991
- case State$1.BeforeEntity: {
992
- this.stateBeforeEntity(c);
1251
+ case State$1.InEntity: {
1252
+ this.stateInEntity();
993
1253
  break;
994
1254
  }
995
- case State$1.InHexEntity: {
996
- this.stateInHexEntity(c);
997
- break;
998
- }
999
- case State$1.InNumericEntity: {
1000
- this.stateInNumericEntity(c);
1001
- break;
1002
- }
1003
- default: {
1004
- // `this._state === State.BeforeNumericEntity`
1005
- this.stateBeforeNumericEntity(c);
1006
- }
1007
1255
  }
1008
1256
  this.index++;
1009
1257
  }
1010
1258
  this.cleanup();
1011
1259
  }
1012
1260
  finish() {
1013
- if (this.state === State$1.InNamedEntity) {
1014
- this.emitNamedEntity();
1015
- }
1016
- // If there is remaining data, emit it in a reasonable way
1017
- if (this.sectionStart < this.index) {
1018
- this.handleTrailingData();
1261
+ if (this.state === State$1.InEntity) {
1262
+ this.entityDecoder.end();
1263
+ this.state = this.baseState;
1019
1264
  }
1265
+ this.handleTrailingData();
1020
1266
  this.cbs.onend();
1021
1267
  }
1022
1268
  /** Handle any trailing data. */
1023
1269
  handleTrailingData() {
1024
1270
  const endIndex = this.buffer.length + this.offset;
1271
+ // If there is no remaining data, we are done.
1272
+ if (this.sectionStart >= endIndex) {
1273
+ return;
1274
+ }
1025
1275
  if (this.state === State$1.InCommentLike) {
1026
1276
  if (this.currentSequence === Sequences.CdataEnd) {
1027
1277
  this.cbs.oncdata(this.sectionStart, endIndex, 0);
@@ -1030,16 +1280,6 @@ class Tokenizer {
1030
1280
  this.cbs.oncomment(this.sectionStart, endIndex, 0);
1031
1281
  }
1032
1282
  }
1033
- else if (this.state === State$1.InNumericEntity &&
1034
- this.allowLegacyEntity()) {
1035
- this.emitNumericEntity(false);
1036
- // All trailing data will have been consumed
1037
- }
1038
- else if (this.state === State$1.InHexEntity &&
1039
- this.allowLegacyEntity()) {
1040
- this.emitNumericEntity(false);
1041
- // All trailing data will have been consumed
1042
- }
1043
1283
  else if (this.state === State$1.InTagName ||
1044
1284
  this.state === State$1.BeforeAttributeName ||
1045
1285
  this.state === State$1.BeforeAttributeValue ||
@@ -1053,22 +1293,23 @@ class Tokenizer {
1053
1293
  this.cbs.ontext(this.sectionStart, endIndex);
1054
1294
  }
1055
1295
  }
1056
- emitPartial(start, endIndex) {
1057
- if (this.baseState !== State$1.Text &&
1058
- this.baseState !== State$1.InSpecialTag) {
1059
- this.cbs.onattribdata(start, endIndex);
1060
- }
1061
- else {
1062
- this.cbs.ontext(start, endIndex);
1063
- }
1064
- }
1065
- emitCodePoint(cp) {
1296
+ emitCodePoint(cp, consumed) {
1066
1297
  if (this.baseState !== State$1.Text &&
1067
1298
  this.baseState !== State$1.InSpecialTag) {
1299
+ if (this.sectionStart < this.entityStart) {
1300
+ this.cbs.onattribdata(this.sectionStart, this.entityStart);
1301
+ }
1302
+ this.sectionStart = this.entityStart + consumed;
1303
+ this.index = this.sectionStart - 1;
1068
1304
  this.cbs.onattribentity(cp);
1069
1305
  }
1070
1306
  else {
1071
- this.cbs.ontextentity(cp);
1307
+ if (this.sectionStart < this.entityStart) {
1308
+ this.cbs.ontext(this.sectionStart, this.entityStart);
1309
+ }
1310
+ this.sectionStart = this.entityStart + consumed;
1311
+ this.index = this.sectionStart - 1;
1312
+ this.cbs.ontextentity(cp, this.sectionStart);
1072
1313
  }
1073
1314
  }
1074
1315
  }
@@ -1187,7 +1428,6 @@ let Parser$1 = class Parser {
1187
1428
  this.attribvalue = "";
1188
1429
  this.attribs = null;
1189
1430
  this.stack = [];
1190
- this.foreignContext = [];
1191
1431
  this.buffers = [];
1192
1432
  this.bufferOffset = 0;
1193
1433
  /** The index of the last written buffer. Used when resuming after a `pause()`. */
@@ -1195,10 +1435,12 @@ let Parser$1 = class Parser {
1195
1435
  /** Indicates whether the parser has finished running / `.end` has been called. */
1196
1436
  this.ended = false;
1197
1437
  this.cbs = cbs !== null && cbs !== void 0 ? cbs : {};
1198
- this.lowerCaseTagNames = (_a = options.lowerCaseTags) !== null && _a !== void 0 ? _a : !options.xmlMode;
1438
+ this.htmlMode = !this.options.xmlMode;
1439
+ this.lowerCaseTagNames = (_a = options.lowerCaseTags) !== null && _a !== void 0 ? _a : this.htmlMode;
1199
1440
  this.lowerCaseAttributeNames =
1200
- (_b = options.lowerCaseAttributeNames) !== null && _b !== void 0 ? _b : !options.xmlMode;
1441
+ (_b = options.lowerCaseAttributeNames) !== null && _b !== void 0 ? _b : this.htmlMode;
1201
1442
  this.tokenizer = new ((_c = options.Tokenizer) !== null && _c !== void 0 ? _c : Tokenizer)(this.options, this);
1443
+ this.foreignContext = [!this.htmlMode];
1202
1444
  (_e = (_d = this.cbs).onparserinit) === null || _e === void 0 ? void 0 : _e.call(_d, this);
1203
1445
  }
1204
1446
  // Tokenizer event handlers
@@ -1211,19 +1453,18 @@ let Parser$1 = class Parser {
1211
1453
  this.startIndex = endIndex;
1212
1454
  }
1213
1455
  /** @internal */
1214
- ontextentity(cp) {
1456
+ ontextentity(cp, endIndex) {
1215
1457
  var _a, _b;
1216
- /*
1217
- * Entities can be emitted on the character, or directly after.
1218
- * We use the section start here to get accurate indices.
1219
- */
1220
- const index = this.tokenizer.getSectionStart();
1221
- this.endIndex = index - 1;
1458
+ this.endIndex = endIndex - 1;
1222
1459
  (_b = (_a = this.cbs).ontext) === null || _b === void 0 ? void 0 : _b.call(_a, fromCodePoint(cp));
1223
- this.startIndex = index;
1460
+ this.startIndex = endIndex;
1224
1461
  }
1462
+ /**
1463
+ * Checks if the current tag is a void element. Override this if you want
1464
+ * to specify your own additional void elements.
1465
+ */
1225
1466
  isVoidElement(name) {
1226
- return !this.options.xmlMode && voidElements.has(name);
1467
+ return this.htmlMode && voidElements.has(name);
1227
1468
  }
1228
1469
  /** @internal */
1229
1470
  onopentagname(start, endIndex) {
@@ -1238,21 +1479,22 @@ let Parser$1 = class Parser {
1238
1479
  var _a, _b, _c, _d;
1239
1480
  this.openTagStart = this.startIndex;
1240
1481
  this.tagname = name;
1241
- const impliesClose = !this.options.xmlMode && openImpliesClose.get(name);
1482
+ const impliesClose = this.htmlMode && openImpliesClose.get(name);
1242
1483
  if (impliesClose) {
1243
- while (this.stack.length > 0 &&
1244
- impliesClose.has(this.stack[this.stack.length - 1])) {
1245
- const element = this.stack.pop();
1484
+ while (this.stack.length > 0 && impliesClose.has(this.stack[0])) {
1485
+ const element = this.stack.shift();
1246
1486
  (_b = (_a = this.cbs).onclosetag) === null || _b === void 0 ? void 0 : _b.call(_a, element, true);
1247
1487
  }
1248
1488
  }
1249
1489
  if (!this.isVoidElement(name)) {
1250
- this.stack.push(name);
1251
- if (foreignContextElements.has(name)) {
1252
- this.foreignContext.push(true);
1253
- }
1254
- else if (htmlIntegrationElements.has(name)) {
1255
- this.foreignContext.push(false);
1490
+ this.stack.unshift(name);
1491
+ if (this.htmlMode) {
1492
+ if (foreignContextElements.has(name)) {
1493
+ this.foreignContext.unshift(true);
1494
+ }
1495
+ else if (htmlIntegrationElements.has(name)) {
1496
+ this.foreignContext.unshift(false);
1497
+ }
1256
1498
  }
1257
1499
  }
1258
1500
  (_d = (_c = this.cbs).onopentagname) === null || _d === void 0 ? void 0 : _d.call(_c, name);
@@ -1280,40 +1522,37 @@ let Parser$1 = class Parser {
1280
1522
  }
1281
1523
  /** @internal */
1282
1524
  onclosetag(start, endIndex) {
1283
- var _a, _b, _c, _d, _e, _f;
1525
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1284
1526
  this.endIndex = endIndex;
1285
1527
  let name = this.getSlice(start, endIndex);
1286
1528
  if (this.lowerCaseTagNames) {
1287
1529
  name = name.toLowerCase();
1288
1530
  }
1289
- if (foreignContextElements.has(name) ||
1290
- htmlIntegrationElements.has(name)) {
1291
- this.foreignContext.pop();
1531
+ if (this.htmlMode &&
1532
+ (foreignContextElements.has(name) ||
1533
+ htmlIntegrationElements.has(name))) {
1534
+ this.foreignContext.shift();
1292
1535
  }
1293
1536
  if (!this.isVoidElement(name)) {
1294
- const pos = this.stack.lastIndexOf(name);
1537
+ const pos = this.stack.indexOf(name);
1295
1538
  if (pos !== -1) {
1296
- if (this.cbs.onclosetag) {
1297
- let count = this.stack.length - pos;
1298
- while (count--) {
1299
- // We know the stack has sufficient elements.
1300
- this.cbs.onclosetag(this.stack.pop(), count !== 0);
1301
- }
1539
+ for (let index = 0; index <= pos; index++) {
1540
+ const element = this.stack.shift();
1541
+ // We know the stack has sufficient elements.
1542
+ (_b = (_a = this.cbs).onclosetag) === null || _b === void 0 ? void 0 : _b.call(_a, element, index !== pos);
1302
1543
  }
1303
- else
1304
- this.stack.length = pos;
1305
1544
  }
1306
- else if (!this.options.xmlMode && name === "p") {
1545
+ else if (this.htmlMode && name === "p") {
1307
1546
  // Implicit open before close
1308
1547
  this.emitOpenTag("p");
1309
1548
  this.closeCurrentTag(true);
1310
1549
  }
1311
1550
  }
1312
- else if (!this.options.xmlMode && name === "br") {
1551
+ else if (this.htmlMode && name === "br") {
1313
1552
  // We can't use `emitOpenTag` for implicit open, as `br` would be implicitly closed.
1314
- (_b = (_a = this.cbs).onopentagname) === null || _b === void 0 ? void 0 : _b.call(_a, "br");
1315
- (_d = (_c = this.cbs).onopentag) === null || _d === void 0 ? void 0 : _d.call(_c, "br", {}, true);
1316
- (_f = (_e = this.cbs).onclosetag) === null || _f === void 0 ? void 0 : _f.call(_e, "br", false);
1553
+ (_d = (_c = this.cbs).onopentagname) === null || _d === void 0 ? void 0 : _d.call(_c, "br");
1554
+ (_f = (_e = this.cbs).onopentag) === null || _f === void 0 ? void 0 : _f.call(_e, "br", {}, true);
1555
+ (_h = (_g = this.cbs).onclosetag) === null || _h === void 0 ? void 0 : _h.call(_g, "br", false);
1317
1556
  }
1318
1557
  // Set `startIndex` for next node
1319
1558
  this.startIndex = endIndex + 1;
@@ -1321,9 +1560,7 @@ let Parser$1 = class Parser {
1321
1560
  /** @internal */
1322
1561
  onselfclosingtag(endIndex) {
1323
1562
  this.endIndex = endIndex;
1324
- if (this.options.xmlMode ||
1325
- this.options.recognizeSelfClosing ||
1326
- this.foreignContext[this.foreignContext.length - 1]) {
1563
+ if (this.options.recognizeSelfClosing || this.foreignContext[0]) {
1327
1564
  this.closeCurrentTag(false);
1328
1565
  // Set `startIndex` for next node
1329
1566
  this.startIndex = endIndex + 1;
@@ -1338,10 +1575,10 @@ let Parser$1 = class Parser {
1338
1575
  const name = this.tagname;
1339
1576
  this.endOpenTag(isOpenImplied);
1340
1577
  // Self-closing tags will be on the top of the stack
1341
- if (this.stack[this.stack.length - 1] === name) {
1578
+ if (this.stack[0] === name) {
1342
1579
  // If the opening tag isn't implied, the closing tag has to be implied.
1343
1580
  (_b = (_a = this.cbs).onclosetag) === null || _b === void 0 ? void 0 : _b.call(_a, name, !isOpenImplied);
1344
- this.stack.pop();
1581
+ this.stack.shift();
1345
1582
  }
1346
1583
  }
1347
1584
  /** @internal */
@@ -1421,7 +1658,7 @@ let Parser$1 = class Parser {
1421
1658
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1422
1659
  this.endIndex = endIndex;
1423
1660
  const value = this.getSlice(start, endIndex - offset);
1424
- if (this.options.xmlMode || this.options.recognizeCDATA) {
1661
+ if (!this.htmlMode || this.options.recognizeCDATA) {
1425
1662
  (_b = (_a = this.cbs).oncdatastart) === null || _b === void 0 ? void 0 : _b.call(_a);
1426
1663
  (_d = (_c = this.cbs).ontext) === null || _d === void 0 ? void 0 : _d.call(_c, value);
1427
1664
  (_f = (_e = this.cbs).oncdataend) === null || _f === void 0 ? void 0 : _f.call(_e);
@@ -1439,8 +1676,9 @@ let Parser$1 = class Parser {
1439
1676
  if (this.cbs.onclosetag) {
1440
1677
  // Set the end index for all remaining tags
1441
1678
  this.endIndex = this.startIndex;
1442
- for (let index = this.stack.length; index > 0; this.cbs.onclosetag(this.stack[--index], true))
1443
- ;
1679
+ for (let index = 0; index < this.stack.length; index++) {
1680
+ this.cbs.onclosetag(this.stack[index], true);
1681
+ }
1444
1682
  }
1445
1683
  (_b = (_a = this.cbs).onend) === null || _b === void 0 ? void 0 : _b.call(_a);
1446
1684
  }
@@ -1459,6 +1697,8 @@ let Parser$1 = class Parser {
1459
1697
  this.endIndex = 0;
1460
1698
  (_d = (_c = this.cbs).onparserinit) === null || _d === void 0 ? void 0 : _d.call(_c, this);
1461
1699
  this.buffers.length = 0;
1700
+ this.foreignContext.length = 0;
1701
+ this.foreignContext.unshift(!this.htmlMode);
1462
1702
  this.bufferOffset = 0;
1463
1703
  this.writeIndex = 0;
1464
1704
  this.ended = false;
@@ -2140,6 +2380,16 @@ function encodeXML(str) {
2140
2380
  }
2141
2381
  return ret + str.substr(lastIdx);
2142
2382
  }
2383
+ /**
2384
+ * Creates a function that escapes all characters matched by the given regular
2385
+ * expression using the given map of characters to escape to their entities.
2386
+ *
2387
+ * @param regex Regular expression to match characters to escape.
2388
+ * @param map Map of characters to escape to their entities.
2389
+ *
2390
+ * @returns Function that escapes all characters matched by the given regular
2391
+ * expression using the given map of characters to escape to their entities.
2392
+ */
2143
2393
  function getEscaper(regex, map) {
2144
2394
  return function escape(data) {
2145
2395
  let match;
@@ -2149,7 +2399,7 @@ function getEscaper(regex, map) {
2149
2399
  if (lastIdx !== match.index) {
2150
2400
  result += data.substring(lastIdx, match.index);
2151
2401
  }
2152
- // We know that this chararcter will be in the map.
2402
+ // We know that this character will be in the map.
2153
2403
  result += map.get(match[0].charCodeAt(0));
2154
2404
  // Every match will be of length 1
2155
2405
  lastIdx = match.index + 1;
@@ -2486,7 +2736,7 @@ function getInnerHTML(node, options) {
2486
2736
  : "";
2487
2737
  }
2488
2738
  /**
2489
- * Get a node's inner text. Same as `textContent`, but inserts newlines for `<br>` tags.
2739
+ * Get a node's inner text. Same as `textContent`, but inserts newlines for `<br>` tags. Ignores comments.
2490
2740
  *
2491
2741
  * @category Stringify
2492
2742
  * @deprecated Use `textContent` instead.
@@ -2505,7 +2755,7 @@ function getText(node) {
2505
2755
  return "";
2506
2756
  }
2507
2757
  /**
2508
- * Get a node's text content.
2758
+ * Get a node's text content. Ignores comments.
2509
2759
  *
2510
2760
  * @category Stringify
2511
2761
  * @param node Node to get the text content of.
@@ -2523,7 +2773,7 @@ function textContent(node) {
2523
2773
  return "";
2524
2774
  }
2525
2775
  /**
2526
- * Get a node's inner text.
2776
+ * Get a node's inner text, ignoring `<script>` and `<style>` tags. Ignores comments.
2527
2777
  *
2528
2778
  * @category Stringify
2529
2779
  * @param node Node to get the inner text of.
@@ -2556,7 +2806,7 @@ function getChildren(elem) {
2556
2806
  *
2557
2807
  * @category Traversal
2558
2808
  * @param elem Node to get the parent of.
2559
- * @returns `elem`'s parent node.
2809
+ * @returns `elem`'s parent node, or `null` if `elem` is a root node.
2560
2810
  */
2561
2811
  function getParent(elem) {
2562
2812
  return elem.parent || null;
@@ -2570,7 +2820,7 @@ function getParent(elem) {
2570
2820
  *
2571
2821
  * @category Traversal
2572
2822
  * @param elem Element to get the siblings of.
2573
- * @returns `elem`'s siblings.
2823
+ * @returns `elem`'s siblings, including `elem`.
2574
2824
  */
2575
2825
  function getSiblings(elem) {
2576
2826
  const parent = getParent(elem);
@@ -2628,7 +2878,8 @@ function getName(elem) {
2628
2878
  *
2629
2879
  * @category Traversal
2630
2880
  * @param elem The element to get the next sibling of.
2631
- * @returns `elem`'s next sibling that is a tag.
2881
+ * @returns `elem`'s next sibling that is a tag, or `null` if there is no next
2882
+ * sibling.
2632
2883
  */
2633
2884
  function nextElementSibling(elem) {
2634
2885
  let { next } = elem;
@@ -2641,7 +2892,8 @@ function nextElementSibling(elem) {
2641
2892
  *
2642
2893
  * @category Traversal
2643
2894
  * @param elem The element to get the previous sibling of.
2644
- * @returns `elem`'s previous sibling that is a tag.
2895
+ * @returns `elem`'s previous sibling that is a tag, or `null` if there is no
2896
+ * previous sibling.
2645
2897
  */
2646
2898
  function prevElementSibling(elem) {
2647
2899
  let { prev } = elem;
@@ -2663,8 +2915,14 @@ function removeElement(elem) {
2663
2915
  elem.next.prev = elem.prev;
2664
2916
  if (elem.parent) {
2665
2917
  const childs = elem.parent.children;
2666
- childs.splice(childs.lastIndexOf(elem), 1);
2918
+ const childsIndex = childs.lastIndexOf(elem);
2919
+ if (childsIndex >= 0) {
2920
+ childs.splice(childsIndex, 1);
2921
+ }
2667
2922
  }
2923
+ elem.next = null;
2924
+ elem.prev = null;
2925
+ elem.parent = null;
2668
2926
  }
2669
2927
  /**
2670
2928
  * Replace an element in the dom
@@ -2693,15 +2951,15 @@ function replaceElement(elem, replacement) {
2693
2951
  * Append a child to an element.
2694
2952
  *
2695
2953
  * @category Manipulation
2696
- * @param elem The element to append to.
2954
+ * @param parent The element to append to.
2697
2955
  * @param child The element to be added as a child.
2698
2956
  */
2699
- function appendChild(elem, child) {
2957
+ function appendChild(parent, child) {
2700
2958
  removeElement(child);
2701
2959
  child.next = null;
2702
- child.parent = elem;
2703
- if (elem.children.push(child) > 1) {
2704
- const sibling = elem.children[elem.children.length - 2];
2960
+ child.parent = parent;
2961
+ if (parent.children.push(child) > 1) {
2962
+ const sibling = parent.children[parent.children.length - 2];
2705
2963
  sibling.next = child;
2706
2964
  child.prev = sibling;
2707
2965
  }
@@ -2739,15 +2997,15 @@ function append(elem, next) {
2739
2997
  * Prepend a child to an element.
2740
2998
  *
2741
2999
  * @category Manipulation
2742
- * @param elem The element to prepend before.
3000
+ * @param parent The element to prepend before.
2743
3001
  * @param child The element to be added as a child.
2744
3002
  */
2745
- function prependChild(elem, child) {
3003
+ function prependChild(parent, child) {
2746
3004
  removeElement(child);
2747
- child.parent = elem;
3005
+ child.parent = parent;
2748
3006
  child.prev = null;
2749
- if (elem.children.unshift(child) !== 1) {
2750
- const sibling = elem.children[1];
3007
+ if (parent.children.unshift(child) !== 1) {
3008
+ const sibling = parent.children[1];
2751
3009
  sibling.prev = child;
2752
3010
  child.next = sibling;
2753
3011
  }
@@ -2779,7 +3037,7 @@ function prepend(elem, prev) {
2779
3037
  }
2780
3038
 
2781
3039
  /**
2782
- * Search a node and its children for nodes passing a test function.
3040
+ * Search a node and its children for nodes passing a test function. If `node` is not an array, it will be wrapped in one.
2783
3041
  *
2784
3042
  * @category Querying
2785
3043
  * @param test Function to test nodes on.
@@ -2789,12 +3047,10 @@ function prepend(elem, prev) {
2789
3047
  * @returns All nodes passing `test`.
2790
3048
  */
2791
3049
  function filter(test, node, recurse = true, limit = Infinity) {
2792
- if (!Array.isArray(node))
2793
- node = [node];
2794
- return find(test, node, recurse, limit);
3050
+ return find(test, Array.isArray(node) ? node : [node], recurse, limit);
2795
3051
  }
2796
3052
  /**
2797
- * Search an array of node and its children for nodes passing a test function.
3053
+ * Search an array of nodes and their children for nodes passing a test function.
2798
3054
  *
2799
3055
  * @category Querying
2800
3056
  * @param test Function to test nodes on.
@@ -2805,24 +3061,41 @@ function filter(test, node, recurse = true, limit = Infinity) {
2805
3061
  */
2806
3062
  function find(test, nodes, recurse, limit) {
2807
3063
  const result = [];
2808
- for (const elem of nodes) {
3064
+ /** Stack of the arrays we are looking at. */
3065
+ const nodeStack = [nodes];
3066
+ /** Stack of the indices within the arrays. */
3067
+ const indexStack = [0];
3068
+ for (;;) {
3069
+ // First, check if the current array has any more elements to look at.
3070
+ if (indexStack[0] >= nodeStack[0].length) {
3071
+ // If we have no more arrays to look at, we are done.
3072
+ if (indexStack.length === 1) {
3073
+ return result;
3074
+ }
3075
+ // Otherwise, remove the current array from the stack.
3076
+ nodeStack.shift();
3077
+ indexStack.shift();
3078
+ // Loop back to the start to continue with the next array.
3079
+ continue;
3080
+ }
3081
+ const elem = nodeStack[0][indexStack[0]++];
2809
3082
  if (test(elem)) {
2810
3083
  result.push(elem);
2811
3084
  if (--limit <= 0)
2812
- break;
3085
+ return result;
2813
3086
  }
2814
3087
  if (recurse && hasChildren(elem) && elem.children.length > 0) {
2815
- const children = find(test, elem.children, recurse, limit);
2816
- result.push(...children);
2817
- limit -= children.length;
2818
- if (limit <= 0)
2819
- break;
3088
+ /*
3089
+ * Add the children to the stack. We are depth-first, so this is
3090
+ * the next array we look at.
3091
+ */
3092
+ indexStack.unshift(0);
3093
+ nodeStack.unshift(elem.children);
2820
3094
  }
2821
3095
  }
2822
- return result;
2823
3096
  }
2824
3097
  /**
2825
- * Finds the first element inside of an array that matches a test function.
3098
+ * Finds the first element inside of an array that matches a test function. This is an alias for `Array.prototype.find`.
2826
3099
  *
2827
3100
  * @category Querying
2828
3101
  * @param test Function to test nodes on.
@@ -2838,27 +3111,29 @@ function findOneChild(test, nodes) {
2838
3111
  *
2839
3112
  * @category Querying
2840
3113
  * @param test Function to test nodes on.
2841
- * @param nodes Array of nodes to search.
3114
+ * @param nodes Node or array of nodes to search.
2842
3115
  * @param recurse Also consider child nodes.
2843
- * @returns The first child node that passes `test`.
3116
+ * @returns The first node that passes `test`.
2844
3117
  */
2845
3118
  function findOne(test, nodes, recurse = true) {
2846
3119
  let elem = null;
2847
3120
  for (let i = 0; i < nodes.length && !elem; i++) {
2848
- const checked = nodes[i];
2849
- if (!isTag(checked)) {
3121
+ const node = nodes[i];
3122
+ if (!isTag(node)) {
2850
3123
  continue;
2851
3124
  }
2852
- else if (test(checked)) {
2853
- elem = checked;
3125
+ else if (test(node)) {
3126
+ elem = node;
2854
3127
  }
2855
- else if (recurse && checked.children.length > 0) {
2856
- elem = findOne(test, checked.children, true);
3128
+ else if (recurse && node.children.length > 0) {
3129
+ elem = findOne(test, node.children, true);
2857
3130
  }
2858
3131
  }
2859
3132
  return elem;
2860
3133
  }
2861
3134
  /**
3135
+ * Checks if a tree of nodes contains at least one node passing a test.
3136
+ *
2862
3137
  * @category Querying
2863
3138
  * @param test Function to test nodes on.
2864
3139
  * @param nodes Array of nodes to search.
@@ -2866,12 +3141,10 @@ function findOne(test, nodes, recurse = true) {
2866
3141
  */
2867
3142
  function existsOne(test, nodes) {
2868
3143
  return nodes.some((checked) => isTag(checked) &&
2869
- (test(checked) ||
2870
- (checked.children.length > 0 &&
2871
- existsOne(test, checked.children))));
3144
+ (test(checked) || existsOne(test, checked.children)));
2872
3145
  }
2873
3146
  /**
2874
- * Search and array of nodes and its children for elements passing a test function.
3147
+ * Search an array of nodes and their children for elements passing a test function.
2875
3148
  *
2876
3149
  * Same as `find`, but limited to elements and with less options, leading to reduced complexity.
2877
3150
  *
@@ -2881,21 +3154,35 @@ function existsOne(test, nodes) {
2881
3154
  * @returns All nodes passing `test`.
2882
3155
  */
2883
3156
  function findAll(test, nodes) {
2884
- var _a;
2885
3157
  const result = [];
2886
- const stack = nodes.filter(isTag);
2887
- let elem;
2888
- while ((elem = stack.shift())) {
2889
- const children = (_a = elem.children) === null || _a === void 0 ? void 0 : _a.filter(isTag);
2890
- if (children && children.length > 0) {
2891
- stack.unshift(...children);
3158
+ const nodeStack = [nodes];
3159
+ const indexStack = [0];
3160
+ for (;;) {
3161
+ if (indexStack[0] >= nodeStack[0].length) {
3162
+ if (nodeStack.length === 1) {
3163
+ return result;
3164
+ }
3165
+ // Otherwise, remove the current array from the stack.
3166
+ nodeStack.shift();
3167
+ indexStack.shift();
3168
+ // Loop back to the start to continue with the next array.
3169
+ continue;
2892
3170
  }
3171
+ const elem = nodeStack[0][indexStack[0]++];
3172
+ if (!isTag(elem))
3173
+ continue;
2893
3174
  if (test(elem))
2894
3175
  result.push(elem);
3176
+ if (elem.children.length > 0) {
3177
+ indexStack.unshift(0);
3178
+ nodeStack.unshift(elem.children);
3179
+ }
2895
3180
  }
2896
- return result;
2897
3181
  }
2898
3182
 
3183
+ /**
3184
+ * A map of functions to check nodes against.
3185
+ */
2899
3186
  const Checks = {
2900
3187
  tag_name(name) {
2901
3188
  if (typeof name === "function") {
@@ -2920,6 +3207,9 @@ const Checks = {
2920
3207
  },
2921
3208
  };
2922
3209
  /**
3210
+ * Returns a function to check whether a node has an attribute with a particular
3211
+ * value.
3212
+ *
2923
3213
  * @param attrib Attribute to check.
2924
3214
  * @param value Attribute value to look for.
2925
3215
  * @returns A function to check whether the a node has an attribute with a
@@ -2932,6 +3222,9 @@ function getAttribCheck(attrib, value) {
2932
3222
  return (elem) => isTag(elem) && elem.attribs[attrib] === value;
2933
3223
  }
2934
3224
  /**
3225
+ * Returns a function that returns `true` if either of the input functions
3226
+ * returns `true` for a node.
3227
+ *
2935
3228
  * @param a First function to combine.
2936
3229
  * @param b Second function to combine.
2937
3230
  * @returns A function taking a node and returning `true` if either of the input
@@ -2941,9 +3234,12 @@ function combineFuncs(a, b) {
2941
3234
  return (elem) => a(elem) || b(elem);
2942
3235
  }
2943
3236
  /**
3237
+ * Returns a function that executes all checks in `options` and returns `true`
3238
+ * if any of them match a node.
3239
+ *
2944
3240
  * @param options An object describing nodes to look for.
2945
- * @returns A function executing all checks in `options` and returning `true` if
2946
- * any of them match a node.
3241
+ * @returns A function that executes all checks in `options` and returns `true`
3242
+ * if any of them match a node.
2947
3243
  */
2948
3244
  function compileTest(options) {
2949
3245
  const funcs = Object.keys(options).map((key) => {
@@ -2955,6 +3251,8 @@ function compileTest(options) {
2955
3251
  return funcs.length === 0 ? null : funcs.reduce(combineFuncs);
2956
3252
  }
2957
3253
  /**
3254
+ * Checks whether a node matches the description in `options`.
3255
+ *
2958
3256
  * @category Legacy Query Functions
2959
3257
  * @param options An object describing nodes to look for.
2960
3258
  * @param node The element to test.
@@ -2965,6 +3263,8 @@ function testElement(options, node) {
2965
3263
  return test ? test(node) : true;
2966
3264
  }
2967
3265
  /**
3266
+ * Returns all nodes that match `options`.
3267
+ *
2968
3268
  * @category Legacy Query Functions
2969
3269
  * @param options An object describing nodes to look for.
2970
3270
  * @param nodes Nodes to search through.
@@ -2977,6 +3277,8 @@ function getElements(options, nodes, recurse, limit = Infinity) {
2977
3277
  return test ? filter(test, nodes, recurse, limit) : [];
2978
3278
  }
2979
3279
  /**
3280
+ * Returns the node with the supplied ID.
3281
+ *
2980
3282
  * @category Legacy Query Functions
2981
3283
  * @param id The unique ID attribute value to look for.
2982
3284
  * @param nodes Nodes to search through.
@@ -2989,6 +3291,8 @@ function getElementById(id, nodes, recurse = true) {
2989
3291
  return findOne(getAttribCheck("id", id), nodes, recurse);
2990
3292
  }
2991
3293
  /**
3294
+ * Returns all nodes with the supplied `tagName`.
3295
+ *
2992
3296
  * @category Legacy Query Functions
2993
3297
  * @param tagName Tag name to search for.
2994
3298
  * @param nodes Nodes to search through.
@@ -3000,6 +3304,8 @@ function getElementsByTagName(tagName, nodes, recurse = true, limit = Infinity)
3000
3304
  return filter(Checks["tag_name"](tagName), nodes, recurse, limit);
3001
3305
  }
3002
3306
  /**
3307
+ * Returns all nodes with the supplied `type`.
3308
+ *
3003
3309
  * @category Legacy Query Functions
3004
3310
  * @param type Element type to look for.
3005
3311
  * @param nodes Nodes to search through.
@@ -3012,11 +3318,12 @@ function getElementsByTagType(type, nodes, recurse = true, limit = Infinity) {
3012
3318
  }
3013
3319
 
3014
3320
  /**
3015
- * Given an array of nodes, remove any member that is contained by another.
3321
+ * Given an array of nodes, remove any member that is contained by another
3322
+ * member.
3016
3323
  *
3017
3324
  * @category Helpers
3018
3325
  * @param nodes Nodes to filter.
3019
- * @returns Remaining nodes that aren't subtrees of each other.
3326
+ * @returns Remaining nodes that aren't contained by other nodes.
3020
3327
  */
3021
3328
  function removeSubsets(nodes) {
3022
3329
  let idx = nodes.length;
@@ -3057,8 +3364,8 @@ var DocumentPosition;
3057
3364
  DocumentPosition[DocumentPosition["CONTAINED_BY"] = 16] = "CONTAINED_BY";
3058
3365
  })(DocumentPosition || (DocumentPosition = {}));
3059
3366
  /**
3060
- * Compare the position of one node against another node in any other document.
3061
- * The return value is a bitmask with the values from {@link DocumentPosition}.
3367
+ * Compare the position of one node against another node in any other document,
3368
+ * returning a bitmask with the values from {@link DocumentPosition}.
3062
3369
  *
3063
3370
  * Document order:
3064
3371
  * > There is an ordering, document order, defined on all the nodes in the
@@ -3122,9 +3429,9 @@ function compareDocumentPosition(nodeA, nodeB) {
3122
3429
  return DocumentPosition.PRECEDING;
3123
3430
  }
3124
3431
  /**
3125
- * Sort an array of nodes based on their relative position in the document and
3126
- * remove any duplicate nodes. If the array contains nodes that do not belong to
3127
- * the same document, sort order is unspecified.
3432
+ * Sort an array of nodes based on their relative position in the document,
3433
+ * removing any duplicate nodes. If the array contains nodes that do not belong
3434
+ * to the same document, sort order is unspecified.
3128
3435
  *
3129
3436
  * @category Helpers
3130
3437
  * @param nodes Array of DOM nodes.
@@ -3225,7 +3532,7 @@ function getRssFeed(feedRoot) {
3225
3532
  addConditionally(entry, "title", "title", children);
3226
3533
  addConditionally(entry, "link", "link", children);
3227
3534
  addConditionally(entry, "description", "description", children);
3228
- const pubDate = fetch("pubDate", children);
3535
+ const pubDate = fetch("pubDate", children) || fetch("dc:date", children);
3229
3536
  if (pubDate)
3230
3537
  entry.pubDate = new Date(pubDate);
3231
3538
  return entry;
@@ -3376,7 +3683,7 @@ var DomUtils = /*#__PURE__*/Object.freeze({
3376
3683
  * Parses the data, returns the resulting document.
3377
3684
  *
3378
3685
  * @param data The data that should be parsed.
3379
- * @param options Optional options for the parser and DOM builder.
3686
+ * @param options Optional options for the parser and DOM handler.
3380
3687
  */
3381
3688
  function parseDocument(data, options) {
3382
3689
  const handler = new DomHandler(undefined, options);
@@ -10545,6 +10852,7 @@ function parse(input, options) {
10545
10852
 
10546
10853
  // AST walker module for Mozilla Parser API compatible trees
10547
10854
 
10855
+
10548
10856
  // An ancestor walk keeps an array of ancestor nodes (including the
10549
10857
  // current node) and passes them to the callback as third parameter
10550
10858
  // (and also as state parameter when no other state is present).
@@ -12020,13 +12328,14 @@ const HASH_CHARACTER_LENGTH = 16;
12020
12328
  * finding the asset urls in the AST assumes that there will be no
12021
12329
  * user code with the same structure as the asset urls. In other words:
12022
12330
  * Don't use a define a property named "canvasKitWasmUrl" that refers to
12023
- * a string literal, don't define a property named "fontUrls" that refers
12024
- * to an array expression of string literals, and don't define a property
12025
- * called "images" that refers to an array expression of object expressions
12026
- * with properties named "imageName", "height", "width", and "url". Otherwise,
12027
- * this plugin may alter your code in unexpected ways (although most likely
12028
- * it will simply give warnings, because it's unlikely there will be valid
12029
- * file assets that will be found and hashed).
12331
+ * a string literal, don't define a property named "fonts" that that refers
12332
+ * to an array expression of object expressions with properties named
12333
+ * "fontName" and "url", and don't define a property called "images" that
12334
+ * refers to an array expression of object expressions with properties named
12335
+ * "imageName", "height", "width", and "url". Otherwise, this plugin may alter
12336
+ * your code in unexpected ways (although most likely it will simply give
12337
+ * warnings, because it's unlikely there will be valid file assets that will
12338
+ * be found and hashed).
12030
12339
  *
12031
12340
  * @param rootDir - root directory of build, usually "dist" because you
12032
12341
  * usually hash only production builds
@@ -12071,10 +12380,16 @@ function hashM2c2kitAssets(rootDir) {
12071
12380
  const literal = node;
12072
12381
  const originalUrlValue = literal.value;
12073
12382
  try {
12074
- const hashedUrlValue = addHashToUrl(originalUrlValue, rootDir);
12383
+ const hashedUrlValue = addHashToUrl(originalUrlValue,
12384
+ /**
12385
+ * by our convention, the wasm file will be served from
12386
+ * the assets directory, so the location is
12387
+ * `assets/${canvasKitWasmUrl}` not `${canvasKitWasmUrl}`
12388
+ */
12389
+ `${rootDir}/assets`);
12075
12390
  literal.value = literal.value.replace(originalUrlValue, hashedUrlValue);
12076
12391
  literal.raw = literal.raw.replace(originalUrlValue, hashedUrlValue);
12077
- addFileToFilesToBeRenamed(rootDir, originalUrlValue, hashedUrlValue, fileRenames);
12392
+ addFileToFilesToBeRenamed(`${rootDir}/assets`, originalUrlValue, hashedUrlValue, fileRenames);
12078
12393
  }
12079
12394
  catch (_a) {
12080
12395
  console.log(`warning: could not hash canvaskit.wasm resource because it was not found at ${originalUrlValue}`);
@@ -12082,25 +12397,48 @@ function hashM2c2kitAssets(rootDir) {
12082
12397
  }
12083
12398
  }
12084
12399
  }
12085
- if (ancestors.length >= 3) {
12086
- const maybeArrayExpression = ancestors.slice(-2)[0];
12087
- if (maybeArrayExpression.type === "ArrayExpression") {
12088
- const maybeProperty = ancestors.slice(-3)[0];
12089
- if (maybeProperty.type === "Property") {
12090
- const property = maybeProperty;
12091
- if (property.key.type === "Identifier" &&
12092
- property.key.name == "fontUrls") {
12093
- // property is fontUrls
12094
- const literal = node;
12095
- const originalUrlValue = literal.value;
12096
- try {
12097
- const hashedUrlValue = addHashToUrl(originalUrlValue, rootDir);
12098
- literal.value = literal.value.replace(originalUrlValue, hashedUrlValue);
12099
- literal.raw = literal.raw.replace(originalUrlValue, hashedUrlValue);
12100
- addFileToFilesToBeRenamed(rootDir, originalUrlValue, hashedUrlValue, fileRenames);
12101
- }
12102
- catch (_b) {
12103
- console.log(`warning: could not hash a font url resource because it was not found at ${originalUrlValue}`);
12400
+ // we'll be looking back 5 levels
12401
+ if (ancestors.length >= 5) {
12402
+ const maybeProperty = ancestors.slice(-2)[0];
12403
+ if (maybeProperty.type === "Property") {
12404
+ const property = maybeProperty;
12405
+ if (property.key.type === "Identifier" &&
12406
+ property.key.name == "url") {
12407
+ // property is url
12408
+ const maybeObjExpression = ancestors.slice(-3)[0];
12409
+ if (maybeObjExpression.type === "ObjectExpression") {
12410
+ const objExpression = maybeObjExpression;
12411
+ const properties = objExpression.properties
12412
+ .filter((p) => p.type === "Property")
12413
+ .map((p) => p);
12414
+ const identifiers = properties
12415
+ .filter((p) => p.key.type === "Identifier")
12416
+ .map((p) => p.key.name);
12417
+ const urlFontAssetProperties = ["fontName", "url"];
12418
+ const propCount = identifiers.filter((i) => urlFontAssetProperties.indexOf(i) !== -1).length;
12419
+ if (propCount === 2) {
12420
+ // the object expression has the 2 properties
12421
+ const maybeArrayExpression = ancestors.slice(-4)[0];
12422
+ if (maybeArrayExpression.type === "ArrayExpression") {
12423
+ const maybeProperty = ancestors.slice(-5)[0];
12424
+ if (maybeProperty.type === "Property") {
12425
+ const property = maybeProperty;
12426
+ if (property.key.type === "Identifier" &&
12427
+ property.key.name == "fonts") {
12428
+ // property is fonts
12429
+ const literal = node;
12430
+ const originalUrlValue = literal.value;
12431
+ try {
12432
+ const hashedUrlValue = addHashToUrl(originalUrlValue, rootDir);
12433
+ literal.value = literal.value.replace(originalUrlValue, hashedUrlValue);
12434
+ literal.raw = literal.raw.replace(originalUrlValue, hashedUrlValue);
12435
+ addFileToFilesToBeRenamed(rootDir, originalUrlValue, hashedUrlValue, fileRenames);
12436
+ }
12437
+ catch (_b) {
12438
+ }
12439
+ }
12440
+ }
12441
+ }
12104
12442
  }
12105
12443
  }
12106
12444
  }