Last active
May 27, 2021 06:23
-
-
Save Nixinova/4ac718e30629d026fac1613428198d2d to your computer and use it in GitHub Desktop.
Nested CSS Tokenizer example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let cssOutput = ` | |
outer1 { | |
prop1: true; | |
inner {prop2: true;} | |
} | |
outer2 { | |
prop3: false; | |
} | |
` | |
type TokenName = 'Root' | 'Style' | 'Block'; | |
type Token = { name: TokenName, content: string, body: Token[] }; | |
let content: string = ''; | |
let tokenTree: Token[] = [{ name: 'Root', content: '', body: [] }]; | |
let selectorTree: string[] = []; | |
// loop through stylesheet and create a token tree | |
for (let i = 0; i < cssOutput.length; i++) { | |
const char: string = cssOutput[i]; | |
const stylesMatch: RegExp = /[\w-]+:[^;]+;/g; | |
const trailingSelectorMatch: RegExp = /[^;]+$/; | |
let currentToken: Token = tokenTree[tokenTree.length - 1]; | |
if (char === '{') { | |
if (!content) continue; | |
let selector: string = content.replace(stylesMatch, '').trim(); | |
let styles: string = content.replace(trailingSelectorMatch, '').trim(); | |
if (styles) { | |
currentToken.body.push({ name: 'Style', content: styles, body: [] }); | |
} | |
const parentSelector: string = selectorTree[selectorTree.length - 1] || ''; | |
if (selector.includes('&')) { | |
selector = selector.replace(/&/g, parentSelector).trim(); | |
} | |
else { | |
selector = parentSelector.split(',').map(psel => psel + ' ' + selector).join(','); | |
} | |
selectorTree.push(selector.trim()); | |
let newToken: Token = { name: 'Block', content: selector, body: [] }; | |
currentToken.body.push(newToken); | |
currentToken = newToken; | |
tokenTree.push(currentToken); | |
content = ''; | |
} | |
else if (char === '}') { | |
if (tokenTree.length < 1 || !content) continue; | |
selectorTree.pop(); | |
currentToken = tokenTree.pop() as Token; | |
if (content.trim()) { | |
currentToken.body.push({ name: 'Style', content: content.trim(), body: [] }); | |
} | |
content = ''; | |
} | |
else { | |
content += char; | |
} | |
} | |
// clear parsed blocks | |
const blockRegex: RegExp = /[^{}]+{[^{}]*}/gs; | |
while (blockRegex.test(cssOutput)) { | |
cssOutput = cssOutput.replace(blockRegex, ''); | |
} | |
// move all sub-blocks to root | |
let blocks: Token[] = []; | |
const flatten = (obj: Token): void => { | |
if (!obj) return; | |
for (const o of obj.body) { | |
if (o.name === 'Style') blocks.push({ name: obj.name, content: obj.content, body: [o] }); | |
else flatten(o); | |
} | |
}; | |
flatten(tokenTree[0]); | |
// create unnested CSS | |
let flattenedOutput: string = ''; | |
for (const block of blocks) { | |
flattenedOutput += block.content + ' {' + block.body[0].content + '}'; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Try it out: https://cutt.ly/zzpEjNo | |
let cssOutput = ` | |
outer1 { | |
prop1: true; | |
inner {prop2: true;} | |
} | |
outer2 { | |
prop3: false; | |
} | |
` | |
interface Token { | |
name: string, | |
content: string, | |
body: Token[], | |
} | |
const tokenized: Token[] = [{ name: 'Root', content: '', body: [] }]; | |
let selector: string = ''; | |
let tokenTree: Token[] = [tokenized[0]]; | |
for (let i = 0; i < cssOutput.length; i++) { | |
const c = cssOutput[i]; | |
let currentToken = tokenTree[tokenTree.length - 1]; | |
selector = selector.trim() | |
if (c === ';') { | |
currentToken.body.push({ name: 'CSS', content: selector, body: [] }) | |
selector = ''; | |
} | |
else if (c === '{') { | |
if (!selector) continue; | |
let newToken: Token = { name: 'Selector', content: selector, body: [] }; | |
currentToken.body.push(newToken) | |
currentToken = newToken; | |
tokenTree.push(currentToken); | |
selector = ''; | |
} | |
else if (c === '}') { | |
if (tokenTree.length < 1 || !selector) continue; | |
currentToken = tokenTree.pop() as Token; | |
currentToken.body.push({ name: 'CSS', content: selector, body: [] }) | |
selector = ''; | |
} | |
else selector += c; | |
} | |
console.log(tokenized) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment