Click here to Skip to main content
15,884,353 members
Articles / Web Development / ASP.NET

HTTP Compression Module

Rate me:
Please Sign up or sign in to vote.
4.81/5 (39 votes)
19 Mar 2008CPOL6 min read 365.6K   4.8K   153  
A compression module for ASP.NET that works with WebResource.axd, JavaScript, and CSS
/**
 * MyMin - JSMin and packer like alternative parser for both JavaScript and CSS
 *
 * This class is a jsmin alternative, based on same parser logic but modified
 * to mantain performances and to parse correctly JavaScript conditional comments too.
 * This file contains another class too, called MyMinCompressor, based on
 * personal fast and strongly compatible decompression method, inspired by
 * Dean Edwards packer logic ( http://dean.edwards.name/packer/ )
 * but compatible with every kind of source code.
 *
 * CLIENT SIDE
 * Internet Explorer <= 5 requires a correct String.prototype.replace
 * implementation (http://www.3site.eu/replace.js) to decode compressed source.
 * This code is compatible with every JavaScript compatible browser.
 *
 * SERVER SIDE
 * .NET 2.0 or greater is required.
 * The best practice to use this code is caching results without run-time
 * evaluation (your server should be stressed too much with big files)
 *
 * Permission is hereby granted to use this version of the library under the
 * same terms as jsmin.php, which has the following license:
 *
 * --
 * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * The Software shall be used for Good, not Evil.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * --
 *
 * @class	MyMin
 *		MyMinCompressor
 *		MyMinException
 * @author	Andrea Giammarchi <http://www.3site.eu>
 * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
 * @copyright 2007 Ryan Grove <ryan@wonko.com> (PHP port)
 * @copyright 2007 Andrea Giammarchi (improvements + MyMinCompressor + MyMinCSS)
 * @license http://opensource.org/licenses/mit-license.php MIT License
 * @version 1.0.1 (2007-10-05)
 */

namespace util
{
	class MyMin
	{

		#region public constants
		public const System.Char LF = '\n', SPACE = ' ', EOS = System.Char.MinValue;
		#endregion

		#region protected variables
		protected System.Boolean cc_on;
		protected System.Char a, ahead, b;
		protected System.Int32 index = 0, length;
		protected System.String input;
		protected System.Text.StringBuilder output = new System.Text.StringBuilder();
		#endregion

		#region static public methods
		static public System.String parse(System.String input)
		{
			return MyMin.parse(input, true);
		}
		static public System.String parse(System.String input, System.Boolean cc_on)
		{
			return new MyMin(input, cc_on).ToString();
		}
		static public System.String parse(System.String input, System.Boolean cc_on, System.Boolean css)
		{
			return css ? new MyMinCSS(input, cc_on).ToString() : new MyMin(input, cc_on).ToString();
		}
		#endregion

		#region constructor
		public MyMin(System.String input)
		{
			this.init(input, true);
		}

		public MyMin(System.String input, System.Boolean cc_on)
		{
			this.init(input, cc_on);
		}
		#endregion

		#region public override methods
		public override System.String ToString()
		{
			return this.output.ToString().TrimStart().Replace("\n\n", "\n");
		}

		public static implicit operator System.String(MyMin o)
		{
			return o.ToString();
		}
		#endregion

		#region virtual protected methods
		virtual protected void action(System.Int32 i)
		{
			if (i < 2)
				this.output.Append(this.a);
			if (i < 3)
			{
				this.a = this.b;
				if (this.a == '\'' || this.a == '"')
				{
					while (true)
					{
						this.output.Append(this.a);
						if (!this.nextCharNoSlash(this.b, "Unterminated string literal."))
							break;
					}
				}
			}
			if (i < 4)
			{
				this.b = this.next();
				if (this.b == '/')
				{
					switch (this.a)
					{
						case MyMin.LF:
						case MyMin.SPACE:
						case '{':
						case ';':

						case '(':
						case ',':
						case '=':
						case ':':
						case '[':
						case '!':
						case '&':
						case '|':
						case '?':
							if ((MyMin.LF == this.a || MyMin.SPACE == this.a) && !this.spaceBeforeRegExp(this.output.ToString()))
								break;
							this.output.Append(this.a);
							this.output.Append(this.b);
							while (this.nextCharNoSlash('/', "Unterminated regular expression literal."))
								this.output.Append(this.a);
							this.b = this.next();
							break;
					}
				}
			}
		}

		virtual protected void appendComment(System.Int32 pos, System.String open, System.String close)
		{
			this.output.Append(this.a);
			this.output.Append(open);
			this.output.Append(new MyMin(this.input.Substring(this.index, pos - this.index), this.cc_on));
			this.output.Append(close);
			this.index = pos;
			this.a = MyMin.LF;
		}

