Minify CSS with C#

The code below compresses and minifies CSS files. Minification is not the same as obfuscation. Minification removes all whitespace , comments and needless characters so that the end result is much smaller and is more difficult to read . Obfuscation does minification as well as mangles variables and functions so that the code is completely unreadable.

The C# class takes in a CSS file and minifies and writes it out to another file.
The class can be called in a single line of code :

 CSSMinify js = new CSSMinify(@"c:\myfiles\styles.css");


The modified file will be saved as c:\myfiles\styles.min.css

The code is not exactly optimized for speed or performance. Its more to show how minification is done. A faster way would be to use regular expressions on the whole css file instead of going character by character.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace CSSMinify
{
    class CSSMinify
    {
        private string mFileName = "";              // file to process
        private string mOriginalData = "";           // data from original file
        private string mModifiedData = "";          // processed data
        private bool mIsError = false;               // becomes true if any error happens
        private string mErr = "";                    // error message 
        private BinaryReader mReader = null;         // stream to process the file byte by byte

        private const int EOF = -1;                 // for end of file


           /// <summary>
        /// Constructor - does all the processing
        /// </summary>
        /// <param name="f">file path</param>
        public CSSMinify(string f) {

            try
            {
                if (File.Exists(f))
                {
                    mFileName = f;
                    
                    //read contents completely. This is only for test purposes. The actual processing is done by another stream
                    StreamReader rdr = new StreamReader(mFileName);
                    mOriginalData = rdr.ReadToEnd();
                    rdr.Close();

                    mReader = new BinaryReader(new FileStream(mFileName, FileMode.Open));
                    doProcess();
                    mReader.Close();

                    //write modified data
                    string outFile = mFileName.Replace(".css", ".min.css");
                    StreamWriter wrt = new StreamWriter(outFile);
                    wrt.Write(mModifiedData);
                    wrt.Close();

                }
                else {
                    mIsError = true;
                    mErr = "File does not exist";
                }

            }
            catch (Exception ex) {
                mIsError = true;
                mErr = ex.Message;
            }
        }

        /// <summary>
        /// Main process
        /// </summary>
        private void doProcess()
        {
            int lastChar = 1;                   // current byte read
            int thisChar = -1;                  // previous byte read
            int nextChar = -1;                  // byte read in peek()
            bool endProcess = false;            // loop control
            bool ignore = false;                // if false then add byte to final output
            bool inComment = false;             // true when current bytes are part of a comment
            bool isDoubleSlashComment = false;  // '//' comment


            // main processing loop
            while (!endProcess)
            {
                endProcess = (mReader.PeekChar() == -1);    // check for EOF before reading
                if (endProcess)
                    break;

                ignore = false;
                thisChar = mReader.ReadByte();

                if (thisChar == '\t')
                    thisChar = ' ';
                else if (thisChar == '\t')
                    thisChar = '\n';
                else if (thisChar == '\r')
                    thisChar = '\n';

                if (thisChar == '\n')
                    ignore = true;

                if (thisChar == ' ')
                {
                    if ((lastChar == ' ') || isDelimiter(lastChar) == 1)
                        ignore = true;
                    else
                    {
                        endProcess = (mReader.PeekChar() == -1); // check for EOF
                        if (!endProcess)
                        {
                            nextChar = mReader.PeekChar();
                            if (isDelimiter(nextChar) == 1)
                                ignore = true;
                        }
                    }
                }


                if (thisChar == '/')
                {
                    nextChar = mReader.PeekChar();
                    if (nextChar == '/' || nextChar == '*')
                    {
                        ignore = true;
                        inComment = true;
                        if (nextChar == '/')
                            isDoubleSlashComment = true;
                        else
                            isDoubleSlashComment = false;
                    }
                    if (nextChar == '/')
                    {
                        int x = 0;
                        x = x + 1;
                    }

                }

                // ignore all characters till we reach end of comment
                if (inComment)
                {
                    while (true)
                    {
                        thisChar = mReader.ReadByte();
                        if (thisChar == '*')
                        {
                            nextChar = mReader.PeekChar();
                            if (nextChar == '/')
                            {
                                thisChar = mReader.ReadByte();
                                inComment = false;
                                break;
                            }
                        }
                        if (isDoubleSlashComment && thisChar == '\n')
                        {
                            inComment = false;
                            break;
                        }

                    } // while (true)
                    ignore = true;
                } // if (inComment) 


                if (!ignore)
                    addToOutput(thisChar);

                lastChar = thisChar;
            } // while (!endProcess) 
        }

        /// <summary>
        /// Add character to modified data string
        /// </summary>
        /// <param name="c">char to add</param>
        private void addToOutput(int c)
        {
            mModifiedData += (char)c;
        }


        /// <summary>
        /// Original data from file
        /// </summary>
        /// <returns></returns>
        public string getOriginalData()
        {
            return mOriginalData;
        }

        /// <summary>
        /// Modified data after processing
        /// </summary>
        /// <returns></returns>
        public string getModifiedData()
        {
            return mModifiedData;
        }

        /// <summary>
        /// Check if a byte is alphanumeric
        /// </summary>
        /// <param name="c">byte to check</param>
        /// <returns>retval - 1 if yes. else 0</returns>
        private int isAlphanumeric(int c)
        {
            int retval = 0;

            if ((c >= 'a' && c <= 'z') ||
                (c >= '0' && c <= '9') ||
                (c >= 'A' && c <= 'Z') ||
                c == '_' || c == '$' || c == '\\' || c > 126)
                retval = 1;

            return retval;

        }

        /// <summary>
        /// Check if a byte is a delimiter 
        /// </summary>
        /// <param name="c">byte to check</param>
        /// <returns>retval - 1 if yes. else 0</returns>
        private int isDelimiter(int c)
        {
            int retval = 0;

            if (c == '(' || c == ',' || c == '=' || c == ':' ||
                c == '[' || c == '!' || c == '&' || c == '|' ||
                c == '?' || c == '+' || c == '-' || c == '~' ||
                c == '*' || c == '/' || c == '{' || c == '\n' ||
                c == ';'
            )
            {
                retval = 1;
            }

            return retval;

        }

    }
}

 

