在C#中内联CSS

我需要从c#中的样式表内联css。

就像这是如何工作的。

http://www.mailchimp.com/labs/inlinecss.php

css很简单,只是类,没有花哨的选择器。

我正在考虑使用正则表达式(?(?[^{}]+){(?[^{}]+)})+从css中删除规则,然后尝试在调用类的地方做简单的字符串替换,但是一些html元素已经有了样式标记,所以我也必须考虑到这一点。

有更简单的方法吗? 或者已经用c#写的东西?

更新 – 2010年9月16日

如果您的html也是有效的xml,我已经能够提出一个简单的CSS内联器。 它使用正则表达式来获取元素中的所有样式。 然后将css选择器转换为xpath表达式,并在任何预先存在的内联样式之前将样式内联添加到匹配元素。

注意,CssToXpath没有完全实现,有些东西它不能做……但是。

CssInliner.cs

 using System.Collections.Generic; using System.Text.RegularExpressions; using System.Xml.Linq; using System.Xml.XPath; namespace CssInliner { public class CssInliner { private static Regex _matchStyles = new Regex("\\s*(?(?[^{}]+){(?[^{}]+)})", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled ); public List Styles { get; private set; } public string InlinedXhtml { get; private set; } private XElement XhtmlDocument { get; set; } public CssInliner(string xhtml) { XhtmlDocument = ParseXhtml(xhtml); Styles = GetStyleMatches(); foreach (var style in Styles) { if (!style.Success) return; var cssSelector = style.Groups["selector"].Value.Trim(); var xpathSelector = CssToXpath.Transform(cssSelector); var cssStyle = style.Groups["style"].Value.Trim(); foreach (var element in XhtmlDocument.XPathSelectElements(xpathSelector)) { var inlineStyle = element.Attribute("style"); var newInlineStyle = cssStyle + ";"; if (inlineStyle != null && !string.IsNullOrEmpty(inlineStyle.Value)) { newInlineStyle += inlineStyle.Value; } element.SetAttributeValue("style", newInlineStyle.Trim().NormalizeCharacter(';').NormalizeSpace()); } } XhtmlDocument.Descendants("style").Remove(); InlinedXhtml = XhtmlDocument.ToString(); } private List GetStyleMatches() { var styles = new List(); var styleElements = XhtmlDocument.Descendants("style"); foreach (var styleElement in styleElements) { var matches = _matchStyles.Matches(styleElement.Value); foreach (Match match in matches) { styles.Add(match); } } return styles; } private static XElement ParseXhtml(string xhtml) { return XElement.Parse(xhtml); } } } 

