UNPKG

css-doodle

Version:

A web component for drawing patterns with CSS

368 lines (345 loc) 8.98 kB
import { scan, iterator } from './tokenizer.js'; import parseValueGroup from './parse-value-group.js'; function readStatement(iter, token) { let fragment = []; let inlineBlock; let stackQuote = []; let stackParen = []; let isInline = false; while (iter.next()) { let { curr, next } = iter.get(); if (curr.isSymbol('(') && !stackQuote.length) { stackParen.push(curr); } else if (curr.isSymbol(')') && !stackQuote.length) { stackParen.pop(); } if (curr.isSymbol("'", '"')) { if (curr.status === 'open') { stackQuote.push(curr); } else { stackQuote.pop(); } } let isStatementBreak = !stackQuote.length && !stackParen.length && (!next || curr.isSymbol(';') || next.isSymbol('}')); if (curr.isSymbol("'", '"')) { if ((next && next.isSymbol('}')) && !stackQuote.length) { isStatementBreak = true; } } if (!stackParen.length && !stackQuote.length && curr.isSymbol('{')) { let selectors = getSelectors(fragment); if (!selectors.length) { continue; } let tokenName = selectors.pop(); let skip = isSkip(...selectors, tokenName); inlineBlock = resolveId(walk(iter, splitTimes(tokenName, { type: 'block', name: tokenName, value: [], })), skip); while (tokenName = selectors.pop()) { inlineBlock = resolveId(splitTimes(tokenName, { type: 'block', name: tokenName, value: [inlineBlock] }), skip); } isInline = true; break; } fragment.push(curr); if (isStatementBreak) { break; } } if (fragment.length && !inlineBlock) { token._valueTokens = fragment; token.value = joinToken(fragment); } else if (inlineBlock) { token.value = inlineBlock; } if (isInline) { token.value.inline = true; } if (token.origin) { token.origin.value = token.value; } return token; } function readStyle(iter) { let stack = []; let style = []; while (iter.next()) { let { curr } = iter.get(); if (curr.isSymbol('{')) { stack.push(curr.value); } else if (curr.isSymbol('}')) { if (stack.length) { stack.pop(); } else { break; } } style.push(curr.value); } return style.join(''); } function walk(iter, parentToken) { let rules = []; let fragment = []; let tokenType = parentToken && parentToken.type || ''; let stack = []; while (iter.next()) { let { prev, curr, next } = iter.get(); if (curr.isSymbol('(')) { stack.push(curr.value); } if (curr.isSymbol(')')) { stack.pop(); } let isBlockBreak = !next || curr.isSymbol('}'); if (isBlock(tokenType) && isBlockBreak) { if (!next && rules.length && !curr.isSymbol('}')) { let last = rules[rules.length - 1].value; if (typeof last === 'string') { rules[rules.length - 1].value += (';' + curr.value); } } parentToken.value = rules; break; } else if (curr.isSymbol('{')) { let selectors = getSelectors(fragment); if (!selectors.length) { continue; } if (isSkip(parentToken.name)) { selectors = [joinToken(fragment)]; } let tokenName = selectors.pop(); let skip = isSkip(...selectors, parentToken.name, tokenName); if (tokenName === 'style') { rules.push({ type: 'block', name: tokenName, value: readStyle(iter) }); } else { let block = resolveId(walk(iter, splitTimes(tokenName, { type: 'block', name: tokenName, value: [] })), skip); while (tokenName = selectors.pop()) { block = resolveId(splitTimes(tokenName, { type: 'block', name: tokenName, value: [block] }), skip); } rules.push(block); } fragment = []; } else if ( curr.isSymbol(':') && !stack.length && !isSpecialProperty(prev, next) && fragment.length ) { let props = getGroups(fragment, token => token.isSymbol(',')); let intial = { type: 'statement', name: 'unkown', value: '' } if (props.length > 1) { intial.origin = { name: props }; } let statement = readStatement(iter, intial); let groupdValue = parseValueGroup(statement.value); let expand = (props.length > 1 && groupdValue.length === props.length); props.forEach((prop, i) => { let item = Object.assign({}, statement, { name: prop }); if (/^\-\-/.test(prop)) { item.variable = true; } if (expand) { item.value = groupdValue[i]; } if (/viewBox/i.test(prop)) { item.detail = parseViewBox(item.value, item._valueTokens); } delete item._valueTokens; rules.push(item); }); if (isBlock(tokenType)) { parentToken.value = rules; } fragment = []; } else if (curr.isSymbol(';')) { if (rules.length && fragment.length) { rules[rules.length - 1].value += (';' + joinToken(fragment)); fragment = []; } } else { fragment.push(curr); } } if (rules.length && isBlock(tokenType)) { parentToken.value = rules; } return tokenType ? parentToken : rules; } function isSpecialProperty(prev, next) { const names = [ 'xlink:actuate', 'xlink:arcrole', 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type', 'xml:base', 'xml:lang', 'xml:space', ]; let prevValue = prev && prev.value; let nextValue = next && next.value; return names.includes(prevValue + ':' + nextValue); } function joinToken(tokens) { return tokens .filter((token, i) => { if (token.isSymbol(';', '}') && i === tokens.length - 1) return false; return true; }) .map(n => n.value).join(''); } function resolveId(block, skip) { let name = block.name || ''; let [tokenName, ...ids] = name.split(/#/); let id = ids[ids.length - 1]; if (tokenName && id && !skip) { block.name = tokenName; block.value.push({ type: 'statement', name: 'id', value: id, }); } return block; } function getGroups(tokens, fn) { let group = []; let temp = []; tokens.forEach(token => { if (fn(token)) { group.push(joinToken(temp)); temp = []; } else { temp.push(token); } }); if (temp.length) { group.push(joinToken(temp)); } return group; } function getSelectors(tokens) { let result = []; let it = iterator(tokens); let temp = []; let hasSymbol; while (it.next()) { let { prev, curr, next } = it.get(); let isTimeSymbol = ( prev && next && curr.value === 'x' && prev.isNumber() && next.isNumber() ); if (curr.isWord() && !hasSymbol && !isTimeSymbol) { result.push(curr.value.trim()); } else { result[result.length - 1] = (result[result.length - 1] + curr.value).trim(); } if (curr.isSymbol()) { hasSymbol = true; } else if (!curr.isSpace()) { hasSymbol = false; } } return result; } function parseViewBox(value, tokens) { const viewBox = { value: [] }; let temp; if (!Array.isArray(tokens)) { return viewBox; } for (let token of tokens) { if (token.isSpace() || token.isSymbol(',', ';')) { continue; } if (viewBox.value.length < 4 && token.isNumber()) { viewBox.value.push(Number(token.value)); } else if (token.isNumber() && temp) { viewBox[temp] = Number(token.value); temp = null; } else if (token.isWord()) { temp = token.value; } } return viewBox; } function splitTimes(name, object) { let target = Object.assign({}, object); if (/\*\s*[0-9]/.test(name)) { let [tokenName, times] = name.split('*'); if (times) { target.times = times.trim(); target.pureName = tokenName.trim(); } } return target; } function isSkip(...names) { return names.some(n => n === 'style'); } function isBlock(type) { return type === 'block'; } function skipHeadSVG(block) { let headSVG, headVariables = []; for (let item of block.value) { if (item.name === 'svg') { headSVG = item; } if (item.variable) { headVariables.push(item); } } if (headSVG && Array.isArray(headSVG.value)) { for (let variable of headVariables) { if (!headSVG.value.find(n => n.name == variable.name)) { headSVG.value.unshift(variable); } } return headSVG; } return block; } function parse(source, root) { let iter = iterator(scan(source)); let tokens = walk(iter, root || { type: 'block', name: 'svg', value: [] }); return skipHeadSVG(tokens); } export default parse;