Friday, August 17, 2012

Sorting CSS styles and classes in your files with a Python script



When editing CSS files, it’s quite common that the length of the file gets so big that it’s hard to find the classes in it. Some IDEs like Eclipse help you to show a sorted outline of the CSS file, but it will not sort the file for you as you want to.

I’ve barely seen any larger projects with nicely structured CSS files, which is partially because neither the editors nor the tools are perfect for CSS editing.

To highlight one of the many problems with CSS, let’s see a simple snippet:

#mystyle li{
      color:green;
}

input
{
background-color: red;
}

#mystyle
{
      /*should not use . and # with the same name!*/
}

b{
font-size: 1.2em;
}

.mystyle{
      font-weight: bold;
}

It’s a bit mixed up here and there, not well formatted, the DOM level CSS styles are mixed with the ID and class definitions, the names are not in sorted order. It’s pretty hard to read.

How about we sort and pretty print it a bit:


b {  
      font-size: 1.2em;
}

input {   
      background-color: red;
}

.mystyle
      font-weight: bold;
}

#mystyle
      /*should not use . and # with the same name!*/
}

#mystyle li {    
      color:green;
}


Looking at the second output, it’s much easier to see what’s going on. It’s fairly obvious now that B and INPUT are the only DOM elements that are overridden and we have some issues around the ‘mystyle’ naming. Moreover, it’s easy to see that any LI within ‘mystyle’ will be green.

To sort a CSS file styles like the above, I’ve created a simple Python script (70 lines) that reads the original CSS and outputs the sorted version to the STDOUT. It does not handle special cases like one line CSS styles and commented out styles, but to be honest I don’t really create those so I don’t need to handle them either.

import re
import argparse

class CSSClass:
    def __init__(self, name, body):
        self.name = name
        self.body = body
    pass

    @staticmethod
    def parseFile(filename):
        f = open(filename)
        name = ''
        body = []
        prev = ''
        cssarr = []
        
        for lineOrig in f:
            line = lineOrig.strip()
            
            slineopen = line.split('{')
            slineclose = line.split('}')
            
            if len(slineopen) > 1:
                body = ['']
                sline0 = slineopen[0].strip()
                if sline0 == '':
                    name = prev # // { in new line
                else:
                    name = sline0 # // { in line with name
            elif len(slineclose) == 2:
                css = CSSClass(name, body)
                cssarr.append(css)
                body = []
                name = ''
            else:
                body.append(line)
            
            prev = line
        
        return cssarr

    @staticmethod
    def csscomparator(cssx, cssy):
        x = cssx.name
        y = cssy.name
        xsw = False
        ysw = False
        
        if re.match('^[.#]', x):
            x = x[1:]
            xsw = True
        if re.match('^[.#]', y):
            y = y[1:]
            ysw = True
            
        if xsw == ysw: # both class or top level
            return cmp(x, y)    
        
        return 1 if xsw else -1 # // if X is class, it's larger
    
    def __str__(self):
        return '%s {\t%s\n}\n' % (self.name,'\n\t'.join( self.body ))
    
if __name__ == '__main__':
    
    parser = argparse.ArgumentParser(description='Sort the style names in your CSS file')
    parser.add_argument('input', metavar='style.css', help='input CSS to be sorted. Output is STDOUT')

    args = parser.parse_args()
    
    cssarr = CSSClass.parseFile(args.input)
        
    cssarr.sort(CSSClass.csscomparator)
    
    for css in cssarr:
        print css
    
    pass



To run it, simply do:

$python cssSorter.py styles.css > sortedstyles.css

No comments:

Post a Comment