		virtual protected void conditionalComment(System.Char find)
		{
			System.Int32 pos = this.input.IndexOf(find, this.index);
			if (pos < 0)
				pos = this.length;
			this.appendComment(pos, "//", find.ToString());
		}

		virtual protected void conditionalComment(System.String find)
		{
			System.Int32 pos = this.input.IndexOf(find, this.index);
			if (pos < 0)
				throw new MyMinException("Unterminated comment.");
			this.appendComment(pos, "/*", find);
		}

		virtual protected System.Char get()
		{
			System.Char c = this.ahead;
			this.ahead = MyMin.EOS;
			if (c == MyMin.EOS && this.index < this.length)
				c = this.input[this.index++];
			return (c == MyMin.EOS || c == MyMin.LF || c >= MyMin.SPACE) ? c : MyMin.SPACE;
		}

		virtual protected void init(System.String input, System.Boolean cc_on)
		{
			this.input = System.Text.RegularExpressions.Regex.Replace(input.Trim(), "(\n\r|\r\n|\r|\n)+", MyMin.LF.ToString());
			this.length = this.input.Length;
			this.cc_on = cc_on;
			this.a = MyMin.LF;
			this.action(3);
			while (this.a != MyMin.EOS)
			{
				switch (this.a)
				{
					case MyMin.SPACE:
						this.action(this.isAlNum(this.b) ? 1 : 2);
						break;
					case MyMin.LF:
						switch (this.b)
						{
							case '{':
							case '[':
							case '(':
							case '+':
							case '-':
								this.action(1);
								break;
							case MyMin.SPACE:
								this.action(3);
								break;
							default:
								this.action(this.isAlNum(this.b) ? 1 : 2);
								break;
						}
						break;
					default:
						switch (this.b)
						{
							case MyMin.SPACE:
								this.action(this.isAlNum(this.a) ? 1 : 3);
								break;
							case MyMin.LF:
								switch (this.a)
								{
									case '}':
									case ']':
									case ')':
									case '+':
									case '-':
									case '"':
									case '\'':
										this.action(1);
										break;
									default:
										this.action(this.isAlNum(this.a) ? 1 : 3);
										break;
								}
								break;
							default:
								this.action(1);
								break;
						}
						break;
				}
			}
		}

		virtual protected System.Boolean isAlNum(System.Char c)
		{
			return c > 126 || c == '\\' || System.Text.RegularExpressions.Regex.IsMatch(c.ToString(), "^(\\w|\\$)$");
		}

		virtual protected System.Char next()
		{
			System.Char c = this.get();
			System.Boolean loop = true;
			if (c == '/')
			{
				switch (this.ahead = this.get())
				{
					case '/':
						if (this.cc_on && this.input[this.index] == '@')
							this.conditionalComment(MyMin.LF);
						while (loop)
						{
							c = this.get();
							if (c <= MyMin.LF)
								loop = false;
						}
						break;
					case '*':
						this.get();
						if (this.cc_on && this.input[this.index] == '@')
							this.conditionalComment("*/");
						while (loop)
						{
							switch (this.get())
							{
								case '*':
									if ((this.ahead = this.get()) == '/')
									{
										this.get();
										c = MyMin.SPACE;
										loop = false;
									}
									break;
								case MyMin.EOS:
									throw new MyMinException("Unterminated comment.");
							}
						}
						break;
				}
			}
			return c;
		}

		virtual protected System.Boolean nextCharNoSlash(System.Char c, System.String message)
		{
			System.Boolean loop = true;
			this.a = this.get();
			if (this.a == c)
				loop = false;
			else
			{
				if (this.a == '\\')
				{
					this.output.Append(this.a);
					this.a = this.get();
				}
				if (this.a <= MyMin.LF)
					throw new MyMinException(message);
			}
			return loop;
		}

		virtual protected System.Boolean spaceBeforeRegExp(System.String output)
		{
			System.Int32 i, length = output.Length;
			System.Boolean result = false;
			System.String tmp;
			System.String[] reserved = "case.else.in.return.typeof".Split('.');
			for (i = 0; i < 5 && !result; i++)
			{
				if (length == reserved[i].Length)
					result = reserved[i] == output;
				else if (length > reserved[i].Length)
				{
					tmp = output.Substring(length - reserved[i].Length - 1);
					result = tmp.Substring(1) == reserved[i] && !this.isAlNum(tmp[0]);
				}
			}
			return length < 2 ? true : result;
		}
		#endregion
	}

	class MyMinCompressor
	{

