/// <summary> /// Take the set of properties that serializes to a shorter string. /// /// When (presumably equivalent) blocks of CSS are passed in, this method checks that certain attempts /// at minification actually resulted in shorter strings. /// /// This isn't always guaranteed when you're injecting default values. /// </summary> private static IEnumerable <NameValueProperty> TakeShorter(IEnumerable <NameValueProperty> first, IEnumerable <NameValueProperty> second) { Func <IEnumerable <NameValueProperty>, string> toString = x => { using (var writer = new StringWriter()) using (var css = new MinimalCssWriter(writer)) { x.Each(e => css.WriteRule(e, false)); return(writer.ToString()); } }; var map = new[] { Tuple.Create(first, toString(first)), Tuple.Create(second, toString(second)) }; return(map.OrderBy(o => o.Item2.Length).First().Item1); }
public static List <Block> Task(List <Block> blocks) { var ret = new List <Block>(); ret.AddRange(blocks.OfType <CssCharset>()); ret.AddRange(blocks.OfType <Model.Import>().Select(s => new Model.Import(MinifyValue(s.ToImport), ForQuery(s.MediaQuery), s.Start, s.Stop, s.FilePath))); ret.AddRange(blocks.Where(w => w is SelectorAndBlock && ((SelectorAndBlock)w).IsReset)); var remainder = blocks.Where(w => !ret.Contains(w)); foreach (var statement in remainder) { var block = statement as SelectorAndBlock; if (block != null) { var rules = new List <Property>(); foreach (var prop in block.Properties) { rules.Add(MinifyProperty(prop)); } rules = MinifyPropertyList(rules).ToList(); ret.Add(new SelectorAndBlock(block.Selector, rules, block.ResetContext, block.Start, block.Stop, block.FilePath)); continue; } var media = statement as MediaBlock; if (media != null) { var subStatements = Task(media.Blocks.ToList()); ret.Add(new MediaBlock(ForQuery(media.MediaQuery), subStatements, media.Start, media.Stop, media.FilePath)); continue; } var keyframes = statement as KeyFramesBlock; if (keyframes != null) { var frames = new List <KeyFrame>(); // minify each frame foreach (var frame in keyframes.Frames) { var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, frame.Properties, null, frame.Start, frame.Stop, frame.FilePath); var mind = Task(new List <Block>() { blockEquiv }); frames.Add(new KeyFrame(frame.Percentages.ToList(), ((SelectorAndBlock)mind[0]).Properties.ToList(), frame.Start, frame.Stop, frame.FilePath)); } // collapse frames if rules are identical var frameMap = frames.ToDictionary( d => { using (var str = new StringWriter()) using (var css = new MinimalCssWriter(str)) { foreach (var rule in d.Properties.Cast <NameValueProperty>()) { css.WriteRule(rule, lastRule: false); } return(str.ToString()); } }, d => d ); frames.Clear(); foreach (var frame in frameMap.GroupBy(k => k.Key)) { var allPercents = new List <decimal>(); foreach (var f in frame) { allPercents.AddRange(f.Value.Percentages); } var urFrame = frame.First().Value; frames.Add(new KeyFrame(allPercents, urFrame.Properties.ToList(), urFrame.Start, urFrame.Stop, urFrame.FilePath)); } ret.Add(new KeyFramesBlock(keyframes.Prefix, keyframes.Name, frames, keyframes.Variables.ToList(), keyframes.Start, keyframes.Stop, keyframes.FilePath)); continue; } ret.Add(statement); } return(ret); }
public static List<Block> Task(List<Block> blocks) { var ret = new List<Block>(); ret.AddRange(blocks.OfType<CssCharset>()); ret.AddRange(blocks.OfType<Model.Import>().Select(s => new Model.Import(MinifyValue(s.ToImport), ForQuery(s.MediaQuery), s.Start, s.Stop, s.FilePath))); ret.AddRange(blocks.Where(w => w is SelectorAndBlock && ((SelectorAndBlock)w).IsReset)); var remainder = blocks.Where(w => !ret.Contains(w)); foreach (var statement in remainder) { var block = statement as SelectorAndBlock; if (block != null) { var rules = new List<Property>(); foreach (var prop in block.Properties) { rules.Add(MinifyProperty(prop)); } rules = MinifyPropertyList(rules).ToList(); ret.Add(new SelectorAndBlock(block.Selector, rules, block.ResetContext, block.Start, block.Stop, block.FilePath)); continue; } var media = statement as MediaBlock; if (media != null) { var subStatements = Task(media.Blocks.ToList()); ret.Add(new MediaBlock(ForQuery(media.MediaQuery), subStatements, media.Start, media.Stop, media.FilePath)); continue; } var keyframes = statement as KeyFramesBlock; if (keyframes != null) { var frames = new List<KeyFrame>(); // minify each frame foreach (var frame in keyframes.Frames) { var blockEquiv = new SelectorAndBlock(InvalidSelector.Singleton, frame.Properties, null, frame.Start, frame.Stop, frame.FilePath); var mind = Task(new List<Block>() { blockEquiv }); frames.Add(new KeyFrame(frame.Percentages.ToList(), ((SelectorAndBlock)mind[0]).Properties.ToList(), frame.Start, frame.Stop, frame.FilePath)); } // collapse frames if rules are identical var frameMap = frames.ToDictionary( d => { using (var str = new StringWriter()) using (var css = new MinimalCssWriter(str)) { foreach (var rule in d.Properties.Cast<NameValueProperty>()) { css.WriteRule(rule, lastRule: false); } return str.ToString(); } }, d => d ); frames.Clear(); foreach (var frame in frameMap.GroupBy(k => k.Key)) { var allPercents = new List<decimal>(); foreach (var f in frame) { allPercents.AddRange(f.Value.Percentages); } var urFrame = frame.First().Value; frames.Add(new KeyFrame(allPercents, urFrame.Properties.ToList(), urFrame.Start, urFrame.Stop, urFrame.FilePath)); } ret.Add(new KeyFramesBlock(keyframes.Prefix, keyframes.Name, frames, keyframes.Variables.ToList(), keyframes.Start, keyframes.Stop, keyframes.FilePath)); continue; } ret.Add(statement); } return ret; }