UNPKG

@wix/css-property-parser

Version:

A comprehensive TypeScript library for parsing and serializing CSS property values with full MDN specification compliance

120 lines (119 loc) 4.38 kB
// Font Family property parser - Modern API Implementation // Handles parsing of CSS font-family property according to MDN specification // https://developer.mozilla.org/en-US/docs/Web/CSS/font-family import { isCssVariable, isGlobalKeyword } from '../utils/shared-utils.js'; import { parse as parseCSSVariable, toCSSValue as cssVariableToCSSValue } from './css-variable.js'; // Generic font families as per CSS spec const GENERIC_FAMILIES = [ 'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'math', 'emoji', 'fangsong' ]; /** * Parse font family value - reusing logic from font.ts but modernized */ function parseFontFamily(value) { if (!value || value.trim() === '') { return null; } const families = []; const parts = value.split(','); for (const part of parts) { const trimmed = part.trim(); if (trimmed === '') continue; // Check for invalid tokens that suggest this isn't a font family // Reject if it contains CSS units (px, em, rem, %, etc.) if (/\d+(px|em|rem|pt|pc|in|cm|mm|ex|ch|vh|vw|vmin|vmax|%)(\s|$)/i.test(trimmed)) { return null; } // Handle quotes - preserve them but validate the content const family = trimmed; let unquotedFamily = family; if ((family.startsWith('"') && family.endsWith('"')) || (family.startsWith("'") && family.endsWith("'"))) { unquotedFamily = family.slice(1, -1); // Reject empty quoted strings if (unquotedFamily === '') { return null; } // Process Unicode escape sequences in quoted strings try { unquotedFamily = unquotedFamily.replace(/\\u([0-9a-fA-F]{4})/g, (match, hex) => { return String.fromCharCode(parseInt(hex, 16)); }); } catch { // Invalid Unicode escape sequence return null; } // Reject quoted strings with invalid characters (newlines, tabs, etc.) if (/[\n\r\t]/.test(unquotedFamily)) { return null; } } // Validate font family names for invalid characters // Generic families are always valid const lowerFamily = unquotedFamily.toLowerCase(); if (!GENERIC_FAMILIES.includes(lowerFamily)) { // For non-generic families, specifically reject characters that are clearly invalid in font names // Reject if contains numbers followed by units or other clearly non-font-name patterns if (/^\d/.test(unquotedFamily) || /[<>{}[\]()@#$%^&*+=|\\:;"?/]/.test(unquotedFamily)) { return null; } } families.push(family); } if (families.length === 0) { return null; } return { type: 'font-list', families }; } /** * Parses a CSS font-family property value * @param value - The CSS font-family property string * @returns Parsed font family object or null if invalid */ export function parse(value) { if (!value || typeof value !== 'string') { return null; } const trimmed = value.trim(); if (trimmed === '') { return null; } // CSS variables - parse and return directly (modern pattern) if (isCssVariable(trimmed)) { return parseCSSVariable(trimmed); } // Global keywords - not supported in current type system for font-family // TODO: Add FontFamilyKeywordValue type to support global keywords if (isGlobalKeyword(trimmed)) { return null; } // Parse font family list const result = parseFontFamily(trimmed); if (result) { return result; } return null; } /** * Converts a parsed font family back to a CSS value string * @param parsed - The parsed font family object * @returns CSS value string or null if invalid */ export function toCSSValue(parsed) { if (!parsed) { return null; } // Handle CSS variables if ('CSSvariable' in parsed) { return cssVariableToCSSValue(parsed); } // Handle font family list if ('families' in parsed && parsed.type === 'font-list') { return parsed.families.join(', '); } return null; }