|
var openParentheses = '('.charCodeAt(0) |
|
var closeParentheses = ')'.charCodeAt(0) |
|
var singleQuote = "'".charCodeAt(0) |
|
var doubleQuote = '"'.charCodeAt(0) |
|
var backslash = '\\'.charCodeAt(0) |
|
var slash = '/'.charCodeAt(0) |
|
var comma = ','.charCodeAt(0) |
|
var colon = ':'.charCodeAt(0) |
|
var star = '*'.charCodeAt(0) |
|
var uLower = 'u'.charCodeAt(0) |
|
var uUpper = 'U'.charCodeAt(0) |
|
var plus = '+'.charCodeAt(0) |
|
var isUnicodeRange = /^[a-f0-9?-]+$/i |
|
|
|
module.exports = function (input) { |
|
var tokens = [] |
|
var value = input |
|
|
|
var next, quote, prev, token, escape, escapePos, whitespacePos, parenthesesOpenPos |
|
var pos = 0 |
|
var code = value.charCodeAt(pos) |
|
var max = value.length |
|
var stack = [{ nodes: tokens }] |
|
var balanced = 0 |
|
var parent |
|
|
|
var name = '' |
|
var before = '' |
|
var after = '' |
|
|
|
while (pos < max) { |
|
|
|
if (code <= 32) { |
|
next = pos |
|
do { |
|
next += 1 |
|
code = value.charCodeAt(next) |
|
} while (code <= 32) |
|
token = value.slice(pos, next) |
|
|
|
prev = tokens[tokens.length - 1] |
|
if (code === closeParentheses && balanced) { |
|
after = token |
|
} else if (prev && prev.type === 'div') { |
|
prev.after = token |
|
prev.sourceEndIndex += token.length |
|
} else if ( |
|
code === comma || |
|
code === colon || |
|
(code === slash && |
|
value.charCodeAt(next + 1) !== star && |
|
(!parent || (parent && parent.type === 'function' && false))) |
|
) { |
|
before = token |
|
} else { |
|
tokens.push({ |
|
type: 'space', |
|
sourceIndex: pos, |
|
sourceEndIndex: next, |
|
value: token, |
|
}) |
|
} |
|
|
|
pos = next |
|
|
|
|
|
} else if (code === singleQuote || code === doubleQuote) { |
|
next = pos |
|
quote = code === singleQuote ? "'" : '"' |
|
token = { |
|
type: 'string', |
|
sourceIndex: pos, |
|
quote: quote, |
|
} |
|
do { |
|
escape = false |
|
next = value.indexOf(quote, next + 1) |
|
if (~next) { |
|
escapePos = next |
|
while (value.charCodeAt(escapePos - 1) === backslash) { |
|
escapePos -= 1 |
|
escape = !escape |
|
} |
|
} else { |
|
value += quote |
|
next = value.length - 1 |
|
token.unclosed = true |
|
} |
|
} while (escape) |
|
token.value = value.slice(pos + 1, next) |
|
token.sourceEndIndex = token.unclosed ? next : next + 1 |
|
tokens.push(token) |
|
pos = next + 1 |
|
code = value.charCodeAt(pos) |
|
|
|
|
|
} else if (code === slash && value.charCodeAt(pos + 1) === star) { |
|
next = value.indexOf('*/', pos) |
|
|
|
token = { |
|
type: 'comment', |
|
sourceIndex: pos, |
|
sourceEndIndex: next + 2, |
|
} |
|
|
|
if (next === -1) { |
|
token.unclosed = true |
|
next = value.length |
|
token.sourceEndIndex = next |
|
} |
|
|
|
token.value = value.slice(pos + 2, next) |
|
tokens.push(token) |
|
|
|
pos = next + 2 |
|
code = value.charCodeAt(pos) |
|
|
|
|
|
} else if ((code === slash || code === star) && parent && parent.type === 'function' && true) { |
|
token = value[pos] |
|
tokens.push({ |
|
type: 'word', |
|
sourceIndex: pos - before.length, |
|
sourceEndIndex: pos + token.length, |
|
value: token, |
|
}) |
|
pos += 1 |
|
code = value.charCodeAt(pos) |
|
|
|
|
|
} else if (code === slash || code === comma || code === colon) { |
|
token = value[pos] |
|
|
|
tokens.push({ |
|
type: 'div', |
|
sourceIndex: pos - before.length, |
|
sourceEndIndex: pos + token.length, |
|
value: token, |
|
before: before, |
|
after: '', |
|
}) |
|
before = '' |
|
|
|
pos += 1 |
|
code = value.charCodeAt(pos) |
|
|
|
|
|
} else if (openParentheses === code) { |
|
|
|
next = pos |
|
do { |
|
next += 1 |
|
code = value.charCodeAt(next) |
|
} while (code <= 32) |
|
parenthesesOpenPos = pos |
|
token = { |
|
type: 'function', |
|
sourceIndex: pos - name.length, |
|
value: name, |
|
before: value.slice(parenthesesOpenPos + 1, next), |
|
} |
|
pos = next |
|
|
|
if (name === 'url' && code !== singleQuote && code !== doubleQuote) { |
|
next -= 1 |
|
do { |
|
escape = false |
|
next = value.indexOf(')', next + 1) |
|
if (~next) { |
|
escapePos = next |
|
while (value.charCodeAt(escapePos - 1) === backslash) { |
|
escapePos -= 1 |
|
escape = !escape |
|
} |
|
} else { |
|
value += ')' |
|
next = value.length - 1 |
|
token.unclosed = true |
|
} |
|
} while (escape) |
|
|
|
whitespacePos = next |
|
do { |
|
whitespacePos -= 1 |
|
code = value.charCodeAt(whitespacePos) |
|
} while (code <= 32) |
|
if (parenthesesOpenPos < whitespacePos) { |
|
if (pos !== whitespacePos + 1) { |
|
token.nodes = [ |
|
{ |
|
type: 'word', |
|
sourceIndex: pos, |
|
sourceEndIndex: whitespacePos + 1, |
|
value: value.slice(pos, whitespacePos + 1), |
|
}, |
|
] |
|
} else { |
|
token.nodes = [] |
|
} |
|
if (token.unclosed && whitespacePos + 1 !== next) { |
|
token.after = '' |
|
token.nodes.push({ |
|
type: 'space', |
|
sourceIndex: whitespacePos + 1, |
|
sourceEndIndex: next, |
|
value: value.slice(whitespacePos + 1, next), |
|
}) |
|
} else { |
|
token.after = value.slice(whitespacePos + 1, next) |
|
token.sourceEndIndex = next |
|
} |
|
} else { |
|
token.after = '' |
|
token.nodes = [] |
|
} |
|
pos = next + 1 |
|
token.sourceEndIndex = token.unclosed ? next : pos |
|
code = value.charCodeAt(pos) |
|
tokens.push(token) |
|
} else { |
|
balanced += 1 |
|
token.after = '' |
|
token.sourceEndIndex = pos + 1 |
|
tokens.push(token) |
|
stack.push(token) |
|
tokens = token.nodes = [] |
|
parent = token |
|
} |
|
name = '' |
|
|
|
|
|
} else if (closeParentheses === code && balanced) { |
|
pos += 1 |
|
code = value.charCodeAt(pos) |
|
|
|
parent.after = after |
|
parent.sourceEndIndex += after.length |
|
after = '' |
|
balanced -= 1 |
|
stack[stack.length - 1].sourceEndIndex = pos |
|
stack.pop() |
|
parent = stack[balanced] |
|
tokens = parent.nodes |
|
|
|
|
|
} else { |
|
next = pos |
|
do { |
|
if (code === backslash) { |
|
next += 1 |
|
} |
|
next += 1 |
|
code = value.charCodeAt(next) |
|
} while ( |
|
next < max && |
|
!( |
|
code <= 32 || |
|
code === singleQuote || |
|
code === doubleQuote || |
|
code === comma || |
|
code === colon || |
|
code === slash || |
|
code === openParentheses || |
|
(code === star && parent && parent.type === 'function' && true) || |
|
(code === slash && parent.type === 'function' && true) || |
|
(code === closeParentheses && balanced) |
|
) |
|
) |
|
token = value.slice(pos, next) |
|
|
|
if (openParentheses === code) { |
|
name = token |
|
} else if ( |
|
(uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) && |
|
plus === token.charCodeAt(1) && |
|
isUnicodeRange.test(token.slice(2)) |
|
) { |
|
tokens.push({ |
|
type: 'unicode-range', |
|
sourceIndex: pos, |
|
sourceEndIndex: next, |
|
value: token, |
|
}) |
|
} else { |
|
tokens.push({ |
|
type: 'word', |
|
sourceIndex: pos, |
|
sourceEndIndex: next, |
|
value: token, |
|
}) |
|
} |
|
|
|
pos = next |
|
} |
|
} |
|
|
|
for (pos = stack.length - 1; pos; pos -= 1) { |
|
stack[pos].unclosed = true |
|
stack[pos].sourceEndIndex = value.length |
|
} |
|
|
|
return stack[0].nodes |
|
} |
|
|