322 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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;
 | |
| };
 |