Collection of utility functions for CSS identifier, string, and URL encoding/decoding, plus AST manipulation helpers.
Functions for encoding and decoding CSS identifiers with proper escaping:
namespace ident {
/**
* Encodes a string as a CSS identifier with proper escaping
* @param str - String to encode as identifier
* @returns Properly escaped CSS identifier
*/
function encode(str: string): string;
/**
* Decodes a CSS identifier, unescaping escape sequences
* @param str - CSS identifier to decode
* @returns Decoded string
*/
function decode(str: string): string;
}Usage Examples:
import { ident } from 'css-tree';
// Encode identifiers with special characters
console.log(ident.encode('my-class')); // "my-class"
console.log(ident.encode('2nd-item')); // "\\32 nd-item"
console.log(ident.encode('with space')); // "with\\ space"
console.log(ident.encode('with:colon')); // "with\\:colon"
// Decode escaped identifiers
console.log(ident.decode('\\32 nd-item')); // "2nd-item"
console.log(ident.decode('with\\ space')); // "with space"
console.log(ident.decode('with\\:colon')); // "with:colon"
// Safe class name generation
function safeClassName(name) {
return ident.encode(name.replace(/[^a-zA-Z0-9-_]/g, '-'));
}Functions for encoding and decoding CSS string literals:
namespace string {
/**
* Encodes a string as a CSS string literal with proper quotes and escaping
* @param str - String to encode
* @param apostrophe - Use single quotes instead of double quotes
* @returns Properly quoted and escaped CSS string
*/
function encode(str: string, apostrophe?: boolean): string;
/**
* Decodes a CSS string literal, removing quotes and unescaping sequences
* @param str - CSS string literal to decode
* @returns Decoded string content
*/
function decode(str: string): string;
}Usage Examples:
import { string } from 'css-tree';
// Encode strings with special characters
console.log(string.encode('Hello World')); // "\"Hello World\""
console.log(string.encode('Hello "World"')); // "\"Hello \\\"World\\\"\""
console.log(string.encode("Hello 'World'", true)); // "'Hello \\'World\\''"
console.log(string.encode('Line 1\nLine 2')); // "\"Line 1\\A Line 2\""
// Decode string literals
console.log(string.decode('"Hello World"')); // "Hello World"
console.log(string.decode('"Hello \\"World\\""')); // "Hello \"World\""
console.log(string.decode("'Hello \\'World\\''")); // "Hello 'World'"
console.log(string.decode('"Line 1\\A Line 2"')); // "Line 1\nLine 2"
// Generate content property values
function generateContent(text) {
return `content: ${string.encode(text)};`;
}Functions for encoding and decoding CSS URL values:
namespace url {
/**
* Encodes a URL string as a complete CSS url() function
* @param str - URL string to encode
* @returns Complete CSS url() function with encoded URL
*/
function encode(str: string): string;
/**
* Decodes a URL from CSS url() function content
* @param str - URL content from url() function
* @returns Decoded URL string
*/
function decode(str: string): string;
}Usage Examples:
import { url } from 'css-tree';
// Encode URLs with special characters
console.log(url.encode('image.png')); // "url(image.png)"
console.log(url.encode('image with spaces.png')); // "url(image\\ with\\ spaces.png)"
console.log(url.encode('path/to/image.png')); // "url(path/to/image.png)"
console.log(url.encode('https://example.com/img.png')); // "url(https://example.com/img.png)"
// Decode URL content
console.log(url.decode('image.png')); // "image.png"
console.log(url.decode('image\\ with\\ spaces.png')); // "image with spaces.png"
console.log(url.decode('path/to/image.png')); // "path/to/image.png"
// Generate background-image values
function generateBackgroundImage(imagePath) {
return `background-image: url(${url.encode(imagePath)});`;
}Functions for analyzing CSS property and keyword names:
/**
* Analyzes a CSS keyword name for vendor prefixes and custom properties
* @param name - Keyword name to analyze
* @returns Analysis result with prefix information
*/
function keyword(name: string): KeywordAnalysis;
/**
* Analyzes a CSS property name for vendor prefixes, hacks, and custom properties
* @param name - Property name to analyze
* @returns Analysis result with detailed information
*/
function property(name: string): PropertyAnalysis;
interface KeywordAnalysis {
/** Base name without prefixes */
basename: string;
/** Original name */
name: string;
/** Vendor prefix if present */
prefix?: string;
/** Vendor identifier (webkit, moz, ms, o) */
vendor?: string;
/** Whether it's a custom property (starts with --) */
custom: boolean;
}
interface PropertyAnalysis {
/** Base name without prefixes or hacks */
basename: string;
/** Original name */
name: string;
/** CSS hack character if present */
hack?: string;
/** Vendor identifier (webkit, moz, ms, o) */
vendor?: string;
/** Vendor prefix (-webkit-, -moz-, etc.) */
prefix?: string;
/** Whether it's a custom property (starts with --) */
custom: boolean;
}Usage Examples:
import { keyword, property } from 'css-tree';
// Analyze keywords
console.log(keyword('inherit')); // { basename: 'inherit', name: 'inherit', custom: false }
console.log(keyword('-webkit-fill')); // { basename: 'fill', name: '-webkit-fill', prefix: '-webkit-', vendor: 'webkit', custom: false }
console.log(keyword('--custom-value')); // { basename: '--custom-value', name: '--custom-value', custom: true }
// Analyze properties
console.log(property('color')); // { basename: 'color', name: 'color', custom: false }
console.log(property('-webkit-transform')); // { basename: 'transform', name: '-webkit-transform', prefix: '-webkit-', vendor: 'webkit', custom: false }
console.log(property('*zoom')); // { basename: 'zoom', name: '*zoom', hack: '*', custom: false }
console.log(property('--main-color')); // { basename: '--main-color', name: '--main-color', custom: true }
// Group properties by vendor
function groupPropertiesByVendor(properties) {
const groups = { standard: [], webkit: [], moz: [], ms: [], o: [] };
properties.forEach(prop => {
const analysis = property(prop);
const group = analysis.vendor || 'standard';
groups[group].push(prop);
});
return groups;
}Core utilities for AST cloning and conversion:
/**
* Deep clones an AST node with all children and properties
* @param node - AST node to clone
* @returns Deep clone of the node
*/
function clone(node: CssNode): CssNode;
/**
* Converts AST with List children to plain objects with arrays
* @param ast - AST to convert
* @returns Plain object representation
*/
function toPlainObject(ast: CssNode): object;
/**
* Converts plain object AST to use List instances for children
* @param ast - Plain object AST
* @returns AST with List children
*/
function fromPlainObject(ast: object): CssNode;Usage Examples:
import { parse, clone, toPlainObject, fromPlainObject, generate } from 'css-tree';
const ast = parse('.example { color: red; }');
// Clone AST for safe modification
const clonedAst = clone(ast);
// Modify clone without affecting original
walk(clonedAst, (node) => {
if (node.type === 'Identifier' && node.name === 'red') {
node.name = 'blue';
}
});
console.log(generate(ast)); // ".example{color:red}"
console.log(generate(clonedAst)); // ".example{color:blue}"
// Convert to plain object for serialization
const plainObject = toPlainObject(ast);
const jsonString = JSON.stringify(plainObject, null, 2);
// Convert back from plain object
const restoredAst = fromPlainObject(JSON.parse(jsonString));
console.log(generate(restoredAst)); // ".example{color:red}"
// Deep clone with custom modifications
function cloneAndModify(ast, modifications) {
const cloned = clone(ast);
walk(cloned, (node) => {
if (modifications[node.type]) {
modifications[node.type](node);
}
});
return cloned;
}Complex utility functions for common CSS processing tasks:
// Normalize vendor prefixes
function normalizeVendorPrefixes(ast) {
const normalized = clone(ast);
walk(normalized, {
Declaration: (node) => {
const analysis = property(node.property.name);
if (analysis.vendor) {
// Store original vendor-prefixed version
node.vendorPrefixed = node.property.name;
// Use standard name
node.property.name = analysis.basename;
}
}
});
return normalized;
}
// Extract custom properties
function extractCustomProperties(ast) {
const customProps = [];
walk(ast, {
Declaration: (node) => {
const analysis = property(node.property.name);
if (analysis.custom) {
customProps.push({
name: node.property.name,
value: generate(node.value),
node: clone(node)
});
}
}
});
return customProps;
}
// Validate identifiers in selectors
function validateSelectors(ast) {
const issues = [];
walk(ast, {
ClassSelector: (node) => {
try {
ident.decode(node.name);
} catch (error) {
issues.push({
type: 'InvalidClassSelector',
name: node.name,
message: error.message
});
}
},
IdSelector: (node) => {
try {
ident.decode(node.name);
} catch (error) {
issues.push({
type: 'InvalidIdSelector',
name: node.name,
message: error.message
});
}
}
});
return issues;
}
// Safe string generation for content property
function generateContentValue(text, useDoubleQuotes = true) {
try {
return string.encode(text, !useDoubleQuotes);
} catch (error) {
// Fallback to basic escaping
const escaped = text.replace(/["'\\]/g, '\\$&');
return useDoubleQuotes ? `"${escaped}"` : `'${escaped}'`;
}
}
// URL resolution helper
function resolveUrls(ast, baseUrl) {
const resolved = clone(ast);
walk(resolved, {
Url: (node) => {
try {
const urlValue = url.decode(node.value);
if (!urlValue.match(/^https?:\/\//)) {
// Resolve relative URL
const resolvedUrl = new URL(urlValue, baseUrl).href;
node.value = url.encode(resolvedUrl);
}
} catch (error) {
console.warn('Failed to resolve URL:', node.value);
}
}
});
return resolved;
}