Вопрос

Мне нужно встроить CSS из таблицы стилей в C #.

Как то, как это работает.

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

CSS прост, просто классы, без модных селекторов.

Я размышлял с помощью Regex (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+ Чтобы разделить правила из CSS, а затем попытки выполнять простую строку заменяет, где называются классы, но некоторые элементы HTML уже имеют тег стиля, поэтому я должен был бы также объяснить это.

Есть ли более простые подход? Или что-то уже написано в C #?

Обновление - 16 сен 2010 г.

Я смог придумать простые inliner CSS, если HTML также является действительным XML. Он использует Regex, чтобы получить все стили в вашем <style /> элемент. Затем преобразуют селекторы CSS в выражения XPath и добавляют стиль, встроенный в соответствующие элементы, прежде чем любой ранее существующий встроенный стиль.

Обратите внимание, что CSStoxPath не полностью реализован, есть некоторые вещи, которые он просто не может сделать ... пока.

Cssinliner.cs.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());
}
Это было полезно?

Решение

Поскольку вы уже 90% от дороги туда с вашей текущей реализацией, почему бы вам не использовать существующую структуру, но вместо этого замените анализатор XML с помощью Parser HTML? Один из самых популярных из них есть HTML Agility Pack. Отказ Он поддерживает запросы XPath и даже имеет интерфейс LINQ, аналогичный стандартному интерфейсу .NET, предоставленный для XML, поэтому он должен быть довольно простым заменой.

Другие советы

У меня есть проект на Github, который делает CSS Inline. Это очень просто и поддерживает мобильные стили. Читайте больше в моем блоге: http://martinnormark.com/move-css-inline-premailer-net.

Отличный вопрос.

Я понятия не имею, если есть решение .NET, но я нашел программу Ruby Предчувствие Это утверждает, что встроенные CSS. Если вы хотите использовать его, у вас есть пара вариантов:

  1. Переписать главную премьер в C # (или любой язык .NET язык, с которым вы знакомы)
  2. Использовать Ironruby Чтобы запустить Ruby в .NET

Я бы порекомендовал использовать фактический анализатор CSS, а не Regexes. Вам не нужно разбирать полный язык, поскольку вы заинтересованы в основном в размножении, но в любом случае такие анализаторы доступны (а также для .NET). Например, посмотрите на Antlr's Список грамматиков, конкретно а CSS 2.1 Грамматика или а CSS3 грамматика Вы можете разделить большие части обеих грамматиков, если вы не возражаете против оптимальных результатов, в которой встроенные стили могут включать дублирующиеся определения, но для этого нужны немного Идея внутренней логики CSS, чтобы иметь возможность разрешать сопоставление атрибутов.

В конечном итоге, однако, это, безусловно, будет много Меньше работы, чем неизоляция серии Adhoc Regex исправляет.

Поскольку этот вариант не очень понятен в других ответах, я думаю, что это заслуживает простого ответа.

Использовать Premailer.net..

Все, что вам нужно сделать, это:

  1. Установите Premailer.net через Nuget.
  2. Введите это:

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

И вы сделали!

Кстати, вы можете добавить using Директива по сокращению этой линии.

Больше информации об использовании в ссылке выше, конечно.

Вот идея, почему вы не делаете почтовый призыв к http://www.mailchimp.com/labs/inlinecss.php. Используя C #. от анализа используя Firebug, похоже на то, что пост требуется 2 параметрах HTML а также полоска который принимает значения (вкл / выкл) результат в параметрах называется текстом.

Вот образец о том, как сделать Почтовый звонок, используя C #

Чад, вы обязательно должны добавлять встроенные CSS? Или не могли бы вы быть лучше, добавив <style> Блок к вашему <head>? Это будет в сущности, заменит необходимость ссылки на файл CSS, а также поддерживать правило, что фактические встроенные правила переопределяют те, которые устанавливают в файл CSS-заголовка / ссылки.

(Извините, забыл добавить цитаты для кода)

Я бы порекомендовал, как это диктонары:

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

Я бы разобрал CSS, чтобы заполнить этот Cssdictionary.

(Добавление «типа стиля», «стиль-свойство», «значение». В примере:

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

После этого я предпочтительно преобразую HTML к XMLDocument.

Вы можете рекурсивно проходить через узлы документов ее детьми, а также посмотреть на родителей (даже позволит вам использовать селекторы).

На каждом элементе вы проверяете тип элемента, идентификатор и класс. Затем вы просматриваете CSSdictionary, чтобы добавить какие-либо стили для этого элемента в атрибут стиля (предоставленного, вы можете поместить их в порядке возникновения, если они имеют перекрывающиеся свойства (и добавьте существующие встроенные стили последнего).

Когда вы закончите, вы выделяете XMLDocument в виде строки и удалите первую строку (<?xml version="1.0"?>) Это должно оставить вас с действительным HTML-документом с помощью Inline CSS.

Конечно, это может наполовину выглядеть как взлом, но в конце я думаю, что это довольно твердое решение, которое обеспечивает стабильность и вполне делает то, что вы, кажется, искали.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top