185 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
		
		
			
		
	
	
			185 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
|  | 
 | ||
|  | // Helper for tokenizing the contents of a CSS selector block
 | ||
|  | 
 | ||
|  | module.exports = (function() { | ||
|  |   var createTokenPrototype = function (processable) { | ||
|  |     var important = '!important'; | ||
|  | 
 | ||
|  |     // Constructor for tokens
 | ||
|  |     function Token (prop, p2, p3) { | ||
|  |       this.prop = prop; | ||
|  |       if (typeof(p2) === 'string') { | ||
|  |         this.value = p2; | ||
|  |         this.isImportant = p3; | ||
|  |       } | ||
|  |       else { | ||
|  |         this.value = processable[prop].defaultValue; | ||
|  |         this.isImportant = p2; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     Token.prototype.prop = null; | ||
|  |     Token.prototype.value = null; | ||
|  |     Token.prototype.granularValues = null; | ||
|  |     Token.prototype.components = null; | ||
|  |     Token.prototype.position = null; | ||
|  |     Token.prototype.isImportant = false; | ||
|  |     Token.prototype.isDirty = false; | ||
|  |     Token.prototype.isShorthand = false; | ||
|  |     Token.prototype.isIrrelevant = false; | ||
|  |     Token.prototype.isReal = true; | ||
|  |     Token.prototype.isMarkedForDeletion = false; | ||
|  | 
 | ||
|  |     // Tells if this token is a component of the other one
 | ||
|  |     Token.prototype.isComponentOf = function (other) { | ||
|  |       if (!processable[this.prop] || !processable[other.prop]) | ||
|  |         return false; | ||
|  |       if (!(processable[other.prop].components instanceof Array) || !processable[other.prop].components.length) | ||
|  |         return false; | ||
|  | 
 | ||
|  |       return processable[other.prop].components.indexOf(this.prop) >= 0; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Clones a token
 | ||
|  |     Token.prototype.clone = function (isImportant) { | ||
|  |       var token = new Token(this.prop, this.value, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant)); | ||
|  |       return token; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Creates an irrelevant token with the same prop
 | ||
|  |     Token.prototype.cloneIrrelevant = function (isImportant) { | ||
|  |       var token = Token.makeDefault(this.prop, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant)); | ||
|  |       token.isIrrelevant = true; | ||
|  |       return token; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Creates an array of property tokens with their default values
 | ||
|  |     Token.makeDefaults = function (props, important) { | ||
|  |       return props.map(function(prop) { | ||
|  |         return new Token(prop, important); | ||
|  |       }); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Parses one CSS property declaration into a token
 | ||
|  |     Token.tokenizeOne = function (fullProp) { | ||
|  |       // Find first colon
 | ||
|  |       var colonPos = fullProp.indexOf(':'); | ||
|  | 
 | ||
|  |       if (colonPos < 0) { | ||
|  |         // This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
 | ||
|  |         return new Token('', fullProp); | ||
|  |       } | ||
|  | 
 | ||
|  |       // Parse parts of the property
 | ||
|  |       var prop = fullProp.substr(0, colonPos).trim(); | ||
|  |       var value = fullProp.substr(colonPos + 1).trim(); | ||
|  |       var isImportant = false; | ||
|  |       var importantPos = value.indexOf(important); | ||
|  | 
 | ||
|  |       // Check if the property is important
 | ||
|  |       if (importantPos >= 1 && importantPos === value.length - important.length) { | ||
|  |         value = value.substr(0, importantPos).trim(); | ||
|  |         isImportant = true; | ||
|  |       } | ||
|  | 
 | ||
|  |       // Return result
 | ||
|  |       var result = new Token(prop, value, isImportant); | ||
|  | 
 | ||
|  |       // If this is a shorthand, break up its values
 | ||
|  |       // NOTE: we need to do this for all shorthands because otherwise we couldn't remove default values from them
 | ||
|  |       if (processable[prop] && processable[prop].isShorthand) { | ||
|  |         result.isShorthand = true; | ||
|  |         result.components = processable[prop].breakUp(result); | ||
|  |         result.isDirty = true; | ||
|  |       } | ||
|  | 
 | ||
|  |       return result; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Breaks up a string of CSS property declarations into tokens so that they can be handled more easily
 | ||
|  |     Token.tokenize = function (input) { | ||
|  |       // Split the input by semicolons and parse the parts
 | ||
|  |       var tokens = input.split(';').map(Token.tokenizeOne); | ||
|  |       return tokens; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Transforms tokens back into CSS properties
 | ||
|  |     Token.detokenize = function (tokens) { | ||
|  |       // If by mistake the input is not an array, make it an array
 | ||
|  |       if (!(tokens instanceof Array)) { | ||
|  |         tokens = [tokens]; | ||
|  |       } | ||
|  | 
 | ||
|  |       var result = ''; | ||
|  | 
 | ||
|  |       // This step takes care of putting together the components of shorthands
 | ||
|  |       // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
 | ||
|  |       for (var i = 0; i < tokens.length; i++) { | ||
|  |         var t = tokens[i]; | ||
|  |         if (t.isShorthand && t.isDirty) { | ||
|  |           var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant); | ||
|  |           Array.prototype.splice.apply(tokens, [i, 1].concat(news)); | ||
|  |           t.isDirty = false; | ||
|  |           i--; | ||
|  |           continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (t.prop) | ||
|  |           result += t.prop + ':'; | ||
|  | 
 | ||
|  |         if (t.value) | ||
|  |           result += t.value; | ||
|  | 
 | ||
|  |         if (t.isImportant) | ||
|  |           result += important; | ||
|  | 
 | ||
|  |         result += ';'; | ||
|  |       } | ||
|  | 
 | ||
|  |       return result.substr(0, result.length - 1); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Gets the final (detokenized) length of the given tokens
 | ||
|  |     Token.getDetokenizedLength = function (tokens) { | ||
|  |       // If by mistake the input is not an array, make it an array
 | ||
|  |       if (!(tokens instanceof Array)) { | ||
|  |         tokens = [tokens]; | ||
|  |       } | ||
|  | 
 | ||
|  |       var result = 0; | ||
|  | 
 | ||
|  |       // This step takes care of putting together the components of shorthands
 | ||
|  |       // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
 | ||
|  |       for (var i = 0; i < tokens.length; i++) { | ||
|  |         var t = tokens[i]; | ||
|  |         if (t.isShorthand && t.isDirty) { | ||
|  |           var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant); | ||
|  |           Array.prototype.splice.apply(tokens, [i, 1].concat(news)); | ||
|  |           t.isDirty = false; | ||
|  |           i--; | ||
|  |           continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (t.prop) { | ||
|  |           result += t.prop.length + 1; | ||
|  |         } | ||
|  |         if (t.value) { | ||
|  |           result += t.value.length; | ||
|  |         } | ||
|  |         if (t.isImportant) { | ||
|  |           result += important.length; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       return result; | ||
|  |     }; | ||
|  | 
 | ||
|  |     return Token; | ||
|  |   }; | ||
|  | 
 | ||
|  |   return { | ||
|  |     createTokenPrototype: createTokenPrototype | ||
|  |   }; | ||
|  | 
 | ||
|  | })(); |