@wdprlib/render 1.4.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/index.cjs +126 -393
  2. package/dist/index.js +117 -384
  3. package/package.json +5 -3
  4. package/src/context.ts +422 -0
  5. package/src/elements/bibliography.ts +123 -0
  6. package/src/elements/clear-float.ts +27 -0
  7. package/src/elements/code.ts +49 -0
  8. package/src/elements/collapsible.ts +105 -0
  9. package/src/elements/color.ts +32 -0
  10. package/src/elements/container.ts +302 -0
  11. package/src/elements/date.ts +59 -0
  12. package/src/elements/embed-block.ts +327 -0
  13. package/src/elements/embed.ts +166 -0
  14. package/src/elements/expr.ts +102 -0
  15. package/src/elements/footnote.ts +76 -0
  16. package/src/elements/html.ts +79 -0
  17. package/src/elements/iframe.ts +44 -0
  18. package/src/elements/iftags.ts +118 -0
  19. package/src/elements/image.ts +154 -0
  20. package/src/elements/include.ts +43 -0
  21. package/src/elements/index.ts +35 -0
  22. package/src/elements/line-break.ts +22 -0
  23. package/src/elements/link.ts +201 -0
  24. package/src/elements/list.ts +241 -0
  25. package/src/elements/math.ts +177 -0
  26. package/src/elements/module/backlinks.ts +28 -0
  27. package/src/elements/module/categories.ts +27 -0
  28. package/src/elements/module/index.ts +67 -0
  29. package/src/elements/module/join.ts +33 -0
  30. package/src/elements/module/listpages.ts +27 -0
  31. package/src/elements/module/listusers.ts +27 -0
  32. package/src/elements/module/page-tree.ts +27 -0
  33. package/src/elements/module/rate.ts +44 -0
  34. package/src/elements/tab-view.ts +75 -0
  35. package/src/elements/table.ts +101 -0
  36. package/src/elements/text.ts +57 -0
  37. package/src/elements/toc.ts +147 -0
  38. package/src/elements/user.ts +79 -0
  39. package/src/escape.ts +829 -0
  40. package/src/hash.ts +62 -0
  41. package/src/index.ts +26 -0
  42. package/src/libs/highlighter/engine.ts +352 -0
  43. package/src/libs/highlighter/index.ts +70 -0
  44. package/src/libs/highlighter/languages/cpp.ts +345 -0
  45. package/src/libs/highlighter/languages/css.ts +104 -0
  46. package/src/libs/highlighter/languages/diff.ts +154 -0
  47. package/src/libs/highlighter/languages/dtd.ts +99 -0
  48. package/src/libs/highlighter/languages/html.ts +59 -0
  49. package/src/libs/highlighter/languages/java.ts +251 -0
  50. package/src/libs/highlighter/languages/javascript.ts +213 -0
  51. package/src/libs/highlighter/languages/php.ts +433 -0
  52. package/src/libs/highlighter/languages/python.ts +308 -0
  53. package/src/libs/highlighter/languages/ruby.ts +360 -0
  54. package/src/libs/highlighter/languages/sql.ts +125 -0
  55. package/src/libs/highlighter/languages/xml.ts +68 -0
  56. package/src/libs/highlighter/types.ts +44 -0
  57. package/src/render.ts +231 -0
  58. package/src/types.ts +140 -0
package/dist/index.js CHANGED
@@ -304,10 +304,76 @@ function normalizeCssValue(value) {
304
304
  result = result.replace(/[\s\u0000-\u001f\u007f-\u009f]/g, "");
305
305
  return result.toLowerCase();
306
306
  }
