Frage

Ich muß Inline-CSS aus einem Stylesheet in c #.

Wie, wie das funktioniert.

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

Die CSS ist einfach, nur Klassen, keine Phantasie-Selektoren.

Ich betrachtete eine Regex (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+ mit den Regeln aus der CSS-Streifen, und dann versuchen, einfache Zeichenfolge ersetzt zu tun, wo die Klassen genannt werden, aber einige der HTML-Elemente, die bereits einen Stil-Tag haben, so dass ich haben würde dass Konto für so gut.

Gibt es einen einfacheren Ansatz? Oder etwas schon in c # geschrieben?

UPDATE - 16. September 2010

Ich habe in der Lage gewesen, mit einem einfachen CSS zu kommen Inliner Ihre HTML bereitgestellt ist auch gültig xml. Es verwendet einen regulären Ausdruck alle die Stile in Ihrem <style /> Elemente zu erhalten. Dann setzt die CSS-Selektoren zu XPath-Ausdrücke und fügt den Stil inline mit den entsprechenden Elementen, vor alle vorhandenen Inline-Stil.

Beachten Sie, dass die CssToXpath nicht vollständig umgesetzt ist, gibt es einige Dinge, die es einfach nicht tun ... noch.

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

Und einige 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);
    }

Es gibt mehr Tests, aber sie importieren HTML-Dateien für die Eingabe und erwartete Ausgabe und ich bin Entsendung alle nicht!

Aber ich sollte die Normalisieren Erweiterungsmethoden schreiben!

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());
}
War es hilfreich?

Lösung

Da Sie bereits 90% der Art und Weise ist es mit Ihrer aktuellen Implementierung, warum Sie nicht verwenden, um Ihre bestehenden Rahmen, sondern die XML-Analyse mit einem HTML-Parser statt ersetzen? Eine der populäreren da draußen ist der HTML Agility Pack-. Es unterstützt XPath-Abfragen und hat sogar eine LINQ-Schnittstelle ähnlich wie den Standard .NET-Schnittstelle für XML zur Verfügung gestellt, so dass es ein ziemlich einfacher Ersatz sein sollte.

Andere Tipps

ich ein Projekt auf Github, die CSS inline macht. Es ist sehr einfach, und mobile Designs unterstützen. Lesen Sie mehr auf meinem Blog: http://martinnormark.com/move-css-inline-premailer -Netz

Ausgezeichnete Frage.

Ich habe keine Ahnung, ob es eine .NET-Lösung ist, aber ich fand ein Programm Ruby-Namen Premailer , dass die Ansprüche an Inline-CSS. Wenn Sie es verwenden möchten, haben Sie ein paar Optionen:

  1. Rewrite Premailer in C # (oder eine .NET-Sprache, die Sie mit vertraut sind)
  2. Verwenden Sie IronRuby von Ruby in .NET

Ich würde empfehlen, einen tatsächlichen CSS-Parser verwenden, anstatt reguläre Ausdrücke. Sie haben nicht die volle Sprache analysieren müssen, da Sie meist in der Reproduktion interessiert sind, aber in jedem Fall solcher Parser verfügbar sind (und für .NET auch). Nehmen wir zum Beispiel einen Blick auf antlr Liste von Grammatiken , insbesondere eine CSS 2.1 Grammatik oder ein CSS3 Grammatik. Sie können möglicherweise große Teile beiden Grammatiken Streifen, wenn Sie nicht suboptimale Ergebnisse nichts dagegen, bei Inline-Styles doppelter Definitionen umfassen können, aber dies gut zu tun, müssen Sie einig Idee der internen CSS-Logik der Lage sein, zu lösen Stenografie Attribute.

Auf lange Sicht jedoch wird dies sicherlich sein, Los weniger Arbeit als eine nicht enden wollende Reihe von Ad-hoc-regex-Fixes.

Da diese Option nicht sehr klar in den anderen Antworten, ich denke, es eine einfache Antwort verdient.

Verwenden Sie PreMailer.Net .

Alles, was Sie tun müssen, ist:

  1. Installieren PreMailer.NET über nuget.
  2. Geben Sie diese:

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

Und Sie sind fertig!

BTW, sollten Sie eine using Richtlinie hinzuzufügen, diese Zeile zu verkürzen.

Weitere Informationen Verwendung in dem obigen Link, natürlich.

Hier ist eine Idee, warum Sie einen Beitrag Anruf dont http: // www. mailchimp.com/labs/inlinecss.php mit c #. aus der Analyse Firebug sieht es aus wie der Post Anruf 2 params muß html und Streifen , die Werte (on / off) das Ergebnis in einem param genannt Text nimmt.

Hier ist ein Beispiel, wie man einen Post Anruf mit c # machen

Tschad, müssen Sie unbedingt die CSS inline hinzufügen? Oder könnten Sie vielleicht besser dran, indem ein <style> Block zu Ihrem <head> hinzufügen? Dies wird im Wesentlichen die Notwendigkeit einer Bezugnahme auf eine CSS-Datei als auch ersetzen und die Regel halten, dass die tatsächlichen Inline-Regeln die, die in der Kopfzeile / verknüpften CSS-Datei überschreiben.

(sorry, vergessen, die Angebote für Code hinzufügen)

würde ich eine dictonary wie diese empfehlen:

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

Ich würde die CSS analysiert diese cssDictionary zu füllen.

(Hinzufügen von 'style-type', 'Stil-Eigenschaft', 'Wert' Im Beispiel:.

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

Danach würde ich vorzugsweise die HTML zu einem XmlDocument konvertieren.

Sie können durch die Dokumente Knoten durch Kinder rekursiv laufen und auch seine Eltern sehen (Dies würde auch ermöglichen, den Einsatz Selektoren zu können).

Auf jedem Element, das Sie für den Elementtyp zu überprüfen, die ID und die Klasse. Sie dann durch die cssDictionary sehen für dieses Element mit dem Attribut style alle Stile hinzufügen (Zugegeben, könnten Sie wollen, dass sie in der Reihenfolge ihres Auftretens zu bringen, wenn sie Eigenschaften überlappende haben (und fügen Sie die vorhandenen Inline-Styles die letzte).

Wenn Sie fertig sind, strahlen Sie die XmlDocument als String und entfernen Sie die erste Zeile (<?xml version="1.0"?>) Das Sie mit einem gültigen HTML-Dokument mit Inline-CSS verlassen sollte.

Sicher, es könnte die Hälfte aussieht wie ein Hack, aber am Ende denke ich, es ist eine ziemlich solide Lösung, dass die Stabilität sicherstellt und recht tut, was Sie scheinen zu suchen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top