Original CSS File
body {
    margin-left:20px;
    margin-right:20px;
}
/*.navbar { background-color:#8cc449;}*/
.navbar-brand { background-color:#8cc449;}
.row-sep {height:25px;}
.blind {display:none;}

.popover {   color : tomato;}
.popover.bottom .arrow:after {
    border-bottom-color: tomato;
}



#topspacer {margin-top:60px;}

.nav-tabs > li > a {
  background-color: #032E42;
}
.panel {background-color: #000000;}
.panel-default > .panel-heading {background-color:#212020;}
.green-text {color: #8cc449;}



#graphTop10 {
    width: 95%;
    height: 280px;
}

#graphTop10Lifetime {
    width: 95%;
    height: 280px;
}
#graphRadio {
    width: 95%;
    height: 280px;
}

#graphTV {
    width: 95%;
    height: 280px;
}

#graphOverall {
    width: 95%;
    height: 280px;
}
#graphComparison{
    width: 95%;
    height: 280px;
}

.songstats {
    padding:5px;
    background-color:#212020;
    color:#8cc449;
    width:150px;
    height:200px;
}

.songinfo {
    padding: 5px;
    background-color: #212020;
    color: #8cc449;
    width: 150px;
    height: 200px;
}
 .centerit {text-align:center;}


 .white {color:#ffffff; }

 /**admin pages*/

 #graphFinger{
    width: 95%;
    height: 280px;
}

 #graphMatches {
    width: 95%;
    height: 280px;
}

.margin_bottom_large {
    height:20px;
}

/****audio.js media player customisations start*****/
.audiojs .scrubber {
    background: none repeat scroll 0 0 #5A5A5A;
    border-bottom: 0 none;
    border-left: 0 none;
    border-top: 1px solid #3F3F3F;
    float: left;
    height: 14px;
    margin: 10px;
    overflow: hidden;
    position: relative;
    width: 75px; /* smaller width */
}

.audiojs .time {
    border-left: 1px solid #000000;
    color: #DDDDDD;
    float: left;
    height: 36px;
    line-height: 36px;
    margin: 0; /* no margin */
    padding: 0 6px 0 9px; /* 2px smaller left padding */
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
}

.audiojs {
    font-family: monospace;
    width:90%;
}
/****audio.js media player customisations end *****/

Minified CSS File
#content.full-page{background-color:#556270;background-image:url("../images/noise.png");background-position:left top;background-repeat:repeat;background-size:auto auto;}.row-smallsep{height:10px;}.row-sep{height:25px;}.row-smallgap{height:35px;}.row-gap{height:50px;}.blind{display:none;}.popover{color:tomato;}.popover.bottom .arrow:after{border-bottom-color:tomato;}.bottom-align-text{position:absolute;bottom:0;right:0;}#topspacer{margin-top:60px;}.green-text{color:#8cc449;}.green-background{background-color:#8cc449;}.blue-text{color:#0094ff;}.green-text-large{color:#8cc449;font-size:1.250em;}.gray-text-large{color:#a5a8a8;font-size:1.250em;}.gray-text{color:#a5a8a8;}.station-total{font-size:1.250em;padding:1em;background-color:#8C7576;}.total-text-large{font-size:1.250em;}#graphTop10{width:95%;height:280px;}#graphTop10Lifetime{width:95%;height:280px;}#graphRadio{width:95%;height:280px;}#graphTV{width:95%;height:280px;}#graphOverall{width:95%;height:280px;}#graphComparison{width:95%;height:280px;}.songstats{padding:5px;background-color:#212020;color:#8cc449;width:150px;height:200px;}.songinfo{padding:5px;background-color:#212020;color:#8cc449;width:150px;height:200px;}.centerit{text-align:center;}.white{color:#ffffff;}.background_black{background-color:#000000;}.background_gray{background-color:#d4d4d4;}.background_darkgray{background-color:#556270;}.bottom_border_black{border-bottom:1px solid #000000;}#graphFinger{width:95%;height:280px;}#graphMatches{width:95%;height:280px;}.margin_bottom_large{height:20px;}#tableRadio>tbody>tr>th,.table>tbody>tr>td{border-top:none;}.line_divider{height:1px;width:100%;display:block;margin:9px 0;overflow:hidden;}.audiojs .scrubber{background:none repeat scroll 0 0 #5A5A5A;border-bottom:0 none;border-left:0 none;border-top:1px solid #3F3F3F;float:left;height:14px;margin:10px;overflow:hidden;position:relative;width:75px;}.audiojs .time{border-left:1px solid #000000;color:#DDDDDD;float:left;height:36px;line-height:36px;margin:0;padding:0 6px 0 9px;text-shadow:1px 1px 0 rgba(0,0,0,0.5);}.audiojs{font-family:monospace;width:90%;}.pageheader{}.adblock1{width:auto;max-width:970px;height:90px;*/}.adblock1 img{width:100%;}.adblock2{width:336px;height:280px;margin:0px auto;}.adblock3{width:336px;height:280px;margin:0px auto;}.spaced_text{line-height:1.628571429;}.bottom_margin_10{margin-bottom:10px;}.image_grayscale{-webkit-filter:grayscale(1);filter:grayscale(1);filter:gray;}#lblBroadcaster:hover{cursor:pointer;}.joyride-tip-guide{background:#000;}

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.