Pregunta

Necesito CSS en línea a partir de una hoja de estilo en C #.

Al igual que el funcionamiento de este.

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

El CSS es simple, sólo las clases, no hay selectores de lujo.

yo estaba contemplando el uso de un (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+ expresiones regulares para despojar a las reglas de la CSS, y luego intentar hacer sustituye cadena sencillas en las que se denominan las clases, pero algunos de los elementos HTML ya tienen una etiqueta de estilo, por lo que tendría que cuenta para eso también.

¿Hay un método más sencillo? O algo ya escrito en C #?

ACTUALIZACIÓN - 16 Sep 2010

he sido capaz de llegar con un simple revestimiento interior CSS proporciona su html es también XML válido. Se utiliza una expresión regular para obtener todos los estilos en su elemento <style />. A continuación, convierte los selectores CSS para expresiones XPath, y añade el estilo en línea para los elementos coincidentes, antes de cualquier estilo en línea pre-existente.

Tenga en cuenta, que el CssToXpath no se aplique plenamente, hay algunas cosas que simplemente no puede hacer ... todavía.

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;
    }
}

y algunas pruebas

    [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);
    }

Hay más pruebas, pero, lo que importan los archivos HTML para la entrada y la salida esperada y no dejo todo eso!

Pero debería publicar los métodos de extensión Normalizar!

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());
}
¿Fue útil?

Solución

Dado que ya se encuentra el 90% de la manera allí con su implementación actual, ¿por qué no usar su marco existente, pero reemplaza el análisis de XML con un analizador de HTML en su lugar? Uno de los más populares por ahí es el HTML agilidad Paquete . Es compatible con las consultas XPath e incluso tiene un LINQ interfaz similar a la interfaz estándar de .NET previsto XML por lo que debe ser un reemplazo bastante sencillo.

Otros consejos

Tengo un proyecto en Github que hace en línea CSS. Es muy simple, y apoyar estilos móviles. Leer más en mi blog: http://martinnormark.com/move-css-inline-premailer -net

Excelente pregunta.

No tengo ni idea de si hay una solución .NET, pero he encontrado un programa llamado Rubí Premailer que pretende CSS en línea. Si desea utilizarlo usted tiene un par de opciones:

  1. reescritura Premailer en C # (o cualquier lenguaje .NET está familiarizado con)
  2. IronRuby para funcionar en Rubí .NET

Me gustaría recomendar el uso de un analizador CSS real en lugar de expresiones regulares. No es necesario para analizar el lenguaje completo, ya que nos interesa sobre todo en la reproducción, pero en cualquier caso tales analizadores está disponible (y para .NET también). Por ejemplo, echar un vistazo a antlr de lista de gramáticas , específicamente un CSS 2.1 gramática o una CSS3 gramática. Posiblemente puede despojar a grandes partes de ambas gramáticas, si no le importa resultados subóptimos en donde los estilos en línea pueden incluir definiciones duplicadas, pero para hacer esto así, tendrá que algunos idea de la lógica interna CSS para poder atributos determinación de la taquigrafía.

A la larga, sin embargo, este será sin duda un mucho menos trabajo que una serie de interminables correcciones adhoc de expresiones regulares.

A medida que esta opción no es muy claro en las otras respuestas, creo que se merece una respuesta directa.

PreMailer.Net .

Todo lo que tiene que hacer es:

  1. Instalar PreMailer.NET través Nuget.
  2. escriba lo siguiente:

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

Y ya está!

Por cierto, es posible que desee agregar una directiva using para acortar esa línea.

Más información de uso en el enlace de arriba, por supuesto.

Aquí es una idea, ¿por qué no hacer una llamada posterior a http: // www. mailchimp.com/labs/inlinecss.php usando C #. a partir del análisis usando firebug parece que la llamada de post necesita 2 params html y tira que toma valores (on / off), el resultado está en una llamada de texto PARAM.

aquí es una muestra de como hacer una llamada posterior usando C #

Chad, qué necesariamente tiene que añadir el CSS en línea? O podría quizás ser mejor mediante la adición de un bloque <style> a su <head>? Esto a su esencia sustituye la necesidad de una referencia a un archivo CSS, así además a mantener la regla de que las normas actuales en línea sobreescriben las establecidas en el archivo css encabezado / referencia.

(lo siento, se olvidó de agregar las cotizaciones de código)

Yo recomendaría una Dictonary como esto:

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

Me analizar el css para llenar este cssDictionary.

(Adición 'de tipo de estilo', 'estilo-propiedad', 'valor' En el ejemplo:.

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

Después de que iba a convertir el código HTML preferentemente a un XmlDocument.

Se puede ejecutar de forma recursiva a través de los nodos de documentos por ella de niños y también mirar hacia arriba son los padres (esto sería incluso permitir que seas capaz de selectores de uso).

En cada elemento de comprobar para el tipo de elemento, el ID y la clase. A continuación, navegar a través de la cssDictionary añadir cualquiera de los estilos de este elemento con el atributo de estilo (Por supuesto, es posible que desee colocarlos en orden de aparición si tienen propiedades superposición (Y agrega los estilos en línea existentes el último).

Cuando haya terminado, se emite la xmlDocument como una cadena y eliminar la primera línea (<?xml version="1.0"?>) Esto debe dejar con un documento HTML válido con CSS en línea.

Claro, podría parecer un medio corte, pero al final creo que es una solución muy sólida que la estabilidad asegura y bastante hace lo que parece estar buscando.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top