CSS File Minification in C#

CSS files are usually pretty riddled with white space, extra characters, etc. that are not needed to work but is simply there so we can go in and read the file. And to be honest we need to keep them that way so that we can go and modify them. It's an endless fight between size vs readability.

So how do we go about getting the best of both worlds? Well you can add a step to your production cycle and use one of the many CSS minification apps out there (such as the YUI Compressor). And they do a good job but it's another added step, you have to make sure you run it on every CSS file, etc. I find them to be a pain personally. That leaves you with minifying the file at run time. And to be honest, it's rather simple and can be done with the following function:

namespace Utilities
{
    /// <summary>
    /// CSS utility class
    /// </summary>
    public static class CSS
    {
        #region Static Public Functions
        /// <summary>
        /// Strips whitespace from a CSS file
        /// </summary>
        /// <param name="Input">Input text</param>
        /// <returns>A stripped CSS file</returns>
        public static string StripWhitespace(string Input)
        {
            Input = Input.Replace("  ", string.Empty);
            Input = Input.Replace(System.Environment.NewLine, string.Empty);
            Input = Input.Replace("\t", string.Empty);
            Input = Input.Replace(" {", "{");
            Input = Input.Replace(" :", ":");
            Input = Input.Replace(": ", ":");
            Input = Input.Replace(", ", ",");
            Input = Input.Replace("; ", ";");
            Input = Input.Replace(";}", "}");
            Input = Regex.Replace(Input, @"(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,}(?=&nbsp;)|(?<=&ndsp;)\s{2,}(?=[<])", string.Empty);
            Input = Regex.Replace(Input, "([!{}:;>+([,])s+", "$1");
            Input = Regex.Replace(Input, "([^;}])}", "$1;}");
            Input = Regex.Replace(Input, "([s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
            Input = Regex.Replace(Input, ":0 0 0 0;", ":0;");
            Input = Regex.Replace(Input, ":0 0 0;", ":0;");
            Input = Regex.Replace(Input, ":0 0;", ":0;");
            Input = Regex.Replace(Input, "background-position:0;", "background-position:0 0;");
            Input = Regex.Replace(Input, "(:|s)0+.(d+)", "$1.$2");
            Input = Regex.Replace(Input, "[^}]+{;}", "");
            Input = Regex.Replace(Input, "(/" + Regex.Escape("*") + ".*?" + Regex.Escape("*") + "/)", string.Empty);
            return Input;
        }
        #endregion
    }
}

The code above strips out all unnecessary whitespace from the string that is entered, removes comments, etc. We could even go in and reduce the size of hex colors if we really wanted, but I'm leaving those alone as the space savings is rather negligible. So what we would do, is set up an HTTPHandler that takes in the path to a CSS file. The handler loads the file and then calls the function above with the resulting string being the value that we return to the user. We could even improve the handler further by allowing it to receive multiple file locations, con cat the file content together and then feed it into the function above. Anyway, definitely give it a try, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 6/2/2009 at 8:43 AM
Tags: , , ,
Categories: C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Turning off Pretty Printing in ASP.Net

Have you ever noticed that when a page is sent in ASP.Net, that it comes in a "pretty printing" sort of format? Ever wanted to get rid of those extra spaces, tabs, etc. to save some bandwidth? It's actually really easy to accomplish. All that you need to do is change the stream that that page uses for output. This can be done by writing your own Stream and changing the response's filter to a new instance of that stream.

The Stream

The stream code is as follows:

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace Site
{
    public class UglyStream:Stream
    {
        private string Compression;
        private Stream StreamUsing;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="StreamUsing">The stream for the page</param>
        /// <param name="Compression">The compression we're using (gzip or deflate)</param>
        public UglyStream(Stream StreamUsing,string Compression)
        {
            this.Compression = Compression;
            this.StreamUsing = StreamUsing;
        }

        /// <summary>
        /// Doesn't deal with reading
        /// </summary>
        public override bool CanRead
        {
            get { return false; }
        }

        /// <summary>
        /// No seeking
        /// </summary>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// Can write out though
        /// </summary>
        public override bool CanWrite
        {
            get { return true; }
        }

        /// <summary>
        /// Nothing to flush
        /// </summary>
        public override void Flush()
        {
           
        }

        /// <summary>
        /// Don't worry about
        /// </summary>
        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary>
        /// No position to take care of
        /// </summary>
        public override long Position
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Don't worry about
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Once again not implemented
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="origin"></param>
        /// <returns></returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Don't worry about
        /// </summary>
        /// <param name="value"></param>
        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Actually writes out the data
        /// </summary>
        /// <param name="buffer">the page's data in byte form</param>
        /// <param name="offset">offset of the data</param>
        /// <param name="count">the amount of data</param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            //Copy the data into our own spot
            byte[] data = new byte[count];
            Buffer.BlockCopy(buffer, offset, data, 0, count);
            //Convert it to a string
            string inputstring = Encoding.ASCII.GetString(data);

            //Remove pretty print formatting
            inputstring = Regex.Replace(inputstring, @">[\s\S]*?<", new MatchEvaluator(Evaluate));

            //convert string to bytes again
            data = Encoding.ASCII.GetBytes(inputstring);

            //compress that data here

            //Write it out to the page's stream
            StreamUsing.Write(data, 0, inputstring.Length);
        }

        /// <summary>
        /// Evaluates whether the text has spaces, page breaks, etc. and removes them.
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected string Evaluate(Match Matcher)
        {
            string MyString = Matcher.ToString();
            MyString = Regex.Replace(MyString, @"\r\n\s*", "");
            return MyString;
        }
    }
}

That's all there is to it. Note though that you have to add in your own compression (or you can look back at my viewstate compression post and cannibalize the code from that). Most of the items aren't even implemented, the only ones that matter are Flush and Write. Both of these have to be implemented. Other than that, who cares. Then all we need to do is on the page we want to use it we add:

Response.Filter=new UglyStream(Response.Filter,"deflate");

And voilà, we have turned off pretty printing. I'm actually a bit surprised that this sort of stuff isn't done by default. The viewstate compression, HTTP compression, pretty printing, etc. or at least give us an easy switch in the config file or something. Maybe it is there and I've just been too lazy to find it, but I'm guessing no... Anyway, use the code, leave feedback, and as always happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 5/9/2008 at 1:49 PM
Tags: , ,
Categories: ASP.Net | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Viewstate Compression When Using AJAX

One thing that might annoy you with ASP.Net is the size of the viewstate when your using heavy controls (DataGrids, etc.). One thing to amelierate this is to obviously turn off the viewstate for any item it isn't needed. However this can only save you so much space and it might not be enough. In cases where your bandwidth are still an issue you can try compressing the viewstate. This is actually very easy to do, just use a deflatestream to compress the data and save it as a hidden field on the page. In fact to show you this approach I've created two classes, Deflate and ZippedPage (note though that you need to have a scriptmanager on the page for them to work): 

ZippedPage.zip (1.22 kb)

When I started using AJAX, I noticed that items weren't being updated during an async postback any more if I was using the original versions of these classes. It took me about 2 days to figure out that the issue was that the scriptmanager had to have the hidden field registered to it... Specifically this line had to be added:

ScriptManager.RegisterHiddenField(this, viewstatename, Convert.ToBase64String(ViewStateBytes));

So if you've created something similar to this and were running into that issue, that line will fix your issues. Anyway, download the code and read it and hopefully you'll learn something or perhaps you can show me a better way. As always comments are welcome.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 3/6/2008 at 2:11 PM
Tags: , ,
Categories: AJAX | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed