Summary: This function returns an ordered list of all duplicate class names, which can easily be used to merge classes.
To start off, get a useful list of duplicates:
var multi = {};
$("*[class]").each(function(){
var class = this.className.replace(/^\s+|\s+$/g,"").replace(/\s+/g,".");
if(!/\./.test(class)) return; //Ignore single classes
if(multi[class]){
multi[class]++;
} else {
multi[class] = 1;
}
});
//Now, merge duplicates, because .class1.class2 == .class2.class1
var multi_nodup = {};
for(var classes in multi){
var a_classes = classes.split(".");
var a_classes = a_classes.sort();
var a_classes = a_classes.join(".");
if(multi_nodup[a_classes]){
multi_nodup[a_classes] += multi[classes];
} else {
multi_nodup[a_classes] = multi[classes]
}
}
//Now, multi_npdup is a map of all duplicate classnames
var array_multi = [];
for(var classes in multi_nodup){
array_multi.push([multi_nodup[classes], classes]);
}
array_multi.sort(function(x,y){return y[0]-x[0]});
//array_multi is an array which looks like [["class1.class2.class2", 33],
// ["class3.class4", 30], ...]
// = A list, consisting of multiple class names, where multiple classnames
// are shown, together with the nuber of occurences, sorted according to
// the frequence
Execute my function, and output variable array_multi
. This will show you a map of multiple class names, so that you can replace multiple classnames, accordingly.
Because of the special way I stored the class names, you can use $("." + array_multi[n][0])
to access all elements which have a set of classname which equals to the set as described at the nth position in array_multi
.
Example of readable output:
//Overwrites current document!
var list = "";
for(var i=0; i<array_multi.length; i++) list += array_multi[i][0] + "\t" + array_multi[i][1];
document.open();
document.write("<pre>"+list+"</pre>")
document.close();
Automatic conversion
A way to automate the merging of the classnames i by adding all separate class properties to a JavaScript string, and add it to an object. This is the most reliable way to get the exact CSS properties, because attempting to get the classnames through the document.styleSheets
object can produce slightly different results. Example:
var classStyle = {};
classStyle["class1"] = "border:1px solid #000;";
classStyle["class2"] = "color:red";
//Make sure that each declaration ends with a semicolon:
for(var i in classStyle) if(!/;$/.test(classStyle[i])) classStyle[i] += ";";
//Initialise
var all_styles = {};
for(var i=0; i<array_multi.length; i++){
all_styles[array_multi[i][1]] = "";
}
//This loop takes definition precedence into account
for(var currentCName in classStyle){
var currentClass = new RegExp("(?:^|\\.)" + currentCName + "(?:\\.|$)");
// Rare occasion of failure: url("data:image/png,base64;....")
var separateProps = classStyle[currentCName].split(";");
var prop_RE = {};
for(var p=0; p<separateProps.length; p++){
var cssProperty = separateProps[p];
if(!/:/.test(cssProperty)) continue; //Invalid CSS property
prop_RE[cssProperty] = new RegExp("(^|;)\\s*" + cssProperty.match(/(\S+)\s*:/gi)[1] + "\\s*:[^;]+;?", "gi");
}
for(var class in all_styles){
if(currentClass.test(class)){
for(var k in prop_RE){
all_styles[class] = all_styles[class].replace(prop_RE[k],"$1") + k;
}
}
}
}
//To finish off:
var allClassesToString = "";
for(var class in all_styles){
var newClass = class.replace(/\./g, "_");
$("."+class).each(function(){
this.className = newClass;
});
allClassesToString += "."+newClass + "{" + all_styles[class] + "}\n";
}
// allClassesToString <------- This variable now holds a string of all duplicate CSS classes!
//Example:
var style = $("<style>");
style.text(allClassesToString);
style.appendTo($("head:first"));