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