You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					322 lines
				
				8.1 KiB
			
		
		
			
		
	
	
					322 lines
				
				8.1 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								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;
							 | 
						||
| 
								 | 
							
								        prev.sourceEndIndex += token.length;
							 | 
						||
| 
								 | 
							
								      } 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,
							 | 
						||
| 
								 | 
							
								          sourceEndIndex: next,
							 | 
						||
| 
								 | 
							
								          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);
							 | 
						||
| 
								 | 
							
								      token.sourceEndIndex = token.unclosed ? next : next + 1;
							 | 
						||
| 
								 | 
							
								      tokens.push(token);
							 | 
						||
| 
								 | 
							
								      pos = next + 1;
							 | 
						||
| 
								 | 
							
								      code = value.charCodeAt(pos);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Comments
							 | 
						||
| 
								 | 
							
								    } 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);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // 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,
							 | 
						||
| 
								 | 
							
								        sourceEndIndex: pos + token.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,
							 | 
						||
| 
								 | 
							
								        sourceEndIndex: pos + token.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,
							 | 
						||
| 
								 | 
							
								                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 = "";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Close parentheses
							 | 
						||
| 
								 | 
							
								    } 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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // 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,
							 | 
						||
| 
								 | 
							
								          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;
							 | 
						||
| 
								 | 
							
								};
							 |