代码之家  ›  专栏  ›  技术社区  ›  CaffGeek

在C中内联CSS#

  •  27
  • CaffGeek  · 技术社区  · 14 年前

    就像这样。

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

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

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

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

    更新-2010年9月16日

    如果您的html也是有效的xml,我就能够提供一个简单的CSS内联。它使用正则表达式来获取所有样式 <style /> 元素。然后将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*(?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})",
                                                    RegexOptions.IgnoreCase
                                                    | RegexOptions.CultureInvariant
                                                    | RegexOptions.IgnorePatternWhitespace
                                                    | RegexOptions.Compiled
                                                );
    
            public List<Match> 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<Match> GetStyleMatches()
            {
                var styles = new List<Match>();
    
                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<string, string>
                                   {
                                       { "*", "//*" }, 
                                       { "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(@"<html>
                                            <head>
                                                <title>Hello, World Page!</title>
                                                <style>
                                                    .redClass { 
                                                        background: red; 
                                                        color: purple; 
                                                    }
                                                </style>
                                            </head>
                                            <body>
                                                <div class=""redClass"">Hello, World!</div>
                                            </body>
                                        </html>").ToString();
            #endregion
    
            #region const string expected ...
            var expected = XElement.Parse(@"<html>
                                                <head>
                                                    <title>Hello, World Page!</title>
                                                </head>
                                                <body>
                                                    <div class=""redClass"" style=""background: red; color: purple;"">Hello, World!</div>
                                                </body>
                                            </html>").ToString();
            #endregion
    
            var result = new CssInliner.CssInliner(html);
    
            Assert.AreEqual(expected, result.InlinedXhtml);
        }
    

    还有更多的测试,但是,他们为输入和预期的输出导入html文件,我不张贴所有这些!

    但是我应该发布规范化扩展方法!

    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());
    }
    
    8 回复  |  直到 14 年前
        1
  •  9
  •   carla Sergey Berezovskiy    7 年前

    既然现在的实现已经完成了90%的工作,为什么不使用现有的框架,而用HTML解析器代替XML解析呢?其中比较流行的是 HTML Agility Pack . 它支持XPath查询,甚至有一个类似于为XML提供的标准.NET接口的LINQ接口,因此它应该是一个相当简单的替代品。

        2
  •  17
  •   MartinHN    13 年前

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

        3
  •  5
  •   Andrew    7 年前

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

    使用 PreMailer.Net

    你所要做的就是:

    1. 键入以下内容:

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

    你就完了!

    顺便说一句,你可能想添加一个 using 指示缩短那条线。

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

        4
  •  4
  •   Greg    14 年前

    好问题。

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

    1. 用C#(或您熟悉的任何.NET语言)重写预编译器
    2. 使用 IronRuby
        5
  •  3
  •   Eamon Nerbonne    14 年前

    list of grammars ,特别是 CSS 2.1 grammar 或者 CSS3 语法。如果您不介意次优结果(其中内联样式可能包含重复的定义),那么您可能会剥离这两个语法的大部分,但要做到这一点,您需要 一些 内部CSS逻辑的想法,能够解决速记属性。

    然而,从长远来看,这肯定是一个巨大的挑战 许多 比一系列临时正则表达式修复工作要少。

        6
  •  1
  •   Vinay B R    14 年前

    我有个主意,你为什么不给我打个电话 http://www.mailchimp.com/labs/inlinecss.php html格式 它获取值(开/关)结果是一个名为text的参数。

    这是一个关于如何制作 post call using c#

        7
  •  1
  •   wvdhouten    14 年前

    查德,你一定要添加CSS内联吗?或者你可以通过增加一个 <style> 阻止你的 <head> ? 这将在本质上取代对CSS文件引用的需要,并维护实际内联规则覆盖在头文件/引用CSS文件中设置的规则。

        8
  •  1
  •   wvdhouten    14 年前

    private Dictionary<string, Dictionary<string, string>> cssDictionary = new Dictionary<string, Dictionary<string, string>();
    

    我会解析css来填充这个css字典。

    Dictionary<string,string> bodyStyleDictionary = new Dictionary<string, string();
        bodyStyleDictionary.Add("background", "#000000");
        cssDictionary.Add("body", bodyStyleDictionary);
    

    在那之后,我最好将HTML转换成xml文档。

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

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

    当然,它可能有一半看起来像黑客,但最终我认为它是一个相当可靠的解决方案,确保稳定,并相当于你似乎在寻找。