All files / html / unstable_escape_css.ts

100.00% Branches 4/4
100.00% Lines 16/16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x2
x32
 
x32
 
x32
x101
 
 
x101
 
x101
 
x101
x32
x32
 
x18
x18
x18
x54
x54
x54





























































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

/**
 * Escapes a string for direct interpolation into an external
 * CSS style sheet, within a `<style>` element, or in a selector.
 *
 * Uses identical logic to
 * [`CSS.escape`](https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape_static)
 * in browsers.
 *
 * @param str The string to escape.
 * @returns The escaped string.
 *
 * @example Usage
 * ```ts
 * import { escapeCss } from "@std/html/unstable-escape-css";
 * import { assertEquals } from "@std/assert";
 *
 * // Invalid in a CSS selector, even though it's a valid HTML ID
 * const elementId = "123";
 * // Unsafe for interpolation
 * const contentInput = `<!-- '" --></style>`;
 *
 * const selector = `#${escapeCss(elementId)}`;
 * const content = `"${escapeCss(contentInput)}"`;
 *
 * // Usable as a CSS selector
 * assertEquals(selector, String.raw`#\31 23`);
 * // Safe for interpolation
 * assertEquals(content, String.raw`"\<\!--\ \'\"\ --\>\<\/style\>"`);
 *
 * // Usage
 * `<style>
 *  ${selector}::after {
 *    content: ${content};
 *  }
 * </style>`;
 * ```
 */
export function escapeCss(str: string): string {
  const matcher =
    // deno-lint-ignore no-control-regex
    /(\0)|([\x01-\x1f\x7f]|(?<=^-?)\d)|(^-$|[ -,.\/:-@\[-^`\{-~])/g;

  return str.replaceAll(matcher, (_, g1, g2, g3) => {
    return g1 != null
      // null char
      ? "�"
      : g2 != null
      // control char or digit at start
      ? escapeAsCodePoint(g2)
      // solo dash or special char
      : escapeChar(g3);
  });
}

function escapeAsCodePoint(char: string) {
  return `\\${char.codePointAt(0)!.toString(16)} `;
}
function escapeChar(char: string) {
  return "\\" + char;
}