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; | ||
|  | }; |