PageSourceSearch

https://www.superctf.com/_snowpack/pkg/interweave.js

js superctf.com collected 2026-10-05 18:19:24 UTC 28,716 bytes, 1,087 lines download raw bytes

1import { r as react } from './common/index-fc424163.js';
2import './common/_commonjsHelpers-8c19dec8.js';
3
4/* eslint-disable no-bitwise, no-magic-numbers, sort-keys */
5// https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories
6
7const TYPE_FLOW = 1;
8const TYPE_SECTION = 1 << 1;
9const TYPE_HEADING = 1 << 2;
10const TYPE_PHRASING = 1 << 3;
11const TYPE_EMBEDDED = 1 << 4;
12const TYPE_INTERACTIVE = 1 << 5;
13const TYPE_PALPABLE = 1 << 6; // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
14
15const tagConfigs = {
16  a: {
17    content: TYPE_FLOW | TYPE_PHRASING,
18    self: false,
19    type: TYPE_FLOW | TYPE_PHRASING | TYPE_INTERACTIVE | TYPE_PALPABLE
20  },
21  address: {
22    invalid: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'address', 'article', 'aside', 'section', 'div', 'header', 'footer'],
23    self: false
24  },
25  audio: {
26    children: ['track', 'source']
27  },
28  br: {
29    type: TYPE_FLOW | TYPE_PHRASING,
30    void: true
31  },
32  body: {
33    content: TYPE_FLOW | TYPE_SECTION | TYPE_HEADING | TYPE_PHRASING | TYPE_EMBEDDED | TYPE_INTERACTIVE | TYPE_PALPABLE
34  },
35  button: {
36    content: TYPE_PHRASING,
37    type: TYPE_FLOW | TYPE_PHRASING | TYPE_INTERACTIVE | TYPE_PALPABLE
38  },
39  caption: {
40    content: TYPE_FLOW,
41    parent: ['table']
42  },
43  col: {
44    parent: ['colgroup'],
45    void: true
46  },
47  colgroup: {
48    children: ['col'],
49    parent: ['table']
50  },
51  details: {
52    children: ['summary'],
53    type: TYPE_FLOW | TYPE_INTERACTIVE | TYPE_PALPABLE
54  },
55  dd: {
56    content: TYPE_FLOW,
57    parent: ['dl']
58  },
59  dl: {
60    children: ['dt', 'dd'],
61    type: TYPE_FLOW
62  },
63  dt: {
64    content: TYPE_FLOW,
65    invalid: ['footer', 'header'],
66    parent: ['dl']
67  },
68  figcaption: {
69    content: TYPE_FLOW,
70    parent: ['figure']
71  },
72  footer: {
73    invalid: ['footer', 'header']
74  },
75  header: {
76    invalid: ['footer', 'header']
77  },
78  hr: {
79    type: TYPE_FLOW,
80    void: true
81  },
82  img: {
83    void: true
84  },
85  li: {
86    content: TYPE_FLOW,
87    parent: ['ul', 'ol', 'menu']
88  },
89  main: {
90    self: false
91  },
92  ol: {
93    children: ['li'],
94    type: TYPE_FLOW
95  },
96  picture: {
97    children: ['source', 'img'],
98    type: TYPE_FLOW | TYPE_PHRASING | TYPE_EMBEDDED
99  },
100  rb: {
101    parent: ['ruby', 'rtc']
102  },
103  rp: {
104    parent: ['ruby', 'rtc']
105  },
106  rt: {
107    content: TYPE_PHRASING,
108    parent: ['ruby', 'rtc']
109  },
110  rtc: {
111    content: TYPE_PHRASING,
112    parent: ['ruby']
113  },
114  ruby: {
115    children: ['rb', 'rp', 'rt', 'rtc']
116  },
117  source: {
118    parent: ['audio', 'video', 'picture'],
119    void: true
120  },
121  summary: {
122    content: TYPE_PHRASING,
123    parent: ['details']
124  },
125  table: {
126    children: ['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr'],
127    type: TYPE_FLOW
128  },
129  tbody: {
130    parent: ['table'],
131    children: ['tr']
132  },
133  td: {
134    content: TYPE_FLOW,
135    parent: ['tr']
136  },
137  tfoot: {
138    parent: ['table'],
139    children: ['tr']
140  },
141  th: {
142    content: TYPE_FLOW,
143    parent: ['tr']
144  },
145  thead: {
146    parent: ['table'],
147    children: ['tr']
148  },
149  tr: {
150    parent: ['table', 'tbody', 'thead', 'tfoot'],
151    children: ['th', 'td']
152  },
153  track: {
154    parent: ['audio', 'video'],
155    void: true
156  },
157  ul: {
158    children: ['li'],
159    type: TYPE_FLOW
160  },
161  video: {
162    children: ['track', 'source']
163  },
164  wbr: {
165    type: TYPE_FLOW | TYPE_PHRASING,
166    void: true
167  }
168};
169
170function createConfigBuilder(config) {
171  return tagName => {
172    tagConfigs[tagName] = { ...config,
173      ...tagConfigs[tagName]
174    };
175  };
176}
177
178['address', 'main', 'div', 'figure', 'p', 'pre'].forEach(createConfigBuilder({
179  content: TYPE_FLOW,
180  type: TYPE_FLOW | TYPE_PALPABLE
181}));
182['abbr', 'b', 'bdi', 'bdo', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'ruby', 'samp', 'strong', 'sub', 'sup', 'time', 'u', 'var'].forEach(createConfigBuilder({
183  content: TYPE_PHRASING,
184  type: TYPE_FLOW | TYPE_PHRASING | TYPE_PALPABLE
185}));
186['p', 'pre'].forEach(createConfigBuilder({
187  content: TYPE_PHRASING,
188  type: TYPE_FLOW | TYPE_PALPABLE
189}));
190['s', 'small', 'span', 'del', 'ins'].forEach(createConfigBuilder({
191  content: TYPE_PHRASING,
192  type: TYPE_FLOW | TYPE_PHRASING
193}));
194['article', 'aside', 'footer', 'header', 'nav', 'section', 'blockquote'].forEach(createConfigBuilder({
195  content: TYPE_FLOW,
196  type: TYPE_FLOW | TYPE_SECTION | TYPE_PALPABLE
197}));
198['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(createConfigBuilder({
199  content: TYPE_PHRASING,
200  type: TYPE_FLOW | TYPE_HEADING | TYPE_PALPABLE
201}));
202['audio', 'canvas', 'iframe', 'img', 'video'].forEach(createConfigBuilder({
203  type: TYPE_FLOW | TYPE_PHRASING | TYPE_EMBEDDED | TYPE_PALPABLE
204})); // Disable this map from being modified
205
206const TAGS = Object.freeze(tagConfigs); // Tags that should never be allowed, even if the allow list is disabled
207
208const BANNED_TAG_LIST = ['applet', 'base', 'body', 'command', 'embed', 'frame', 'frameset', 'head', 'html', 'link', 'meta', 'noscript', 'object', 'script', 'style', 'title'];
209const ALLOWED_TAG_LIST = Object.keys(TAGS).filter(tag => tag !== 'canvas' && tag !== 'iframe'); // Filters apply to HTML attributes
210
211const FILTER_ALLOW = 1;
212const FILTER_DENY = 2;
213const FILTER_CAST_NUMBER = 3;
214const FILTER_CAST_BOOL = 4;
215const FILTER_NO_CAST = 5; // Attributes not listed here will be denied
216// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
217
218const ATTRIBUTES = Object.freeze({
219  alt: FILTER_ALLOW,
220  cite: FILTER_ALLOW,
221  class: FILTER_ALLOW,
222  colspan: FILTER_CAST_NUMBER,
223  controls: FILTER_CAST_BOOL,
224  datetime: FILTER_ALLOW,
225  default: FILTER_CAST_BOOL,
226  disabled: FILTER_CAST_BOOL,
227  dir: FILTER_ALLOW,
228  height: FILTER_ALLOW,
229  href: FILTER_ALLOW,
230  id: FILTER_ALLOW,
231  kind: FILTER_ALLOW,
232  label: FILTER_ALLOW,
233  lang: FILTER_ALLOW,
234  loading: FILTER_ALLOW,
235  loop: FILTER_CAST_BOOL,
236  media: FILTER_ALLOW,
237  muted: FILTER_CAST_BOOL,
238  poster: FILTER_ALLOW,
239  rel: FILTER_ALLOW,
240  role: FILTER_ALLOW,
241  rowspan: FILTER_CAST_NUMBER,
242  scope: FILTER_ALLOW,
243  sizes: FILTER_ALLOW,
244  span: FILTER_CAST_NUMBER,
245  start: FILTER_CAST_NUMBER,
246  style: FILTER_NO_CAST,
247  src: FILTER_ALLOW,
248  srclang: FILTER_ALLOW,
249  srcset: FILTER_ALLOW,
250  target: FILTER_ALLOW,
251  title: FILTER_ALLOW,
252  type: FILTER_ALLOW,
253  width: FILTER_ALLOW
254}); // Attributes to camel case for React props
255
256const ATTRIBUTES_TO_PROPS = Object.freeze({
257  class: 'className',
258  colspan: 'colSpan',
259  datetime: 'dateTime',
260  rowspan: 'rowSpan',
261  srclang: 'srcLang',
262  srcset: 'srcSet'
263});
264
265function _extends() {
266  _extends = Object.assign || function (target) {
267    for (var i = 1; i < arguments.length; i++) {
268      var source = arguments[i];
269
270      for (var key in source) {
271        if (Object.prototype.hasOwnProperty.call(source, key)) {
272          target[key] = source[key];
273        }
274      }
275    }
276
277    return target;
278  };
279
280  return _extends.apply(this, arguments);
281}
282
283function Element({
284  attributes = {},
285  className,
286  children = null,
287  selfClose = false,
288  tagName
289}) {
290  const Tag = tagName;
291  return selfClose ? /*#__PURE__*/react.createElement(Tag, _extends({
292    className: className
293  }, attributes)) : /*#__PURE__*/react.createElement(Tag, _extends({
294    className: className
295  }, attributes), children);
296}
297
298class Filter {
299  /**
300   * Filter and clean an HTML attribute value.
301   */
302  attribute(name, value) {
303    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
304    return value;
305  }
306  /**
307   * Filter and clean an HTML node.
308   */
309
310
311  node(name, node) {
312    return node;
313  }
314
315}
316
317/*!
318 * escape-html
319 * Copyright(c) 2012-2013 TJ Holowaychuk
320 * Copyright(c) 2015 Andreas Lubbe
321 * Copyright(c) 2015 Tiancheng "Timothy" Gu
322 * MIT Licensed
323 */
324
325/**
326 * Module variables.
327 * @private
328 */
329
330var matchHtmlRegExp = /["'&<>]/;
331
332/**
333 * Module exports.
334 * @public
335 */
336
337var escapeHtml_1 = escapeHtml;
338
339/**
340 * Escape special characters in the given string of html.
341 *
342 * @param  {string} string The string to escape for inserting into HTML
343 * @return {string}
344 * @public
345 */
346
347function escapeHtml(string) {
348  var str = '' + string;
349  var match = matchHtmlRegExp.exec(str);
350
351  if (!match) {
352    return str;
353  }
354
355  var escape;
356  var html = '';
357  var index = 0;
358  var lastIndex = 0;
359
360  for (index = match.index; index < str.length; index++) {
361    switch (str.charCodeAt(index)) {
362      case 34: // "
363        escape = '&quot;';
364        break;
365      case 38: // &
366        escape = '&amp;';
367        break;
368      case 39: // '
369        escape = '&#39;';
370        break;
371      case 60: // <
372        escape = '&lt;';
373        break;
374      case 62: // >
375        escape = '&gt;';
376        break;
377      default:
378        continue;
379    }
380
381    if (lastIndex !== index) {
382      html += str.substring(lastIndex, index);
383    }
384
385    lastIndex = index + 1;
386    html += escape;
387  }
388
389  return lastIndex !== index
390    ? html + str.substring(lastIndex, index)
391    : html;
392}
393
394function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
395const INVALID_STYLES = /(url|image|image-set)\(/i;
396
397class StyleFilter extends Filter {
398  attribute(name, value) {
399    if (name === 'style') {
400      Object.keys(value).forEach(key => {
401        if (String(value[key]).match(INVALID_STYLES)) {
402          // eslint-disable-next-line no-param-reassign
403          delete value[key];
404        }
405      });
406    } // eslint-disable-next-line @typescript-eslint/no-unsafe-return
407
408
409    return value;
410  }
411
412}
413/* eslint-disable no-bitwise, no-cond-assign, complexity, @typescript-eslint/no-unsafe-return */
414
415
416const ELEMENT_NODE = 1;
417const TEXT_NODE = 3;
418const INVALID_ROOTS = /^<(!doctype|(html|head|body)(\s|>))/i;
419const ALLOWED_ATTRS = /^(aria-|data-|\w+:)/iu;
420const OPEN_TOKEN = /{{{(\w+)\/?}}}/;
421
422function createDocument() {
423  // Maybe SSR? Just do nothing instead of crashing!
424  if (typeof window === 'undefined' || typeof document === 'undefined') {
425    return undefined;
426  }
427
428  return document.implementation.createHTMLDocument('Interweave');
429}
430
431class Parser {
432  constructor(markup, props = {}, matchers = [], filters = []) {
433    var _props$allowList;
434
435    _defineProperty(this, "allowed", void 0);
436
437    _defineProperty(this, "banned", void 0);
438
439    _defineProperty(this, "blocked", void 0);
440
441    _defineProperty(this, "container", void 0);
442
443    _defineProperty(this, "content", []);
444
445    _defineProperty(this, "props", void 0);
446
447    _defineProperty(this, "matchers", void 0);
448
449    _defineProperty(this, "filters", void 0);
450
451    _defineProperty(this, "keyIndex", void 0);
452
453    this.props = props;
454    this.matchers = matchers;
455    this.filters = [...filters, new StyleFilter()];
456    this.keyIndex = -1;
457    this.container = this.createContainer(markup || '');
458    this.allowed = new Set((_props$allowList = props.allowList) !== null && _props$allowList !== void 0 ? _props$allowList : ALLOWED_TAG_LIST);
459    this.banned = new Set(BANNED_TAG_LIST);
460    this.blocked = new Set(props.blockList);
461  }
462  /**
463   * Loop through and apply all registered attribute filters.
464   */
465
466
467  applyAttributeFilters(name, value) {
468    return this.filters.reduce((nextValue, filter) => nextValue !== null && typeof filter.attribute === 'function' ? filter.attribute(name, nextValue) : nextValue, value);
469  }
470  /**
471   * Loop through and apply all registered node filters.
472   */
473
474
475  applyNodeFilters(name, node) {
476    // Allow null to be returned
477    return this.filters.reduce((nextNode, filter) => nextNode !== null && typeof filter.node === 'function' ? filter.node(name, nextNode) : nextNode, node);
478  }
479  /**
480   * Loop through and apply all registered matchers to the string.
481   * If a match is found, create a React element, and build a new array.
482   * This array allows React to interpolate and render accordingly.
483   */
484
485
486  applyMatchers(string, parentConfig) {
487    const elements = {};
488    const {
489      props
490    } = this;
491    let matchedString = string;
492    let elementIndex = 0;
493    let parts = null;
494    this.matchers.forEach(matcher => {
495      const tagName = matcher.asTag().toLowerCase();
496      const config = this.getTagConfig(tagName); // Skip matchers that have been disabled from props or are not supported
497
498      if (props[matcher.inverseName] || !this.isTagAllowed(tagName)) {
499        return;
500      } // Skip matchers in which the child cannot be rendered
501
502
503      if (!this.canRenderChild(parentConfig, config)) {
504        return;
505      } // Continuously trigger the matcher until no matches are found
506
507
508      let tokenizedString = '';
509
510      while (matchedString && (parts = matcher.match(matchedString))) {
511        const {
512          index,
513          length,
514          match,
515          valid,
516          void: isVoid,
517          ...partProps
518        } = parts;
519        const tokenName = matcher.propName + String(elementIndex); // Piece together a new string with interpolated tokens
520
521        if (index > 0) {
522          tokenizedString += matchedString.slice(0, index);
523        }
524
525        if (valid) {
526          tokenizedString += isVoid ? `{{{${tokenName}/}}}` : `{{{${tokenName}}}}${match}{{{/${tokenName}}}}`;
527          this.keyIndex += 1;
528          elementIndex += 1;
529          elements[tokenName] = {
530            children: match,
531            matcher,
532            props: { ...props,
533              ...partProps,
534              key: this.keyIndex
535            }
536          };
537        } else {
538          tokenizedString += match;
539        } // Reduce the string being matched against,
540        // otherwise we end up in an infinite loop!
541
542
543        if (matcher.greedy) {
544          matchedString = tokenizedString + matchedString.slice(index + length);
545          tokenizedString = '';
546        } else {
547          // eslint-disable-next-line unicorn/explicit-length-check
548          matchedString = matchedString.slice(index + (length || match.length));
549        }
550      } // Update the matched string with the tokenized string,
551      // so that the next matcher can apply to it.
552
553
554      if (!matcher.greedy) {
555        matchedString = tokenizedString + matchedString;
556      }
557    });
558
559    if (elementIndex === 0) {
560      return string;
561    }
562
563    return this.replaceTokens(matchedString, elements);
564  }
565  /**
566   * Determine whether the child can be rendered within the parent.
567   */
568
569
570  canRenderChild(parentConfig, childConfig) {
571    if (!parentConfig.tagName || !childConfig.tagName) {
572      return false;
573    } // No children
574
575
576    if (parentConfig.void) {
577      return false;
578    } // Valid children
579
580
581    if (parentConfig.children.length > 0) {
582      return parentConfig.children.includes(childConfig.tagName);
583    }
584
585    if (parentConfig.invalid.length > 0 && parentConfig.invalid.includes(childConfig.tagName)) {
586      return false;
587    } // Valid parent
588
589
590    if (childConfig.parent.length > 0) {
591      return childConfig.parent.includes(parentConfig.tagName);
592    } // Self nesting
593
594
595    if (!parentConfig.self && parentConfig.tagName === childConfig.tagName) {
596      return false;
597    } // Content category type
598
599
600    return Boolean(parentConfig && parentConfig.content & childConfig.type);
601  }
602  /**
603   * Convert line breaks in a string to HTML `<br/>` tags.
604   * If the string contains HTML, we should not convert anything,
605   * as line breaks should be handled by `<br/>`s in the markup itself.
606   */
607
608
609  convertLineBreaks(markup) {
610    const {
611      noHtml,
612      disableLineBreaks
613    } = this.props;
614
615    if (noHtml || disableLineBreaks || markup.match(/<((?:\/[ a-z]+)|(?:[ a-z]+\/))>/gi)) {
616      return markup;
617    } // Replace carriage returns
618
619
620    let nextMarkup = markup.replace(/\r\n/g, '\n'); // Replace long line feeds
621
622    nextMarkup = nextMarkup.replace(/\n{3,}/g, '\n\n\n'); // Replace line feeds with `<br/>`s
623
624    nextMarkup = nextMarkup.replace(/\n/g, '<br/>');
625    return nextMarkup;
626  }
627  /**
628   * Create a detached HTML document that allows for easy HTML
629   * parsing while not triggering scripts or loading external
630   * resources.
631   */
632
633
634  createContainer(markup) {
635    var _this$props$container;
636
637    const factory = typeof global !== 'undefined' && global.INTERWEAVE_SSR_POLYFILL || createDocument;
638    const doc = factory();
639
640    if (!doc) {
641      return undefined;
642    }
643
644    const tag = (_this$props$container = this.props.containerTagName) !== null && _this$props$container !== void 0 ? _this$props$container : 'body';
645    const el = tag === 'body' || tag === 'fragment' ? doc.body : doc.createElement(tag);
646
647    if (markup.match(INVALID_ROOTS)) ; else {
648      el.innerHTML = this.convertLineBreaks(this.props.escapeHtml ? escapeHtml_1(markup) : markup);
649    }
650
651    return el;
652  }
653  /**
654   * Convert an elements attribute map to an object map.
655   * Returns null if no attributes are defined.
656   */
657
658
659  extractAttributes(node) {
660    const {
661      allowAttributes
662    } = this.props;
663    const attributes = {};
664    let count = 0;
665
666    if (node.nodeType !== ELEMENT_NODE || !node.attributes) {
667      return null;
668    } // @ts-expect-error Cant type iterator
669
670
671    [...node.attributes].forEach(attr => {
672      const {
673        name,
674        value
675      } = attr;
676      const newName = name.toLowerCase();
677      const filter = ATTRIBUTES[newName] || ATTRIBUTES[name]; // Verify the node is safe from attacks
678
679      if (!this.isSafe(node)) {
680        return;
681      } // Do not allow denied attributes, excluding ARIA attributes
682      // Do not allow events or XSS injections
683
684
685      if (!newName.match(ALLOWED_ATTRS) && (!allowAttributes && (!filter || filter === FILTER_DENY) || newName.startsWith('on') || value.replace(/(\s|\0|&#x0([9AD]);)/, '').match(/(javascript|vbscript|livescript|xss):/i))) {
686        return;
687      } // Apply attribute filters
688
689
690      let newValue = newName === 'style' ? this.extractStyleAttribute(node) : value; // Cast to boolean
691
692      if (filter === FILTER_CAST_BOOL) {
693        newValue = true; // Cast to number
694      } else if (filter === FILTER_CAST_NUMBER) {
695        newValue = Number.parseFloat(String(newValue)); // Cast to string
696      } else if (filter !== FILTER_NO_CAST) {
697        newValue = String(newValue);
698      }
699
700      attributes[ATTRIBUTES_TO_PROPS[newName] || newName] = this.applyAttributeFilters(newName, newValue);
701      count += 1;
702    });
703
704    if (count === 0) {
705      return null;
706    }
707
708    return attributes;
709  }
710  /**
711   * Extract the style attribute as an object and remove values that allow for attack vectors.
712   */
713
714
715  extractStyleAttribute(node) {
716    const styles = {}; // eslint-disable-next-line unicorn/prefer-spread
717
718    Array.from(node.style).forEach(key => {
719      const value = node.style[key];
720
721      if (typeof value === 'string' || typeof value === 'number') {
722        styles[key.replace(/-([a-z])/g, (match, letter) => String(letter).toUpperCase())] = value;
723      }
724    });
725    return styles;
726  }
727  /**
728   * Return configuration for a specific tag.
729   */
730
731
732  getTagConfig(tagName) {
733    const common = {
734      children: [],
735      content: 0,
736      invalid: [],
737      parent: [],
738      self: true,
739      tagName: '',
740      type: 0,
741      void: false
742    }; // Only spread when a tag config exists,
743    // otherwise we use the empty `tagName`
744    // for parent config inheritance.
745
746    if (TAGS[tagName]) {
747      return { ...common,
748        ...TAGS[tagName],
749        tagName
750      };
751    }
752
753    return common;
754  }
755  /**
756   * Verify that a node is safe from XSS and injection attacks.
757   */
758
759
760  isSafe(node) {
761    // URLs should only support HTTP, email and phone numbers
762    if (typeof HTMLAnchorElement !== 'undefined' && node instanceof HTMLAnchorElement) {
763      const href = node.getAttribute('href'); // Fragment protocols start with about:
764      // So let's just allow them
765
766      if (href !== null && href !== void 0 && href.startsWith('#')) {
767        return true;
768      }
769
770      const protocol = node.protocol.toLowerCase();
771      return protocol === ':' || protocol === 'http:' || protocol === 'https:' || protocol === 'mailto:' || protocol === 'tel:';
772    }
773
774    return true;
775  }
776  /**
777   * Verify that an HTML tag is allowed to render.
778   */
779
780
781  isTagAllowed(tagName) {
782    if (this.banned.has(tagName) || this.blocked.has(tagName)) {
783      return false;
784    } // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
785
786
787    return this.props.allowElements || this.allowed.has(tagName);
788  }
789  /**
790   * Parse the markup by injecting it into a detached document,
791   * while looping over all child nodes and generating an
792   * array to interpolate into JSX.
793   */
794
795
796  parse() {
797    if (!this.container) {
798      return [];
799    }
800
801    return this.parseNode(this.container, this.getTagConfig(this.container.nodeName.toLowerCase()));
802  }
803  /**
804   * Loop over the nodes children and generate a
805   * list of text nodes and React elements.
806   */
807
808
809  parseNode(parentNode, parentConfig) {
810    const {
811      noHtml,
812      noHtmlExceptMatchers,
813      allowElements,
814      transform,
815      transformOnlyAllowList
816    } = this.props;
817    let content = [];
818    let mergedText = ''; // @ts-expect-error Cant type iterator
819
820    [...parentNode.childNodes].forEach(node => {
821      // Create React elements from HTML elements
822      if (node.nodeType === ELEMENT_NODE) {
823        const tagName = node.nodeName.toLowerCase();
824        const config = this.getTagConfig(tagName); // Persist any previous text
825
826        if (mergedText) {
827          content.push(mergedText);
828          mergedText = '';
829        } // Apply node filters first
830
831
832        const nextNode = this.applyNodeFilters(tagName, node);
833
834        if (!nextNode) {
835          return;
836        } // Apply transformation second
837
838
839        let children;
840
841        if (transform && !(transformOnlyAllowList && !this.isTagAllowed(tagName))) {
842          this.keyIndex += 1;
843          const key = this.keyIndex; // Must occur after key is set
844
845          children = this.parseNode(nextNode, config);
846          const transformed = transform(nextNode, children, config);
847
848          if (transformed === null) {
849            return;
850          }
851
852          if (typeof transformed !== 'undefined') {
853            content.push( /*#__PURE__*/react.cloneElement(transformed, {
854              key
855            }));
856            return;
857          } // Reset as we're not using the transformation
858
859
860          this.keyIndex = key - 1;
861        } // Never allow these tags (except via a transformer)
862
863
864        if (this.banned.has(tagName)) {
865          return;
866        } // Only render when the following criteria is met:
867        //  - HTML has not been disabled
868        //  - Tag is allowed
869        //  - Child is valid within the parent
870
871
872        if (!(noHtml || noHtmlExceptMatchers && tagName !== 'br') && this.isTagAllowed(tagName) && (allowElements || this.canRenderChild(parentConfig, config))) {
873          var _children;
874
875          this.keyIndex += 1; // Build the props as it makes it easier to test
876
877          const attributes = this.extractAttributes(nextNode);
878          const elementProps = {
879            tagName
880          };
881
882          if (attributes) {
883            elementProps.attributes = attributes;
884          }
885
886          if (config.void) {
887            elementProps.selfClose = config.void;
888          }
889
890          content.push( /*#__PURE__*/react.createElement(Element, { ...elementProps,
891            key: this.keyIndex
892          }, (_children = children) !== null && _children !== void 0 ? _children : this.parseNode(nextNode, config))); // Render the children of the current element only.
893          // Important: If the current element is not allowed,
894          // use the parent element for the next scope.
895        } else {
896          content = [...content, ...this.parseNode(nextNode, config.tagName ? config : parentConfig)];
897        } // Apply matchers if a text node
898
899      } else if (node.nodeType === TEXT_NODE) {
900        const text = noHtml && !noHtmlExceptMatchers ? node.textContent : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
901        this.applyMatchers(node.textContent || '', parentConfig);
902
903        if (Array.isArray(text)) {
904          content = [...content, ...text];
905        } else {
906          mergedText += text;
907        }
908      }
909    });
910
911    if (mergedText) {
912      content.push(mergedText);
913    }
914
915    return content;
916  }
917  /**
918   * Deconstruct the string into an array, by replacing custom tokens with React elements,
919   * so that React can render it correctly.
920   */
921
922
923  replaceTokens(tokenizedString, elements) {
924    if (!tokenizedString.includes('{{{')) {
925      return tokenizedString;
926    }
927
928    const nodes = [];
929    let text = tokenizedString;
930    let open = null; // Find an open token tag
931
932    while (open = text.match(OPEN_TOKEN)) {
933      const [match, tokenName] = open;
934      const startIndex = open.index;
935      const isVoid = match.includes('/');
936
937
938      if (startIndex > 0) {
939        nodes.push(text.slice(0, startIndex)); // Reduce text so that the closing tag will be found after the opening
940
941        text = text.slice(startIndex);
942      }
943
944      const {
945        children,
946        matcher,
947        props: elementProps
948      } = elements[tokenName];
949      let endIndex; // Use tag as-is if void
950
951      if (isVoid) {
952        endIndex = match.length;
953        nodes.push(matcher.createElement(children, elementProps)); // Find the closing tag if not void
954      } else {
955        const close = text.match(new RegExp(`{{{/${tokenName}}}}`));
956
957        endIndex = close.index + close[0].length;
958        nodes.push(matcher.createElement(this.replaceTokens(text.slice(match.length, close.index), elements), elementProps));
959      } // Reduce text for the next interation
960
961
962      text = text.slice(endIndex);
963    } // Extra the remaining text
964
965
966    if (text.length > 0) {
967      nodes.push(text);
968    } // Reduce to a string if possible
969
970
971    if (nodes.length === 0) {
972      return '';
973    }
974
975    if (nodes.length === 1 && typeof nodes[0] === 'string') {
976      return nodes[0];
977    }
978
979    return nodes;
980  }
981
982}
983/* eslint-disable react/jsx-fragments */
984
985
986function Markup(props) {
987  var _ref;
988
989  const {
990    attributes,
991    className,
992    containerTagName,
993    content,
994    emptyContent,
995    parsedContent,
996    tagName,
997    noWrap: baseNoWrap
998  } = props;
999  const tag = (_ref = containerTagName !== null && containerTagName !== void 0 ? containerTagName : tagName) !== null && _ref !== void 0 ? _ref : 'span';
1000  const noWrap = tag === 'fragment' ? true : baseNoWrap;
1001  let mainContent;
1002
1003  if (parsedContent) {
1004    mainContent = parsedContent;
1005  } else {
1006    const markup = new Parser(content !== null && content !== void 0 ? content : '', props).parse();
1007
1008    if (markup.length > 0) {
1009      mainContent = markup;
1010    }
1011  }
1012
1013  if (!mainContent) {
1014    mainContent = emptyContent;
1015  }
1016
1017  if (noWrap) {
1018    // eslint-disable-next-line react/jsx-no-useless-fragment
1019    return /*#__PURE__*/react.createElement(react.Fragment, null, mainContent);
1020  }
1021
1022  return /*#__PURE__*/react.createElement(Element, {
1023    attributes: attributes,
1024    className: className,
1025    tagName: tag
1026  }, mainContent);
1027}
1028/* eslint-disable promise/prefer-await-to-callbacks */
1029
1030
1031function Interweave(props) {
1032  const {
1033    attributes,
1034    className,
1035    content = '',
1036    disableFilters = false,
1037    disableMatchers = false,
1038    emptyContent = null,
1039    filters = [],
1040    matchers = [],
1041    onAfterParse = null,
1042    onBeforeParse = null,
1043    tagName = 'span',
1044    noWrap = false,
1045    ...parserProps
1046  } = props;
1047  const allMatchers = disableMatchers ? [] : matchers;
1048  const allFilters = disableFilters ? [] : filters;
1049  const beforeCallbacks = onBeforeParse ? [onBeforeParse] : [];
1050  const afterCallbacks = onAfterParse ? [onAfterParse] : []; // Inherit callbacks from matchers
1051
1052  allMatchers.forEach(matcher => {
1053    if (matcher.onBeforeParse) {
1054      beforeCallbacks.push(matcher.onBeforeParse.bind(matcher));
1055    }
1056
1057    if (matcher.onAfterParse) {
1058      afterCallbacks.push(matcher.onAfterParse.bind(matcher));
1059    }
1060  }); // Trigger before callbacks
1061
1062  const markup = beforeCallbacks.reduce((string, callback) => {
1063    const nextString = callback(string, props);
1064
1065    return nextString;
1066  }, content !== null && content !== void 0 ? content : ''); // Parse the markup
1067
1068  const parser = new Parser(markup, parserProps, allMatchers, allFilters); // Trigger after callbacks
1069
1070  const nodes = afterCallbacks.reduce((parserNodes, callback) => {
1071    const nextNodes = callback(parserNodes, props);
1072
1073    return nextNodes;
1074  }, parser.parse());
1075  return /*#__PURE__*/react.createElement(Markup, {
1076    attributes: attributes,
1077    className: className // eslint-disable-next-line react/destructuring-assignment
1078    ,
1079    containerTagName: props.containerTagName,
1080    emptyContent: emptyContent,
1081    noWrap: noWrap,
1082    parsedContent: nodes.length === 0 ? undefined : nodes,
1083    tagName: tagName
1084  });
1085}
1086
1087export { Interweave };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.