public static string MoveCssInline(string htmlInput, bool removeStyleElements, Uri basePath) { using (var reader = new System.IO.StringReader(htmlInput)) { var web = new System.Net.WebClient(); var sgmlReader = new Pipes.Sgml.SgmlReader(); sgmlReader.DocType = "HTML"; sgmlReader.WhitespaceHandling = WhitespaceHandling.All; sgmlReader.CaseFolding = CaseFolding.ToLower; sgmlReader.InputStream = reader; sgmlReader.SimulatedNode = "html"; sgmlReader.StripDocType = false; var doc = new XmlDocument(); doc.Load(sgmlReader); var root = doc.DocumentElement; if (root.ChildNodes.OfType<XmlElement>().Count() == 1) root = root.ChildNodes.OfType<XmlElement>().Single(); var parser = new Parser(); var engine = new HtmlQueryEngine(); var visitor = new MatchVisitor(engine) { Comparison = StringComparison.OrdinalIgnoreCase }; var css = new StringBuilder(); var nodes = doc.SelectNodes("//style").OfType<System.Xml.XmlNode>().ToList(); foreach (var node in nodes) { css.AppendLine(node.InnerText); if (removeStyleElements) node.ParentNode.RemoveChild(node); } nodes = doc.SelectNodes("//link[@rel='stylesheet' and @href]").OfType<System.Xml.XmlNode>().ToList(); foreach (var node in nodes) { css.Append("@import url('").Append(node.Attributes["href"].Value).Append("')"); if (node.Attributes["media"] != null && !string.IsNullOrEmpty(node.Attributes["media"].Value)) { css.Append(" ").Append(node.Attributes["media"].Value); } css.AppendLine(";"); if (removeStyleElements) node.ParentNode.RemoveChild(node); } var stylesheet = parser.Parse(css.ToString()); var elements = root.SelectNodes("//*").OfType<XmlElement>().Select(e => new Pipes.Xml.XmlElementWrapper(e)).ToList(); var settings = new GlobalStyleContext(); settings.ResourceLoader = p => { var href = (basePath == null ? new Uri(p) : new Uri(basePath, p)); return DownloadStream(href); }; var rules = stylesheet.Rules.GetStyleRules(settings).ToList(); IEnumerable<StyleRule> elemRules; string inlineStyle; IEnumerable<Property> props; foreach (var elem in elements) { inlineStyle = elem.Attribute<string>("style", null); elemRules = rules; if (!string.IsNullOrEmpty(inlineStyle)) { elemRules = elemRules.Concat(Enumerable.Repeat(Utils.ParseInline(inlineStyle), 1)); } props = elemRules.ApplicableProperties(elem, engine, StringComparison.OrdinalIgnoreCase, settings); if (props.Any()) { inlineStyle = props.Select(p => p.ToString()).Aggregate((p, c) => p + ";" + c); elem.Attribute("style", inlineStyle); } } return doc.OuterXml; } }
public static IEnumerable<StyleRule> GetStyleRules(this IEnumerable<RuleSet> rules, GlobalStyleContext settings) { StyleRule style; MediaRule media; while (rules.Any()) { var loader = new ImportLoader(rules.OfType<ImportRule>() .Where(i => !i.Media.Any() || i.Media.Any(m => MediaMatches(m, settings))) .ToList(), settings.ResourceLoader); loader.Start(); foreach (var rule in rules) { style = rule as StyleRule; media = rule as MediaRule; if (style != null) { yield return style; } else if (media != null) { if (media.Media.Any(m => MediaMatches(m, settings))) { foreach (var s in media.RuleSets.GetStyleRules(settings)) { yield return s; } } } } loader.WaitAll(); if (loader.Sheets.Any()) { rules = loader.Sheets.SelectMany(s => s.Rules); } else { rules = Enumerable.Empty<RuleSet>(); } } }
private static bool MediaMatches(MediaDefinition d, GlobalStyleContext settings) { var context = new StyleContext(); var result = (d.Modifier != MediaTypeModifier.Not && (d.Type & settings.Media) > 0) || (d.Modifier == MediaTypeModifier.Not && (d.Type & settings.Media) == 0); if (result) { var width = d.Properties.Where(p => string.Compare(p.Name, "width", StringComparison.OrdinalIgnoreCase) == 0 || p.Name.EndsWith("-width", StringComparison.OrdinalIgnoreCase)); var plainProps = width.OfType<MediaPropertyPlain>(); double px; foreach (var plain in plainProps) { px = ToPx(plain.Value as PrimitiveTerm, context, false); if (plain.Name.StartsWith("max-", StringComparison.OrdinalIgnoreCase) && px > settings.MediaWidth) return false; if (plain.Name.StartsWith("min-", StringComparison.OrdinalIgnoreCase) && px < settings.MediaWidth) return false; } var rangeProps = width.OfType<MediaPropertyRange>(); foreach (var range in rangeProps) { if (range.LowerBound != null) { px = ToPx(range.LowerBound as PrimitiveTerm, context, false); if (settings.MediaWidth < px || (settings.MediaWidth == px && !range.LowerCompare.EndsWith("="))) return false; } if (range.UpperBound != null) { px = ToPx(range.UpperBound as PrimitiveTerm, context, false); if (settings.MediaWidth > px || (settings.MediaWidth == px && !range.UpperCompare.EndsWith("="))) return false; } } } return result; }
public static IEnumerable<Property> ApplicableProperties(this IEnumerable<StyleRule> rules, IQueryableNode node, IQueryEngine engine, StringComparison comparison, GlobalStyleContext settings) { var visitor = new MatchVisitor(engine); visitor.Comparison = comparison; var terms = new Dictionary<string, TermSpecificity>(); TermSpecificity existing; int specificity = 0; int propSpecificity; foreach (var style in rules) { if (style.Selector != null) { visitor.Initialize(node); style.Selector.Visit(visitor); } if (style.Selector == null || visitor.IsMatch()) { specificity = style.Selector == null ? 0 : (visitor.MatchSpecificity > 0 ? visitor.MatchSpecificity : style.Selector.GetSpecificity()); foreach (var prop in style.Declarations.SelectMany(p => p.Expand(settings.LeftToRight))) { propSpecificity = specificity + (prop.Important ? (1 << 20) : 0); if (terms.TryGetValue(prop.Name, out existing)) { if (propSpecificity >= existing.Specificity) { existing.Property = prop; } } else { terms[prop.Name] = new TermSpecificity() { Property = prop, Specificity = propSpecificity }; } } } } return terms.Values.Select(v => v.Property); }