UNPKG

css-what

Version:
148 lines (147 loc) 4.83 kB
import { SelectorType, AttributeAction } from "./types.js"; const attribValueChars = ["\\", '"']; const pseudoValueChars = [...attribValueChars, "(", ")"]; const charsToEscapeInAttributeValue = new Set(attribValueChars.map((c) => c.charCodeAt(0))); const charsToEscapeInPseudoValue = new Set(pseudoValueChars.map((c) => c.charCodeAt(0))); const charsToEscapeInName = new Set([ ...pseudoValueChars, "~", "^", "$", "*", "+", "!", "|", ":", "[", "]", " ", ".", "%", ].map((c) => c.charCodeAt(0))); /** * Turns `selector` back into a string. * * @param selector Selector to stringify. */ export function stringify(selector) { return selector .map((token) => token .map((token, index, array) => stringifyToken(token, index, array)) .join("")) .join(", "); } function stringifyToken(token, index, array) { switch (token.type) { // Simple types case SelectorType.Child: { return index === 0 ? "> " : " > "; } case SelectorType.Parent: { return index === 0 ? "< " : " < "; } case SelectorType.Sibling: { return index === 0 ? "~ " : " ~ "; } case SelectorType.Adjacent: { return index === 0 ? "+ " : " + "; } case SelectorType.Descendant: { return " "; } case SelectorType.ColumnCombinator: { return index === 0 ? "|| " : " || "; } case SelectorType.Universal: { // Return an empty string if the selector isn't needed. return token.namespace === "*" && index + 1 < array.length && "name" in array[index + 1] ? "" : `${getNamespace(token.namespace)}*`; } case SelectorType.Tag: { return getNamespacedName(token); } case SelectorType.PseudoElement: { return `::${escapeName(token.name, charsToEscapeInName)}${token.data === null ? "" : `(${escapeName(token.data, charsToEscapeInPseudoValue)})`}`; } case SelectorType.Pseudo: { return `:${escapeName(token.name, charsToEscapeInName)}${token.data === null ? "" : `(${typeof token.data === "string" ? escapeName(token.data, charsToEscapeInPseudoValue) : stringify(token.data)})`}`; } case SelectorType.Attribute: { if (token.name === "id" && token.action === AttributeAction.Equals && token.ignoreCase === "quirks" && !token.namespace) { return `#${escapeName(token.value, charsToEscapeInName)}`; } if (token.name === "class" && token.action === AttributeAction.Element && token.ignoreCase === "quirks" && !token.namespace) { return `.${escapeName(token.value, charsToEscapeInName)}`; } const name = getNamespacedName(token); if (token.action === AttributeAction.Exists) { return `[${name}]`; } return `[${name}${getActionValue(token.action)}="${escapeName(token.value, charsToEscapeInAttributeValue)}"${token.ignoreCase === null ? "" : token.ignoreCase ? " i" : " s"}]`; } } } function getActionValue(action) { switch (action) { case AttributeAction.Equals: { return ""; } case AttributeAction.Element: { return "~"; } case AttributeAction.Start: { return "^"; } case AttributeAction.End: { return "$"; } case AttributeAction.Any: { return "*"; } case AttributeAction.Not: { return "!"; } case AttributeAction.Hyphen: { return "|"; } default: { throw new Error("Shouldn't be here"); } } } function getNamespacedName(token) { return `${getNamespace(token.namespace)}${escapeName(token.name, charsToEscapeInName)}`; } function getNamespace(namespace) { return namespace === null ? "" : `${namespace === "*" ? "*" : escapeName(namespace, charsToEscapeInName)}|`; } function escapeName(name, charsToEscape) { let lastIndex = 0; let escapedName = ""; for (let index = 0; index < name.length; index++) { if (charsToEscape.has(name.charCodeAt(index))) { escapedName += `${name.slice(lastIndex, index)}\\${name.charAt(index)}`; lastIndex = index + 1; } } return escapedName.length > 0 ? escapedName + name.slice(lastIndex) : name; }