Domanda

ho bisogno di CSS in linea da un foglio di stile in C #.

Come come funziona.

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

Il CSS è semplice, basta classi, non selettori di fantasia.

stavo contemplando utilizzando un (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+ regex per mettere a nudo le regole del CSS, e poi tentando di fare semplici sostituisce stringa in cui le classi sono chiamati, ma alcuni degli elementi HTML già un tag di stile, quindi avrei dovuto conto per quello.

C'è un approccio più semplice? O qualcosa di già scritto in C #?

UPDATE - 16 Settembre 2010

Sono stato in grado di venire con un semplice CSS inliner fornito il codice HTML è anche XML valido. Esso utilizza una regex per ottenere tutti gli stili nel vostro elemento <style />. Poi converte i selettori CSS per espressioni XPath, e aggiunge la linea stile agli elementi corrispondenti, prima di ogni stile in linea preesistente.

Si noti che il CssToXpath non è completamente implementato, ci sono alcune cose che proprio non può fare ... ancora.

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

e alcuni test

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

Ci sono altri test, ma, importano file HTML per l'uscita di ingresso e atteso ed io non sto postando tutto questo!

Ma devo inviare i metodi di estensione Normalizzazione!

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());
}
È stato utile?

Soluzione

Visto che siete già il 90% del modo in là con l'implementazione corrente, perché non si utilizza il quadro esistente, ma sostituisce il parsing XML con un parser HTML, invece? Uno dei più popolari là fuori è il HTML Agility pacchetto . Supporta query XPath e ha anche un LINQ un'interfaccia simile all'interfaccia .NET standard fornito per XML e quindi dovrebbe essere un sostituto abbastanza semplice.

Altri suggerimenti

Ho un progetto su Github che fa CSS in linea. E 'molto semplice, e sostenere stili di mobili. Per saperne di più sul mio blog: http://martinnormark.com/move-css-inline-premailer -net

Ottima domanda.

Non ho idea se c'è una soluzione .NET, ma ho trovato un programma di Ruby chiamato Premailer che pretende di CSS in linea. Se si desidera utilizzarlo avete un paio di opzioni:

  1. Rewrite Premailer in C # (o qualsiasi altro linguaggio .NET sei a conoscenza)
  2. IronRuby per eseguire Ruby in .NET

Mi consiglia di utilizzare un parser CSS reale, piuttosto che espressioni regolari. Non è necessario per analizzare il linguaggio completo dal momento che ti interessa per lo più nella riproduzione, ma in ogni caso tali analizzatori sono disponibili (e per NET troppo). Ad esempio, dare un'occhiata a antlr di lista delle grammatiche , in particolare un CSS 2.1 grammatica o un CSS3 grammatica. È forse possibile togliere gran parte di entrambe le grammatiche, se non ti dispiace risultati non ottimali in cui gli stili inline possono includere definizioni duplicati, ma per fare questo bene, avrete bisogno di alcuni idea della logica interna CSS essere in grado di risolvere gli attributi stenografia.

Nel lungo periodo, tuttavia, questo sarà certamente un molto meno lavoro di una serie infinita di correzioni ad hoc regex.

Dato che questa opzione non è molto chiaro nelle altre risposte, penso che merita una risposta diretta.

PreMailer.Net .

Tutto quello che dovete fare è:

  1. Installa PreMailer.NET tramite NuGet.
  2. Tipo questo:

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

e si è fatto!

A proposito, si consiglia di aggiungere una direttiva using per accorciare quella linea.

Maggiori informazioni utilizzo nel link qui sopra, naturalmente.

Ecco un'idea, perchè non si effettua una chiamata post per http: // www. mailchimp.com/labs/inlinecss.php utilizzando c #. dall'analisi utilizzando firebug sembra che la chiamata posta necessita 2 params html e striscia che assume valori (on / off) il risultato è in una chiamata di testo param.

Ecco un esempio su come fare un posta chiamata utilizzando C #

Chad, hai necessariamente aggiungere il CSS in linea? O forse si potrebbe essere meglio con l'aggiunta di un blocco <style> al tuo <head>? Questa volontà in sostanza sostituisce la necessità di un riferimento a un file CSS e più mantenere la regola che le regole in linea effettivi sovrascrivono quelle impostate nel file css di intestazione / riferimento.

(mi dispiace, ha dimenticato di aggiungere le virgolette per il codice)

mi sento di raccomandare un dictonary in questo modo:

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

vorrei analizzare il css per colmare questa cssDictionary.

(Aggiunta di 'style-type', 'stile-proprietà', 'valore' Nell'esempio:.

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

Dopo che avrei preferenza di convertire il codice HTML in un XmlDocument.

È possibile eseguire in modo ricorsivo attraverso i nodi documenti da esso è bambini e anche guardare in alto IT di genitori (Ciò permetterà anche di essere in grado di utilizzare selettori).

Su ogni elemento di controllare per il tipo di elemento, l'id e la classe. È quindi di consultare l'cssDictionary per aggiungere tutti gli stili per questo elemento per l'attributo di stile (Certo, si potrebbe desiderare di metterli in ordine cronologico se hanno sovrapposizione proprietà (e aggiungere la stili inline esistenti l'ultimo).

Quando hai finito, si emettono XmlDocument come una stringa e rimuovere la prima linea (<?xml version="1.0"?>) Questo dovrebbe lasciare con un documento HTML valido css inline.

Certo, potrebbe mezzo sembra un hack, ma alla fine penso che sia una soluzione piuttosto solido che la stabilità si accerta e molto fa quello che ti sembra di essere alla ricerca di.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top