import { styleParseError } from './error.mjs'; import { findEndOfQuotedString, findEndOfURL } from './strings.mjs'; import { textTokensToRule, mergeTextTokens, textTokensToSelector } from './text.mjs'; function findTokens(code, tokens) { const list = []; const lc = code.toLowerCase(); tokens.forEach((token) => { let index = 0; while (true) { index = lc.indexOf(token, index); if (index === -1) { return; } list.push({ token, index }); index++; } }); list.sort((a, b) => a.index - b.index); return list; } function getTokens(css) { const items = []; let textQueue = []; let start = 0; let depth = 0; try { const checkRule = (text) => { if (!textQueue.length) { return; } const item = textTokensToRule(textQueue); if (item) { items.push(item); return; } const value = mergeTextTokens(textQueue) + text; if (!value.length) { return; } throw styleParseError("Invalid css rule", css, textQueue[0]?.index); }; findTokens(css, ['"', "'", "/*", "{", "}", ";", "url(", "\\"]).forEach( (token) => { if (token.index < start) { return; } switch (token.token) { case "/*": { textQueue.push({ type: "chunk", text: css.slice(start, token.index), index: start }); start = token.index; const end = css.indexOf("*/", start + 2); if (end === -1) { throw styleParseError( "Missing comment closing statement", css, start ); } start = end + 2; break; } case "\\": textQueue.push({ type: "chunk", text: css.slice(start, token.index + 2), index: start }); start = token.index + 2; break; case "url(": { textQueue.push({ type: "chunk", text: css.slice(start, token.index), index: start }); start = token.index; const end = findEndOfURL(css, start); if (typeof end !== "number") { throw end; } textQueue.push({ type: "url", text: css.slice(start, end), index: start }); start = end; break; } case '"': case "'": { textQueue.push({ type: "chunk", text: css.slice(start, token.index), index: start }); start = token.index; const end = findEndOfQuotedString( css, token.token, start ); if (end === null) { throw styleParseError( "Missing closing " + token.token, css, start ); } textQueue.push({ type: "quoted-string", text: css.slice(start, end), index: start }); start = end; break; } case ";": { textQueue.push({ type: "chunk", text: css.slice(start, token.index), index: start }); checkRule(token.token); start = token.index + 1; textQueue = []; break; } case "{": { textQueue.push({ type: "chunk", text: css.slice(start, token.index), index: start }); const item = textTokensToSelector(textQueue); if (!item) { throw styleParseError( "Invalid css rule", css, start ); } items.push(item); start = token.index + 1; textQueue = []; depth++; break; } case "}": { textQueue.push({ type: "chunk", text: css.slice(start, token.index), index: start }); checkRule(""); items.push({ type: "close", index: token.index }); if (!depth) { throw styleParseError( "Unexpected }", css, token.index ); } depth--; start = token.index + 1; textQueue = []; break; } default: throw new Error( `Forgot to parse token: ${token.token}` ); } } ); if (depth) { return styleParseError("Missing }", css); } textQueue.push({ type: "chunk", text: css.slice(start), index: start }); checkRule(""); } catch (err) { if (typeof err === "object" && err.type === "style-parse-error") { return err; } throw err; } return items; } export { getTokens };