Question

Je dois css en ligne à partir d'une feuille de style en C #.

Comme comment cela fonctionne.

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

Le CSS est simple, les classes seulement, pas de sélecteurs de fantaisie.

Je contemplais l'aide d'un (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+ regex pour dépouiller les règles du css, puis tenter de le faire remplace à cordes simples où les classes sont appelées, mais quelques-uns des éléments HTML ont déjà une balise de style, donc je dois compte pour cela aussi.

Y at-il une approche plus simple? Ou quelque chose déjà écrit en C #?

Mise à jour - 16 septembre 2010

J'ai été en mesure de venir avec un simple CSS html à condition que votre revêtement intérieur est également valide xml. Il utilise une expression régulière pour obtenir tous les styles dans votre élément <style />. convertit ensuite les sélecteurs CSS à des expressions XPath, et ajoute la ligne de style aux éléments correspondants, avant tout style en ligne pré-existante.

Notez que le CssToXpath est pas pleinement mis en œuvre, il y a des choses qu'il ne peut pas faire ... encore.

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

Et certains tests

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

Il y a plus de tests, mais, ils importer des fichiers html pour l'entrée et la sortie attendue et je ne suis pas tout ce que l'affichage!

Mais je poster les méthodes d'extension Normaliser!

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());
}
Était-ce utile?

La solution

Puisque vous êtes déjà 90% du chemin avec votre implémentation actuelle, pourquoi ne pas utiliser votre cadre existant, mais remplacer l'analyse XML avec un analyseur HTML à la place? L'un des plus populaires là-bas est le HTML Agilité pack . Il prend en charge les requêtes XPath et a même une interface LINQ similaire à l'interface .NET standard XML prévu il devrait donc être un remplacement assez simple.

Autres conseils

J'ai un projet sur Github qui fait en ligne CSS. Il est très simple, et le soutien des styles mobiles. En savoir plus sur mon blog: http://martinnormark.com/move-css-inline-premailer -net

Excellente question.

Je ne sais pas s'il y a une solution .NET, mais j'ai trouvé un programme Ruby appelé Premailer qui prétend CSS en ligne. Si vous voulez l'utiliser, vous avez deux options:

  1. Réécrire Premailer en C # (ou tout autre langage .NET vous connaissez)
  2. IronRuby pour exécuter Ruby dans .NET

Je vous recommande d'utiliser un analyseur CSS réelle plutôt que Regexes. Vous n'avez pas besoin d'analyser la langue complète puisque vous êtes intéressé principalement dans la reproduction, mais en tout cas ces parseurs sont disponibles (et pour .NET aussi). Par exemple, jetez un oeil chez antlr liste des grammaires, en particulier un grammaire CSS 2.1 ou CSS3 grammaire. Vous pouvez éventuellement enlever une grande partie des deux grammaires si vous ne me dérange pas de sous-optimaux dans laquelle les styles en ligne peuvent inclure des définitions en double, mais de le faire bien, vous aurez besoin certains idée de la logique interne CSS pour être en mesure de résoudre les attributs raccourcies.

À long terme, cependant, ce sera certainement un beaucoup moins de travail que d'une série de corrections neverending adhoc regex.

Comme cette option est très claire dans les autres réponses, je pense qu'il mérite une réponse simple.

PreMailer.Net .

Tout ce que vous avez à faire est:

  1. Installer PreMailer.NET via NuGet.
  2. Tapez ceci:

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

Et vous fait!

BTW, vous pouvez ajouter une directive using pour raccourcir cette ligne.

Plus d'infos sur l'utilisation dans le lien ci-dessus, bien sûr.

Voici une idée, pourquoi ne pas vous faire un appel de poste à http: // www. mailchimp.com/labs/inlinecss.php en utilisant c #. de l'analyse à l'aide Firebug, il ressemble à l'appel de poste a besoin de 2 params html et bande qui prend des valeurs (on / off), le résultat est dans un param texte appelé.

ici est un exemple sur la façon de faire un après appel à l'aide c #

Tchad, avez-vous nécessairement ajouter la ligne CSS? Ou pourriez-vous être peut-être mieux en ajoutant un bloc <style> à votre <head>? Cette volonté, en substance, remplacer la nécessité d'une référence à un fichier CSS, ainsi, plus maintenir la règle selon laquelle les règles inline réels remplacent ceux définis dans le fichier css tête / référencé.

(désolé, a oublié d'ajouter les guillemets pour le code)

Je recommande un dictonary comme ceci:

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

J'analyser le code CSS pour remplir cette cssDictionary.

(Ajout de 'style-type', 'style propriété', 'valeur' ??Dans l'exemple:.

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

Après que je convertir le code HTML de préférence à un XmlDocument.

Vous pouvez récursive courir à travers les documents noeuds par ses enfants et aussi regarder jusqu'à ce sont les parents (ce qui vous permettra même d'être en mesure de sélecteurs d'utilisation).

Sur chaque élément que vous vérifiez le type d'élément, l'identifiant et la classe. Vous parcourez ensuite à travers le cssDictionary d'ajouter des styles pour cet élément à l'attribut de style (D'accord, vous pouvez les placer dans l'ordre d'apparition si elles ont des propriétés qui se chevauchent (et ajoutez les styles en ligne existants le dernier).

Lorsque vous avez terminé, vous émettez la xmlDocument comme une chaîne et enlever la première ligne (<?xml version="1.0"?>) Cela devrait vous laisser un document HTML valide avec css en ligne.

Bien sûr, il pourrait ressembler à un demi-hack, mais à la fin, je pense que c'est une solution assez solide qui assure la stabilité et tout à fait ce que vous ne semblez être à la recherche.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top