index.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { DecodingMode, decodeHTML, decodeXML } from "./decode.js";
  2. import { encodeHTML, encodeNonAsciiHTML } from "./encode.js";
  3. import {
  4. encodeXML,
  5. escapeAttribute,
  6. escapeText,
  7. escapeUTF8,
  8. } from "./escape.js";
  9. /** The level of entities to support. */
  10. export enum EntityLevel {
  11. /** Support only XML entities. */
  12. XML = 0,
  13. /** Support HTML entities, which are a superset of XML entities. */
  14. HTML = 1,
  15. }
  16. export enum EncodingMode {
  17. /**
  18. * The output is UTF-8 encoded. Only characters that need escaping within
  19. * XML will be escaped.
  20. */
  21. UTF8,
  22. /**
  23. * The output consists only of ASCII characters. Characters that need
  24. * escaping within HTML, and characters that aren't ASCII characters will
  25. * be escaped.
  26. */
  27. ASCII,
  28. /**
  29. * Encode all characters that have an equivalent entity, as well as all
  30. * characters that are not ASCII characters.
  31. */
  32. Extensive,
  33. /**
  34. * Encode all characters that have to be escaped in HTML attributes,
  35. * following {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}.
  36. */
  37. Attribute,
  38. /**
  39. * Encode all characters that have to be escaped in HTML text,
  40. * following {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}.
  41. */
  42. Text,
  43. }
  44. export interface DecodingOptions {
  45. /**
  46. * The level of entities to support.
  47. * @default {@link EntityLevel.XML}
  48. */
  49. level?: EntityLevel;
  50. /**
  51. * Decoding mode. If `Legacy`, will support legacy entities not terminated
  52. * with a semicolon (`;`).
  53. *
  54. * Always `Strict` for XML. For HTML, set this to `true` if you are parsing
  55. * an attribute value.
  56. *
  57. * The deprecated `decodeStrict` function defaults this to `Strict`.
  58. *
  59. * @default {@link DecodingMode.Legacy}
  60. */
  61. mode?: DecodingMode | undefined;
  62. }
  63. /**
  64. * Decodes a string with entities.
  65. *
  66. * @param input String to decode.
  67. * @param options Decoding options.
  68. */
  69. export function decode(
  70. input: string,
  71. options: DecodingOptions | EntityLevel = EntityLevel.XML,
  72. ): string {
  73. const level = typeof options === "number" ? options : options.level;
  74. if (level === EntityLevel.HTML) {
  75. const mode = typeof options === "object" ? options.mode : undefined;
  76. return decodeHTML(input, mode);
  77. }
  78. return decodeXML(input);
  79. }
  80. /**
  81. * Decodes a string with entities. Does not allow missing trailing semicolons for entities.
  82. *
  83. * @param input String to decode.
  84. * @param options Decoding options.
  85. * @deprecated Use `decode` with the `mode` set to `Strict`.
  86. */
  87. export function decodeStrict(
  88. input: string,
  89. options: DecodingOptions | EntityLevel = EntityLevel.XML,
  90. ): string {
  91. const normalizedOptions =
  92. typeof options === "number" ? { level: options } : options;
  93. normalizedOptions.mode ??= DecodingMode.Strict;
  94. return decode(input, normalizedOptions);
  95. }
  96. /**
  97. * Options for `encode`.
  98. */
  99. export interface EncodingOptions {
  100. /**
  101. * The level of entities to support.
  102. * @default {@link EntityLevel.XML}
  103. */
  104. level?: EntityLevel;
  105. /**
  106. * Output format.
  107. * @default {@link EncodingMode.Extensive}
  108. */
  109. mode?: EncodingMode;
  110. }
  111. /**
  112. * Encodes a string with entities.
  113. *
  114. * @param input String to encode.
  115. * @param options Encoding options.
  116. */
  117. export function encode(
  118. input: string,
  119. options: EncodingOptions | EntityLevel = EntityLevel.XML,
  120. ): string {
  121. const { mode = EncodingMode.Extensive, level = EntityLevel.XML } =
  122. typeof options === "number" ? { level: options } : options;
  123. switch (mode) {
  124. case EncodingMode.UTF8: {
  125. return escapeUTF8(input);
  126. }
  127. case EncodingMode.Attribute: {
  128. return escapeAttribute(input);
  129. }
  130. case EncodingMode.Text: {
  131. return escapeText(input);
  132. }
  133. case EncodingMode.ASCII: {
  134. return level === EntityLevel.HTML
  135. ? encodeNonAsciiHTML(input)
  136. : encodeXML(input);
  137. }
  138. // biome-ignore lint/complexity/noUselessSwitchCase: we get an error for the switch not being exhaustive
  139. case EncodingMode.Extensive: // eslint-disable-line unicorn/no-useless-switch-case
  140. default: {
  141. return level === EntityLevel.HTML
  142. ? encodeHTML(input)
  143. : encodeXML(input);
  144. }
  145. }
  146. }
  147. export {
  148. DecodingMode,
  149. decodeHTML,
  150. // Legacy aliases (deprecated)
  151. decodeHTML as decodeHTML4,
  152. decodeHTML as decodeHTML5,
  153. decodeHTMLAttribute,
  154. decodeHTMLStrict,
  155. decodeHTMLStrict as decodeHTML4Strict,
  156. decodeHTMLStrict as decodeHTML5Strict,
  157. decodeXML,
  158. decodeXML as decodeXMLStrict,
  159. EntityDecoder,
  160. } from "./decode.js";
  161. export {
  162. encodeHTML,
  163. // Legacy aliases (deprecated)
  164. encodeHTML as encodeHTML4,
  165. encodeHTML as encodeHTML5,
  166. encodeNonAsciiHTML,
  167. } from "./encode.js";
  168. export {
  169. encodeXML,
  170. escape,
  171. escapeAttribute,
  172. escapeText,
  173. escapeUTF8,
  174. } from "./escape.js";