307
+ function isUrlAllowed(rawUrl) {
308
+ let url = rawUrl;
309
+ if (url.length >= 2) {
310
+ const first = url[0];
311
+ const last = url[url.length - 1];
312
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
313
+ url = url.slice(1, -1);
314
+ }
315
+ }
316
+ if (url === "")
317
+ return true;
318
+ if (url.startsWith("#"))
319
+ return true;
320
+ if (url.startsWith("./") || url.startsWith("../"))
321
+ return true;
322
+ if (url.startsWith("//"))
323
+ return true;
324
+ if (url.startsWith("/"))
325
+ return true;
326
+ if (url.startsWith("http://") || url.startsWith("https://"))
327
+ return true;
328
+ if (url.startsWith("data:image/")) {
329
+ const after = url.slice("data:image/".length);
330
+ const sep = Math.min(after.indexOf(";") === -1 ? after.length : after.indexOf(";"), after.indexOf(",") === -1 ? after.length : after.indexOf(","));
331
+ const mime = after.slice(0, sep);
332
+ if (mime === "png" || mime === "jpeg" || mime === "jpg" || mime === "gif" || mime === "webp") {
333
+ return true;
334
+ }
335
+ }
336
+ return false;
337
+ }
338
+ function* iterateUrls(normalized) {
339
+ let searchPos = 0;
340
+ while (searchPos < normalized.length) {
341
+ const idx = normalized.indexOf("url(", searchPos);
342
+ if (idx === -1)
343
+ return;
344
+ let depth = 1;
345
+ let quoteChar = null;
346
+ let i = idx + 4;
347
+ while (i < normalized.length && depth > 0) {
348
+ const ch = normalized[i];
349
+ if (quoteChar !== null) {
350
+ if (ch === quoteChar)
351
+ quoteChar = null;
352
+ } else if (ch === '"' || ch === "'") {
353
+ quoteChar = ch;
354
+ } else if (ch === "(") {
355
+ depth++;
356
+ } else if (ch === ")") {
357
+ depth--;
358
+ }
359
+ i++;
360
+ }
361
+ if (depth > 0) {
362
+ yield { inner: normalized.slice(idx + 4), malformed: true };
363
+ return;
364
+ }
365
+ yield { inner: normalized.slice(idx + 4, i - 1), malformed: false };
366
+ searchPos = i;
367
+ }
368
+ }
307
369
  function isDangerousCssValue(value) {
308
370
  const normalized = normalizeCssValue(value);
309
- if (normalized.includes("url("))
310
- return true;
371
+ for (const { inner, malformed } of iterateUrls(normalized)) {
372
+ if (malformed)
373
+ return true;
374
+ if (!isUrlAllowed(inner))
375
+ return true;
376
+ }
311
377
  if (normalized.includes("expression("))
312
378
  return true;
313
379
  if (normalized.includes("-moz-binding"))
@@ -318,21 +384,61 @@ function isDangerousCssValue(value) {
318
384
  return true;
319
385
  return false;
320
386
  }
387
+ function splitDeclarations(style) {
388
+ const out = [];
389
+ let buf = "";
390
+ let parenDepth = 0;
391
+ let quoteChar = null;
392
+ for (const ch of style) {
393
+ if (quoteChar !== null) {
394
+ buf += ch;
395
+ if (ch === quoteChar)
396
+ quoteChar = null;
397
+ continue;
398
+ }
399
+ if (ch === '"' || ch === "'") {
400
+ quoteChar = ch;
401
+ buf += ch;
402
+ continue;
403
+ }
404
+ if (ch === "(") {
405
+ parenDepth++;
406
+ buf += ch;
407
+ continue;
408
+ }
409
+ if (ch === ")") {
410
+ if (parenDepth > 0)
411
+ parenDepth--;
412
+ buf += ch;
413
+ continue;
414
+ }
415
+ if (ch === ";" && parenDepth === 0) {
416
+ out.push(buf);
417
+ buf = "";
418
+ continue;
419
+ }
420
+ buf += ch;
421
+ }
422
+ if (buf.length > 0)
423
+ out.push(buf);
424
+ return out;
425
+ }
321
426
  function sanitizeStyleValue(style) {
322
427
  const endsWithSemicolon = style.trimEnd().endsWith(";");
323
- const declarations = style.split(";").map((d) => d.trim()).filter(Boolean);
428
+ const declarations = splitDeclarations(style).map((d) => d.trim()).filter(Boolean);
324
429
  const safe = [];
325
430
  for (const decl of declarations) {
326
431
  const colonIdx = decl.indexOf(":");
327
432
  if (colonIdx === -1)
328
433
  continue;
329
- const property = decl.slice(0, colonIdx).trim().toLowerCase();
434
+ const property = decl.slice(0, colonIdx).trim();
330
435
  const value = decl.slice(colonIdx + 1).trim();
331
436
  if (isDangerousCssValue(value))
332
437
  continue;
333
- if (property.startsWith("-moz-binding"))
438
+ const normalisedProperty = normalizeCssValue(property);
439
+ if (normalisedProperty.startsWith("-moz-binding"))
334
440
  continue;
335
- if (property === "behavior")
441
+ if (normalisedProperty === "behavior")
336
442
  continue;
337
443
  safe.push(decl);
338
444
  }
@@ -4558,6 +4664,9 @@ function generateDefaultUrl(pageName, contents) {
4558
4664
  return path;
4559
4665
  }
4560
4666
  function renderHtmlBlock(ctx, data) {
4667
+ if (ctx.settings.allowHtmlBlocks === false) {
4668
+ return;
4669
+ }
4561
4670
  const index = ctx.nextHtmlBlockIndex();
4562
4671
  const pageName = ctx.page?.pageName ?? "";
4563
4672
  const callbackUrl = ctx.options.resolvers?.htmlBlockUrl?.(index);
@@ -4660,382 +4769,12 @@ function formatDate(date, format) {
4660
4769
  return format.replace(/%Y/g, String(date.getFullYear())).replace(/%m/g, String(date.getMonth() + 1).padStart(2, "0")).replace(/%d/g, String(date.getDate()).padStart(2, "0")).replace(/%H/g, String(date.getHours()).padStart(2, "0")).replace(/%M/g, String(date.getMinutes()).padStart(2, "0")).replace(/%S/g, String(date.getSeconds()).padStart(2, "0"));
4661
4770
  }
4662
4771
 
4663
- // packages/render/src/utils/expr-eval.ts
4664
- var FALSE_VALUES = new Set(["false", "null", "", "0"]);
4665
- function isTruthy(value) {
4666
- return !FALSE_VALUES.has(value.toLowerCase().trim());
4667
- }
4668
- var MAX_EXPRESSION_LENGTH = 256;
4669
- function isTruthyNum(n) {
4670
- return n !== 0 && !Number.isNaN(n);
4671
- }
4672
- function evaluateExpression(expr) {
4673
- try {
4674
- if (expr.length > MAX_EXPRESSION_LENGTH) {
4675
- return { success: false, error: "expression too long" };
4676
- }
4677
- if (expr.trim() === "") {
4678
- return { success: false, error: "empty expression" };
4679
- }
4680
- const tokens = tokenize2(expr);
4681
- if (tokens.length <= 1) {
4682
- return { success: false, error: "empty expression" };
4683
- }
4684
- const parser = new ExprParser(tokens);
4685
- const result = parser.parse();
4686
- if (!Number.isFinite(result)) {
4687
- return { success: false, error: "division by zero" };
4688
- }
4689
- return { success: true, value: result };
4690
- } catch (e) {
4691
- const msg = e instanceof Error ? e.message : "unknown error";
4692
- return { success: false, error: msg };
4693
- }
4694
- }
4695
- function tokenize2(expr) {
4696
- const tokens = [];
4697
- let i = 0;
4698
- while (i < expr.length) {
4699
- const ch = expr[i];
4700
- if (/\s/.test(ch)) {
4701
- i++;
4702
- continue;
4703
- }
4704
- if (/\d/.test(ch) || ch === "." && /\d/.test(expr[i + 1] ?? "")) {
4705
- let numStr = "";
4706
- let hasDot = false;
4707
- while (i < expr.length) {
4708
- const c = expr[i];
4709
- if (c === ".") {
4710
- if (hasDot)
4711
- break;
4712
- hasDot = true;
4713
- } else if (!/\d/.test(c)) {
4714
- break;
4715
- }
4716
- numStr += c;
4717
- i++;
4718
- }
4719
- const num = parseFloat(numStr);
4720
- if (!Number.isFinite(num)) {
4721
- throw new Error("Invalid number");
4722
- }
4723
- tokens.push({ kind: "NUMBER", value: num });
4724
- continue;
4725
- }
4726
- if (/[a-zA-Z_]/.test(ch)) {
4727
- let id = "";
4728
- while (i < expr.length) {
4729
- const c = expr[i];
4730
- if (!/[a-zA-Z0-9_]/.test(c))
4731
- break;
4732
- id += c;
4733
- i++;
4734
- }
4735
- tokens.push({ kind: "IDENTIFIER", value: id.toLowerCase() });
4736
- continue;
4737
- }
4738
- if (ch === "<" && expr[i + 1] === "=") {
4739
- tokens.push({ kind: "LE", value: "<=" });
4740
- i += 2;
4741
- continue;
4742
- }
4743
- if (ch === ">" && expr[i + 1] === "=") {
4744
- tokens.push({ kind: "GE", value: ">=" });
4745
- i += 2;
4746
- continue;
4747
- }
4748
- if (ch === "!" && expr[i + 1] === "=") {
4749
- tokens.push({ kind: "NE", value: "!=" });
4750
- i += 2;
4751
- continue;
4752
- }
4753
- if (ch === "<" && expr[i + 1] === ">") {
4754
- tokens.push({ kind: "NE", value: "<>" });
4755
- i += 2;
4756
- continue;
4757
- }
4758
- switch (ch) {
4759
- case "+":
4760
- tokens.push({ kind: "PLUS", value: "+" });
4761
- break;
4762
- case "-":
4763
- tokens.push({ kind: "MINUS", value: "-" });
4764
- break;
4765
- case "*":
4766
- tokens.push({ kind: "STAR", value: "*" });
4767
- break;
4768
- case "/":
4769
- tokens.push({ kind: "SLASH", value: "/" });
4770
- break;
4771
- case "%":
4772
- tokens.push({ kind: "PERCENT", value: "%" });
4773
- break;
4774
- case "^":
4775
- tokens.push({ kind: "CARET", value: "^" });
4776
- break;
4777
- case "(":
4778
- tokens.push({ kind: "LPAREN", value: "(" });
4779
- break;
4780
- case ")":
4781
- tokens.push({ kind: "RPAREN", value: ")" });
4782
- break;
4783
- case ",":
4784
- tokens.push({ kind: "COMMA", value: "," });
4785
- break;
4786
- case "<":
4787
- tokens.push({ kind: "LT", value: "<" });
4788
- break;
4789
- case ">":
4790
- tokens.push({ kind: "GT", value: ">" });
4791
- break;
4792
- case "=":
4793
- tokens.push({ kind: "EQ", value: "=" });
4794
- break;
4795
- default:
4796
- throw new Error(`Unknown character: ${ch}`);
4797
- }
4798
- i++;
4799
- }
4800
- tokens.push({ kind: "EOF", value: "" });
4801
- return tokens;
4802
- }
4803
-
4804
- class ExprParser {
4805
- tokens;
4806
- pos = 0;
4807
- constructor(tokens) {
4808
- this.tokens = tokens;
4809
- }
4810
- parse() {
4811
- const result = this.parseOr();
4812
- if (this.current().kind !== "EOF") {
4813
- throw new Error("too many values in the stack");
4814
- }
4815
- return result;
4816
- }
4817
- current() {
4818
- return this.tokens[this.pos] ?? { kind: "EOF", value: "" };
4819
- }
4820
- advance() {
4821
- const token = this.current();
4822
- this.pos++;
4823
- return token;
4824
- }
4825
- parseOr() {
4826
- let left = this.parseAnd();
4827
- while (this.current().kind === "IDENTIFIER" && this.current().value === "or") {
4828
- this.advance();
4829
- const right = this.parseAnd();
4830
- left = isTruthyNum(left) || isTruthyNum(right) ? 1 : 0;
4831
- }
4832
- return left;
4833
- }
4834
- parseAnd() {
4835
- let left = this.parseNot();
4836
- while (this.current().kind === "IDENTIFIER" && this.current().value === "and") {
4837
- this.advance();
4838
- const right = this.parseNot();
4839
- left = isTruthyNum(left) && isTruthyNum(right) ? 1 : 0;
4840
- }
4841
- return left;
4842
- }
4843
- parseNot() {
4844
- if (this.current().kind === "IDENTIFIER" && this.current().value === "not") {
4845
- this.advance();
4846
- const value = this.parseNot();
4847
- return isTruthyNum(value) ? 0 : 1;
4848
- }
4849
- return this.parseComparison();
4850
- }
4851
- parseComparison() {
4852
- let left = this.parseAddition();
4853
- const kind = this.current().kind;
4854
- if (kind === "LT" || kind === "GT" || kind === "LE" || kind === "GE" || kind === "EQ" || kind === "NE") {
4855
- this.advance();
4856
- const right = this.parseAddition();
4857
- switch (kind) {
4858
- case "LT":
4859
- return left < right ? 1 : 0;
4860
- case "GT":
4861
- return left > right ? 1 : 0;
4862
- case "LE":
4863
- return left <= right ? 1 : 0;
4864
- case "GE":
4865
- return left >= right ? 1 : 0;
4866
- case "EQ":
4867
- return left === right ? 1 : 0;
4868
- case "NE":
4869
- return left !== right ? 1 : 0;
4870
- }
4871
- }
4872
- return left;
4873
- }
4874
- parseAddition() {
4875
- let left = this.parseMultiplication();
4876
- while (true) {
4877
- const kind = this.current().kind;
4878
- if (kind === "PLUS") {
4879
- this.advance();
4880
- left = left + this.parseMultiplication();
4881
- } else if (kind === "MINUS") {
4882
- this.advance();
4883
- left = left - this.parseMultiplication();
4884
- } else {
4885
- break;
4886
- }
4887
- }
4888
- return left;
4889
- }
4890
- parseMultiplication() {
4891
- let left = this.parsePower();
4892
- while (true) {
4893
- const kind = this.current().kind;
4894
- if (kind === "STAR") {
4895
- this.advance();
4896
- left = left * this.parsePower();
4897
- } else if (kind === "SLASH") {
4898
- this.advance();
4899
- left = left / this.parsePower();
4900
- } else if (kind === "PERCENT") {
4901
- this.advance();
4902
- left = left % this.parsePower();
4903
- } else {
4904
- break;
4905
- }
4906
- }
4907
- return left;
4908
- }
4909
- parsePower() {
4910
- const left = this.parseUnary();
4911
- if (this.current().kind === "CARET") {
4912
- this.advance();
4913
- const right = this.parsePower();
4914
- return Math.pow(left, right);
4915
- }
4916
- return left;
4917
- }
4918
- parseUnary() {
4919
- const kind = this.current().kind;
4920
- if (kind === "MINUS") {
4921
- this.advance();
4922
- return -this.parseUnary();
4923
- }
4924
- if (kind === "PLUS") {
4925
- this.advance();
4926
- return +this.parseUnary();
4927
- }
4928
- return this.parsePrimary();
4929
- }
4930
- parsePrimary() {
4931
- const token = this.current();
4932
- if (token.kind === "NUMBER") {
4933
- this.advance();
4934
- return token.value;
4935
- }
4936
- if (token.kind === "LPAREN") {
4937
- this.advance();
4938
- const value = this.parseOr();
4939
- if (this.current().kind !== "RPAREN") {
4940
- throw new Error("Expected )");
4941
- }
4942
- this.advance();
4943
- return value;
4944
- }
4945
- if (token.kind === "IDENTIFIER") {
4946
- const name = token.value;
4947
- this.advance();
4948
- if (this.current().kind === "LPAREN") {
4949
- return this.parseFunctionCall(name);
4950
- }
4951
- throw new Error(`undefined constant "${name}"`);
4952
- }
4953
- throw new Error("Expected expression");
4954
- }
4955
- parseFunctionCall(name) {
4956
- if (this.current().kind !== "LPAREN") {
4957
- throw new Error("Expected (");
4958
- }
4959
- this.advance();
4960
- const args = [];
4961
- if (this.current().kind !== "RPAREN") {
4962
- args.push(this.parseOr());
4963
- while (this.current().kind === "COMMA") {
4964
- this.advance();
4965
- args.push(this.parseOr());
4966
- }
4967
- }
4968
- if (this.current().kind !== "RPAREN") {
4969
- throw new Error("Expected )");
4970
- }
4971
- this.advance();
4972
- return this.callFunction(name, args);
4973
- }
4974
- callFunction(name, args) {
4975
- switch (name) {
4976
- case "abs":
4977
- this.checkArgs(name, args, 1);
4978
- return Math.abs(args[0]);
4979
- case "min":
4980
- this.checkArgsMin(name, args, 1);
4981
- return Math.min(...args);
4982
- case "max":
4983
- this.checkArgsMin(name, args, 1);
4984
- return Math.max(...args);
4985
- case "floor":
4986
- this.checkArgs(name, args, 1);
4987
- return Math.floor(args[0]);
4988
- case "ceil":
4989
- this.checkArgs(name, args, 1);
4990
- return Math.ceil(args[0]);
4991
- case "round":
4992
- this.checkArgs(name, args, 1);
4993
- return Math.round(args[0]);
4994
- case "sqrt":
4995
- this.checkArgs(name, args, 1);
4996
- return Math.sqrt(args[0]);
4997
- case "sin":
4998
- this.checkArgs(name, args, 1);
4999
- return Math.sin(args[0]);
5000
- case "cos":
5001
- this.checkArgs(name, args, 1);
5002
- return Math.cos(args[0]);
5003
- case "tan":
5004
- this.checkArgs(name, args, 1);
5005
- return Math.tan(args[0]);
5006
- case "ln":
5007
- this.checkArgs(name, args, 1);
5008
- return Math.log(args[0]);
5009
- case "log":
5010
- this.checkArgs(name, args, 1);
5011
- return Math.log10(args[0]);
5012
- case "exp":
5013
- this.checkArgs(name, args, 1);
5014
- return Math.exp(args[0]);
5015
- case "pow":
5016
- this.checkArgs(name, args, 2);
5017
- return Math.pow(args[0], args[1]);
5018
- default:
5019
- throw new Error(`undefined function "${name}"`);
5020
- }
5021
- }
5022
- checkArgs(name, args, expected) {
5023
- if (args.length !== expected) {
5024
- throw new Error(`${name}() expects ${expected} argument(s), got ${args.length}`);
5025
- }
5026
- }
5027
- checkArgsMin(name, args, min) {
5028
- if (args.length < min) {
5029
- throw new Error(`${name}() expects at least ${min} argument(s), got ${args.length}`);
5030
- }
5031
- }
5032
- }
5033
-
5034
4772
  // packages/render/src/elements/expr.ts
4773
+ import { evaluateExpression, formatExprValue, isTruthy } from "@wdprlib/ast";
5035
4774
  function renderExpr(ctx, data) {
5036
4775
  const result = evaluateExpression(data.expression);
5037
4776
  if (result.success) {
5038
- ctx.pushEscaped(formatNumber(result.value));
4777
+ ctx.pushEscaped(formatExprValue(result.value));
5039
4778
  } else if (result.error !== "empty expression") {
5040
4779
  ctx.pushEscaped(`run-time error: ${result.error}`);
5041
4780
  }
@@ -5065,12 +4804,6 @@ function renderBranchElements(ctx, elements) {
5065
4804
  }
5066
4805
  renderElements(ctx, elements.slice(0, lastIdx + 1));
5067
4806
  }
5068
- function formatNumber(n) {
5069
- if (Number.isInteger(n)) {
5070
- return String(n);
5071
- }
5072
- return n.toFixed(6).replace(/\.?0+$/, "");
5073
- }
5074
4807
 
5075
4808
  // packages/render/src/render.ts
5076
4809
  function renderToHtml(tree, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdprlib/render",
3
- "version": "1.4.0",
3
+ "version": "2.1.0",
4
4
  "description": "HTML renderer for Wikidot markup",
5
5
  "keywords": [
6
6
  "html",
@@ -16,7 +16,8 @@
16
16
  "directory": "packages/render"
17
17
  },
18
18
  "files": [
19
- "dist"
19
+ "dist",
20
+ "src"
20
21
  ],
21
22
  "type": "module",
22
23
  "sideEffects": false,
@@ -25,6 +26,7 @@
25
26
  "types": "./dist/index.d.ts",
26
27
  "exports": {
27
28
  ".": {
29
+ "bun": "./src/index.ts",
28
30
  "import": {
29
31
  "types": "./dist/index.d.ts",
30
32
  "default": "./dist/index.js"
@@ -39,7 +41,7 @@
39
41
  "access": "public"
40
42
  },
41
43
  "dependencies": {
42
- "@wdprlib/ast": "1.2.1",
44
+ "@wdprlib/ast": "2.1.0",
43
45
  "domhandler": "^5.0.3",
44
46
  "htmlparser2": "^10.0.0",
45
47
  "sanitize-html": "^2.14.0",