		#region protected variables
		protected System.Int32 baseLength = 0;
		protected System.String baseString = "0123456789abcdefghijklmnopqrstuvwxyz";
		protected System.Collections.Generic.Dictionary<System.String, System.Int32> dict;
		protected System.Collections.Generic.Dictionary<System.String, System.String>[] list;
		protected System.String[] keywords;
		#endregion

		#region constructor
		public MyMinCompressor()
		{
			this.init(36);
		}

		public MyMinCompressor(System.Int32 baseLength)
		{
			this.init(baseLength);
		}
		#endregion

		#region public methods
		public System.String getCSSMachine(System.String css)
		{
			return this.getCSSMachine(css, System.String.Empty);
		}

		public System.String getCSSMachine(System.String css, System.String media)
		{
			System.Text.StringBuilder result = new System.Text.StringBuilder();
			result.Append("(function(M,y,m,i,n,C,S,s){if(m[n]){S=m.styleSheets;s=m.createElement(\"style\");s.type=\"text/css\";s.media=C||\"all\";M=M.replace(/\\w+/g,function(m){return y[parseInt(m,");
			result.Append(this.baseLength);
			result.Append(")]});m[n](\"head\")[0][i](s);/*@cc_on if(!(S[S.length-1].cssText=M))@*/s[i](m.createTextNode(M));m.write('<link hide=\"')}})('");
			result.Append(this.parse(css));
			result.Append("','");
			result.Append(System.String.Join(".", this.keywords));
			result.Append("'.split('.'),document,'appendChild','getElementsByTagName'");
			result.Append(media != System.String.Empty ? ",'" + media + "'" : "");
			result.Append(')');
			return result.ToString();
		}

		public System.String getJSMachine(System.String js)
		{
			System.Text.StringBuilder result = new System.Text.StringBuilder();
			result.Append("eval((function(M){return '");
			result.Append(this.parse(js));
			result.Append("'.replace(/\\w+/g,function(m){return M[parseInt(m,");
			result.Append(this.baseLength);
			result.Append(")]})})('");
			result.Append(System.String.Join(".", this.keywords));
			result.Append("'.split('.')))");
			return result.ToString();
		}

		public System.String getMachine(System.String js, System.String css)
		{
			return this.getMachine(js, css, System.String.Empty);
		}

		public System.String getMachine(System.String js, System.String css, System.String media)
		{
			System.Text.StringBuilder result = new System.Text.StringBuilder();
			System.String[] source;
			result.Append(js);
			result.Append(MyMin.EOS);
			result.Append(css);
			source = this.parse(result.ToString()).Split(MyMin.EOS);
			result = new System.Text.StringBuilder();
			result.Append("eval((function(M,y,m,i,n,C,S,s){if(m[n]){S=m.styleSheets;s=m.createElement(\"style\");s.type=\"text/css\";s.media=C||\"all\";C=M('");
			result.Append(source[1]);
			result.Append("',y);m[n](\"head\")[0][i](s);/*@cc_on if(!(S[S.length-1].cssText=C))@*/s[i](m.createTextNode(C));m.write('<link hide=\"')}return s?M('");
			result.Append(source[0]);
			result.Append("',y):\"\"})(function(s,l){return s.replace(/\\w+/g,function(m){return l[parseInt(m,");
			result.Append(this.baseLength);
			result.Append(")]})},'");
			result.Append(System.String.Join(".", this.keywords));
			result.Append("'.split('.'),document,'appendChild','getElementsByTagName'");
			result.Append(media != System.String.Empty ? ",'" + media + "'" : "");
			result.Append("))");
			return result.ToString();
		}
		#endregion

		#region virtual protected methods
		virtual protected System.String convertToBase(System.Int32 num)
		{
			System.Int32 module = 0;
			System.String result = "";
			while (num > 0)
			{
				result = this.baseString[(module = num % this.baseLength)].ToString() + result;
				num = (System.Int32)((num - module) / this.baseLength);
			}
			return result != "" ? result : this.baseString[0].ToString();
		}

		virtual protected System.String countMatches(System.Text.RegularExpressions.Match m)
		{
			if (!this.dict.ContainsKey(m.Value))
				this.dict.Add(m.Value, 0);
			this.dict[m.Value]++;
			return System.String.Empty;
		}

		virtual protected void init(System.Int32 baseLength)
		{
			this.baseLength = baseLength;
			this.baseString = this.baseString.Substring(0, baseLength);
		}

