123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- 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) {
- // 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 &&
- (!parent ||
- (parent && parent.type === "function" && parent.value !== "calc")))
- ) {
- 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);
- // Operation within calc
- } else if (
- (code === slash || code === star) &&
- parent &&
- parent.type === "function" &&
- parent.value === "calc"
- ) {
- token = value[pos];
- tokens.push({
- type: "word",
- sourceIndex: pos - before.length,
- value: token
- });
- pos += 1;
- 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);
- 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);
- // Whitespaces before closed
- 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,
- 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);
- }
- } else {
- token.after = "";
- token.nodes = [];
- }
- 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 === star &&
- parent &&
- parent.type === "function" &&
- parent.value === "calc") ||
- (code === slash &&
- parent.type === "function" &&
- parent.value === "calc") ||
- (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,
- value: 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;
- };
|