123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- 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);
- module.exports = function (input) {
- var tokens = [];
- var value = input;
- var next, quote, prev, token, escape, escapePos, whitespacePos;
- 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) {
- // Whitespaces
- 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;
- } else if (code === comma || code === colon || code === slash && value.charCodeAt(next + 1) !== star) {
- before = token;
- } else {
- tokens.push({
- type: 'space',
- sourceIndex: pos,
- value: token
- });
- }
- pos = next;
- // Quotes
- } 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);
- tokens.push(token);
- pos = next + 1;
- code = value.charCodeAt(pos);
- // Comments
- } else if (code === slash && value.charCodeAt(pos + 1) === star) {
- token = {
- type: 'comment',
- sourceIndex: pos
- };
- next = value.indexOf('*/', pos);
- if (next === -1) {
- token.unclosed = true;
- next = value.length;
- }
- token.value = value.slice(pos + 2, next);
- tokens.push(token);
- pos = next + 2;
- code = value.charCodeAt(pos);
- // Dividers
- } else if (code === slash || code === comma || code === colon) {
- token = value[pos];
- tokens.push({
- type: 'div',
- sourceIndex: pos - before.length,
- value: token,
- before: before,
- after: ''
- });
- before = '';
- pos += 1;
- code = value.charCodeAt(pos);
- // Open parentheses
- } else if (openParentheses === code) {
- // Whitespaces after open parentheses
- next = pos;
- do {
- next += 1;
- code = value.charCodeAt(next);
- } while (code <= 32);
- token = {
- type: 'function',
- sourceIndex: pos - name.length,
- value: name,
- before: value.slice(pos + 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);
- // Whitespaces before closed
- whitespacePos = next;
- do {
- whitespacePos -= 1;
- code = value.charCodeAt(whitespacePos);
- } while (code <= 32);
- if (pos !== whitespacePos + 1) {
- token.nodes = [{
- type: 'word',
- sourceIndex: pos,
- 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,
- value: value.slice(whitespacePos + 1, next)
- });
- } else {
- token.after = value.slice(whitespacePos + 1, next);
- }
- pos = next + 1;
- code = value.charCodeAt(pos);
- tokens.push(token);
- } else {
- balanced += 1;
- token.after = '';
- tokens.push(token);
- stack.push(token);
- tokens = token.nodes = [];
- parent = token;
- }
- name = '';
- // Close parentheses
- } else if (closeParentheses === code && balanced) {
- pos += 1;
- code = value.charCodeAt(pos);
- parent.after = after;
- after = '';
- balanced -= 1;
- stack.pop();
- parent = stack[balanced];
- tokens = parent.nodes;
- // Words
- } 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 === closeParentheses && balanced
- ));
- token = value.slice(pos, next);
- if (openParentheses === code) {
- name = token;
- } else {
- tokens.push({
- type: 'word',
- sourceIndex: pos,
- value: token
- });
- }
- pos = next;
- }
- }
- for (pos = stack.length - 1; pos; pos -= 1) {
- stack[pos].unclosed = true;
- }
- return stack[0].nodes;
- };
|