CssToXpath.cs

 using System.Text.RegularExpressions; namespace CssInliner { public static class CssToXpath { public static string Transform(string css) { #region Translation Rules // References: http://ejohn.org/blog/xpath-css-selectors/ // http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js var regexReplaces = new[] { // add @ for attribs new RegexReplace { Regex = new Regex(@"\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]", RegexOptions.Multiline), Replace = @"[@$1$2]" }, // multiple queries new RegexReplace { Regex = new Regex(@"\s*,\s*", RegexOptions.Multiline), Replace = @"|" }, // , + ~ > new RegexReplace { Regex = new Regex(@"\s*(\+|~|>)\s*", RegexOptions.Multiline), Replace = @"$1" }, //* ~ + > new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*])~([a-zA-Z0-9_\-\*])", RegexOptions.Multiline), Replace = @"$1/following-sibling::$2" }, new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*])\+([a-zA-Z0-9_\-\*])", RegexOptions.Multiline), Replace = @"$1/following-sibling::*[1]/self::$2" }, new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*])>([a-zA-Z0-9_\-\*])", RegexOptions.Multiline), Replace = @"$1/$2" }, // all unescaped stuff escaped new RegexReplace { Regex = new Regex(@"\[([^=]+)=([^'|""][^\]]*)\]", RegexOptions.Multiline), Replace = @"[$1='$2']" }, // all descendant or self to // new RegexReplace { Regex = new Regex(@"(^|[^a-zA-Z0-9_\-\*])(#|\.)([a-zA-Z0-9_\-]+)", RegexOptions.Multiline), Replace = @"$1*$2$3" }, new RegexReplace { Regex = new Regex(@"([\>\+\|\~\,\s])([a-zA-Z\*]+)", RegexOptions.Multiline), Replace = @"$1//$2" }, new RegexReplace { Regex = new Regex(@"\s+\/\/", RegexOptions.Multiline), Replace = @"//" }, // :first-child new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*]+):first-child", RegexOptions.Multiline), Replace = @"*[1]/self::$1" }, // :last-child new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*]+):last-child", RegexOptions.Multiline), Replace = @"$1[not(following-sibling::*)]" }, // :only-child new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*]+):only-child", RegexOptions.Multiline), Replace = @"*[last()=1]/self::$1" }, // :empty new RegexReplace { Regex = new Regex(@"([a-zA-Z0-9_\-\*]+):empty", RegexOptions.Multiline), Replace = @"$1[not(*) and not(normalize-space())]" }, // |= attrib new RegexReplace { Regex = new Regex(@"\[([a-zA-Z0-9_\-]+)\|=([^\]]+)\]", RegexOptions.Multiline), Replace = @"[@$1=$2 or starts-with(@$1,concat($2,'-'))]" }, // *= attrib new RegexReplace { Regex = new Regex(@"\[([a-zA-Z0-9_\-]+)\*=([^\]]+)\]", RegexOptions.Multiline), Replace = @"[contains(@$1,$2)]" }, // ~= attrib new RegexReplace { Regex = new Regex(@"\[([a-zA-Z0-9_\-]+)~=([^\]]+)\]", RegexOptions.Multiline), Replace = @"[contains(concat(' ',normalize-space(@$1),' '),concat(' ',$2,' '))]" }, // ^= attrib new RegexReplace { Regex = new Regex(@"\[([a-zA-Z0-9_\-]+)\^=([^\]]+)\]", RegexOptions.Multiline), Replace = @"[starts-with(@$1,$2)]" }, // != attrib new RegexReplace { Regex = new Regex(@"\[([a-zA-Z0-9_\-]+)\!=([^\]]+)\]", RegexOptions.Multiline), Replace = @"[not(@$1) or @$1!=$2]" }, // ids new RegexReplace { Regex = new Regex(@"#([a-zA-Z0-9_\-]+)", RegexOptions.Multiline), Replace = @"[@id='$1']" }, // classes new RegexReplace { Regex = new Regex(@"\.([a-zA-Z0-9_\-]+)", RegexOptions.Multiline), Replace = @"[contains(concat(' ',normalize-space(@class),' '),' $1 ')]" }, // normalize multiple filters new RegexReplace { Regex = new Regex(@"\]\[([^\]]+)", RegexOptions.Multiline), Replace = @" and ($1)" }, }; #endregion foreach (var regexReplace in regexReplaces) { css = regexReplace.Regex.Replace(css, regexReplace.Replace); } return "//" + css; } } struct RegexReplace { public Regex Regex; public string Replace; } } 

还有一些测试

  [TestMethod] public void TestCssToXpathRules() { var translations = new Dictionary { { "*", "//*" }, { "p", "//p" }, { "p > *", "//p/*" }, { "#foo", "//*[@id='foo']" }, { "*[title]", "//*[@title]" }, { ".bar", "//*[contains(concat(' ',normalize-space(@class),' '),' bar ')]" }, { "div#test .note span:first-child", "//div[@id='test']//*[contains(concat(' ',normalize-space(@class),' '),' note ')]//*[1]/self::span" } }; foreach (var translation in translations) { var expected = translation.Value; var result = CssInliner.CssToXpath.Transform(translation.Key); Assert.AreEqual(expected, result); } } [TestMethod] public void HtmlWithMultiLineClassStyleReturnsInline() { #region var html = ... var html = XElement.Parse(@"  Hello, World Page!  .redClass { background: red; color: purple; }    
Hello, World!
").ToString(); #endregion #region const string expected ... var expected = XElement.Parse(@" Hello, World Page!
Hello, World!
").ToString(); #endregion var result = new CssInliner.CssInliner(html); Assert.AreEqual(expected, result.InlinedXhtml); }

有更多的测试,但是,他们导入输入和预期输出的html文件,我不会发布所有这些!

但我应该发布Normalize扩展方法!

 private static readonly Regex NormalizeSpaceRegex = new Regex(@"\s{2,}", RegexOptions.None); public static string NormalizeSpace(this string data) { return NormalizeSpaceRegex.Replace(data, @" "); } public static string NormalizeCharacter(this string data, char character) { var normalizeCharacterRegex = new Regex(character + "{2,}", RegexOptions.None); return normalizeCharacterRegex.Replace(data, character.ToString()); } 

由于您已经有90%的方式使用当前的实现,为什么不使用现有的框架,而是用HTML解析器替换XML解析? 其中一个比较受欢迎的是HTML Agility Pack 。 它支持XPath查询,甚至有一个类似于为XML提供的标准.NET接口的LINQ接口,因此它应该是一个相当简单的替代品。

我在Github上有一个使CSS内联的项目。 它非常简单,并支持移动样式。 在我的博客上阅读更多内容: http : //martinnormark.com/move-css-inline-premailer-net

好问题。

我不知道是否有.NET解决方案,但我发现了一个名为Premailer的Ruby程序声称内联CSS。 如果你想使用它,你有几个选择:

  1. 用C#(或您熟悉的任何.NET语言)重写Premailer
  2. 使用IronRuby在.NET中运行Ruby

我建议使用实际的CSS解析器而不是Regexes。 您不需要解析完整的语言,因为您主要对复制感兴趣,但无论如何这种解析器都可用(对于.NET也是如此)。 例如,看一下antlr的语法列表 ,特别是CSS 2.1语法或CSS3语法。 如果您不介意其中内联样式可能包含重复定义的次优结果,您可以剥离两个语法的大部分内容,但为了做到这一点,您需要了解内部CSS逻辑以便能够解析速记属性。

然而,从长远来看,这肯定会比一系列永久性的正则表达式修复工作少得多。

这是一个想法,为什么不使用c#拨打http://www.mailchimp.com/labs/inlinecss.php 。 从使用firebug的分析看来,post调用需要2个params htmlstrip ,它取值(开/关)结果在一个名为text的param中。

这里有一个关于如何使用c#进行发布呼叫的示例

乍得,你有必要添加CSS内联吗? 或者你可以通过在添加

块来改善自己? 这实际上将取代对CSS文件的引用的需要,并且保持实际内联规则覆盖在头/引用的css文件中设置的规则。

(对不起,忘了为代码添加引号)

我会推荐像这样的dictonary:

 private Dictionary> cssDictionary = new Dictionary(); 

我会解析css来填充这个cssDictionary。

(添加’style-type’,’style-property’,’value’。例如:

 Dictionary bodyStyleDictionary = new Dictionary 

之后,我最好将HTML转换为XmlDocument。

您可以通过它的子节点递归遍历文档节点,并查找它的父节点(这甚至可以使您能够使用选择器)。

在每个元素上检查元素类型,id和类。 然后浏览cssDictionary以将此元素的任何样式添加到样式属性(当然,如果它们具有重叠属性,您可能希望按出现顺序放置它们(并在最后添加现有内联样式)。

完成后,将xmlDocument作为字符串发出并删除第一行( )这将为您留下带有内联css的有效html文档。

当然,它可能看起来像一个黑客,但最后我认为这是一个非常可靠的解决方案,确保稳定性,并完全做你似乎正在寻找的东西。

由于这个选项在其他回复中不是很清楚,我认为这应该是一个直截了当的答案。

使用PreMailer.Net

你所要做的就是:

  1. 通过nuget安装PreMailer.NET。
  2. 输入:

     var inlineStyles = PreMailer.Net.PreMailer.MoveCssInline(htmlSource, false); destination = inlineStyles.Html; 

你完成了!

顺便说一句,你可能想添加一个using指令来缩短该行。

当然,在上面的链接中有更多的使用信息。