		virtual protected System.String parse(System.String source)
		{
			System.Int32 i = 0;
			System.Collections.Generic.Dictionary<System.String, System.String> tmp;
			this.dict = new System.Collections.Generic.Dictionary<System.String, System.Int32>();
			source = source.Replace("\\", "\\\\").Replace("'", "\\'");
			new System.Text.RegularExpressions.Regex(@"\w+").Replace(source, new System.Text.RegularExpressions.MatchEvaluator(this.countMatches));
			this.list = new System.Collections.Generic.Dictionary<System.String, System.String>[this.dict.Keys.Count];
			foreach (System.String key in this.dict.Keys)
			{
				tmp = new System.Collections.Generic.Dictionary<System.String, System.String>();
				tmp.Add("match", key);
				tmp.Add("count", this.dict[key].ToString());
				this.list[i++] = tmp;
			}
			this.sort();
			this.keywords = new System.String[i = this.list.Length];
			while (i-- > 0)
			{
				tmp = this.list[i];
				this.dict[this.keywords[i] = tmp["match"]] = i;
			}
			source = new System.Text.RegularExpressions.Regex(@"\w+").Replace(source, new System.Text.RegularExpressions.MatchEvaluator(this.replaceMatches));
			source = new System.Text.RegularExpressions.Regex("(\r\n|\n\r|\n|\r)+").Replace(source, "\\n");
			return source;
		}

		virtual protected System.String replaceMatches(System.Text.RegularExpressions.Match m)
		{
			return this.convertToBase(this.dict[m.Value]);
		}

		virtual protected void sort()
		{
			System.Boolean sorted = false;
			System.Collections.Generic.Dictionary<System.String, System.String> tmp;
			for (System.Int32 i = 1, j = this.list.Length; i < j; i++)
			{
				if (this.sortKeywords(this.list[i - 1], this.list[i]))
				{
					sorted = true;
					tmp = this.list[i];
					this.list[i] = this.list[i - 1];
					this.list[i - 1] = tmp;
					i = i > 2 ? i - 2 : 0;
				}
			}
			if (sorted)
				this.sort();
		}

		virtual protected System.Boolean sortKeywords(
			System.Collections.Generic.Dictionary<System.String, System.String> a,
			System.Collections.Generic.Dictionary<System.String, System.String> b
		)
		{
			System.Int32 ac = System.Convert.ToInt32(a["count"]), bc = System.Convert.ToInt32(b["count"]);
			return (ac < bc) || (ac == bc && a["match"].Length < b["match"].Length);
		}
		#endregion
	}

	class MyMinCSS
	{

		#region protected variables
		protected System.String output;
		#endregion

		#region constructor
		public MyMinCSS(System.String input)
		{
			this.output = this.init(input, true);
		}

		public MyMinCSS(System.String input, System.Boolean cc_on)
		{
			this.output = this.init(input, cc_on);
		}
		#endregion

		#region public override methods
		public override System.String ToString()
		{
			return this.output.ToString().TrimEnd();
		}

		public static implicit operator System.String(MyMinCSS o)
		{
			return o.ToString();
		}
		#endregion

		#region virtual protected methods
		virtual protected System.Boolean action(System.Char c)
		{
			System.Boolean r = true;
			switch (c)
			{
				case '}':
				case '{':
				case ';':
				case ',':
				case ':':
					r = false;
					break;
			}
			return r;
		}

		virtual protected System.String init(System.String input, System.Boolean cc_on)
		{
			System.Char c;
			System.Text.StringBuilder output = new System.Text.StringBuilder();
			input = System.Text.RegularExpressions.Regex.Replace(input.Trim(), "(\\s)+", MyMin.SPACE.ToString());
			for (System.Int32 i = 0, l = input.Length; i < l; i++)
			{
				c = input[i];
				switch (c)
				{
					case MyMin.SPACE:
						if (++i < l)
						{
							if (this.action(input[i]))
							{
								if (-1 < (i - 2))
								{
									if (this.action(input[i - 2]))
										output.Append(c);
								}
								else
									output.Append(c);
							};
							--i;
						};
						break;
					case '/':
						if (++i < l)
						{
							c = input[i];
							if (c == '*')
							{
								if (++i < l)
								{
									l = input.IndexOf("*/", i);
									if (-1 < l)
									{
										i = l + 2;
										l = input.Length;
										if (i < l)
										{
											--i;
											input = System.String.Format("{0};{1}", input.Substring(0, i), input.Substring(i + 1));
										}
									}
									else
										throw new MyMinException("Unterminated comment.");
								}
								else
									throw new MyMinException("Unterminated comment.");
							}
							else
								output.Append(input[--i]);
						};
						break;
					default:
						output.Append(c);
						break;
				}
			}
			return output.ToString().TrimEnd();
		}
		#endregion

	}

	class MyMinException : System.Exception
	{
		#region constructor
		public MyMinException(System.String message) : base(message) { }
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions