using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.UI; public class StyleSheetHandler : IHttpHandler { protected Dictionary _cache; // Memory cache of processed stylesheets. public StyleSheetHandler() { _cache = new Dictionary(); } public bool IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { var rawFileName = context.Request.PhysicalPath; var rawFileLastModified = File.GetLastWriteTimeUtc(rawFileName); var browserName = context.Request.Browser.Browser; var browserVersion = new Version(context.Request.Browser.Version); var newCacheKey = new StylesheetCacheKey(rawFileName, rawFileLastModified, browserName, browserVersion); // Check for entry in cache of processed contents of stylesheets that is at least as recent as last // modified time of raw file. var cacheEntry = _cache.SingleOrDefault(entry => entry.Key.Equals(newCacheKey) && entry.Key.RawFileLastModified >= newCacheKey.RawFileLastModified); string processedContent; if (cacheEntry.Value != null) { // Found entry in cache. processedContent = cacheEntry.Value; } else { // Process raw content of stylesheet file. var rawContent = File.ReadAllText(rawFileName); processedContent = ProcessStylesheet(rawContent, browserName, browserVersion); // Add processed stylesheet to cache. _cache[newCacheKey] = processedContent; } // Write process content of stylesheet to response stream. context.Response.ContentType = "text/css"; context.Response.Output.Write(processedContent); context.Response.Flush(); } protected string ProcessStylesheet(string content, string browserName, Version browserVersion) { // Matches blocks (regions) of commented text (of form "/*{comment}*/"). var regexComments = new Regex(@"/\*.+?\*/", RegexOptions.Singleline); // Matches browser-conditional tags of form "[[?{browser condition} {true result} | {false result}]]". var regexTags = new Regex(@"\[\?(?.+?) +?(?.*?) ?\| ?(?.*?) *?\]", RegexOptions.None); // Evaluate tags within each comment block. var processedContent = regexComments.Replace(content, new MatchEvaluator(blockMatch => { var tagCount = 0; var evaluatedText = regexTags.Replace(blockMatch.Value, new MatchEvaluator(tagMatch => { tagCount++; // Invert condition if preceded by '!'. var browser = tagMatch.Groups["browser"].Value; bool inverted = false; if (browser[0] == '!') { inverted = true; browser = browser.Substring(1); } return (string.Equals(browser, browserName, StringComparison.InvariantCultureIgnoreCase) ^ inverted) ? tagMatch.Groups["trueResult"].Value : tagMatch.Groups["falseResult"].Value; })); return (tagCount > 0) ? evaluatedText.Substring(2, evaluatedText.Length - 4) : blockMatch.Value; })); return processedContent; } protected struct StylesheetCacheKey { public string StylesheetPath; // Path of raw stylesheet file. public DateTime RawFileLastModified; // Time at which raw file was last modified. public string BrowserName; // Name of browser. public Version BrowserVersion; // Version of browser. public StylesheetCacheKey(string stylesheetPath, DateTime rawFileLastModified, string browserName, Version browserVersion) { this.StylesheetPath = stylesheetPath; this.RawFileLastModified = rawFileLastModified; this.BrowserName = browserName; this.BrowserVersion = browserVersion; } public override bool Equals(object obj) { var objKey = (StylesheetCacheKey)obj; return this.StylesheetPath.Equals(objKey.StylesheetPath) && this.BrowserName.Equals(objKey.BrowserName) && this.BrowserVersion.Equals(objKey.BrowserVersion); } public override int GetHashCode() { return this.StylesheetPath.GetHashCode() ^ this.BrowserName.GetHashCode() ^ this.BrowserVersion.GetHashCode(); } public override string ToString() { return string.Format("{0}, {1} {2}", this.StylesheetPath, this.BrowserName, this.BrowserVersion.ToString()); } } }