سؤال

أحتاج إلى مضمّن CSS من ورقة الأنماط في C#.

مثل كيف يعمل هذا.

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

CSS بسيطة ، فصول فقط ، لا محددات خيالية.

كنت أفكر في استخدام regex (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+ لتجريد القواعد من CSS ، ثم محاولة القيام بسلسلة بسيطة تستبدل المكان الذي تسمى الفئات ، ولكن بعض عناصر HTML لديها بالفعل علامة نمط ، لذلك يجب أن أفسر ذلك أيضًا.

هل هناك نهج أبسط؟ أو شيء مكتوب بالفعل في C#؟

تحديث - 16 سبتمبر 2010

لقد تمكنت من التوصل إلى CSS Inliner بسيط بشرط أن يكون HTML الخاص بك هو XML صالح أيضًا. يستخدم regex للحصول على جميع الأنماط في <style /> عنصر. ثم يحول محددات CSS إلى تعبيرات XPath ، ويضيف النمط المضمّن إلى العناصر المطابقة ، قبل أي نمط مضمّن مسبقًا.

لاحظ أن csstoxpath لم يتم تنفيذها بالكامل ، فهناك بعض الأشياء التي لا يمكنها فعلها ... حتى الآن.

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

وبعض الاختبارات

    [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 مع محلل HTML بدلاً من ذلك؟ واحدة من أكثرها شعبية هناك HTML ATCILITY PACK. وهو يدعم استعلامات XPath وحتى لديه واجهة LINQ مماثلة لواجهة .NET القياسية المقدمة لـ XML بحيث يجب أن يكون بديلاً مباشرًا إلى حد ما.

نصائح أخرى

لدي مشروع على Github يجعل CSS مضمرا. إنها بسيطة للغاية ، ودعم أنماط الهاتف المحمول. اقرأ المزيد على مدونتي: http://martinnormark.com/move-css-inline-premailer-net

سؤال ممتاز.

ليس لدي أي فكرة عما إذا كان هناك حل .NET ، لكنني وجدت برنامج Ruby يسمى بريسللر التي تدعي إلى CSS المضمنة. إذا كنت تريد استخدامه ، فلديك خياران:

  1. أعد كتابة Premailer في C# (أو أي لغة .NET التي تعرفها)
  2. يستخدم Ironruby لتشغيل روبي في .NET

أوصي باستخدام محلل CSS الفعلي بدلاً من regexes. لا تحتاج إلى تحليل اللغة الكاملة لأنك مهتم في الغالب بالتكاثر ، ولكن على أي حال يتوفر مثل هؤلاء المحللين (وللحصول على .NET أيضًا). على سبيل المثال ، ألق نظرة على Antlr's قائمة القواعد, ، على وجه التحديد أ CSS 2.1 القواعد أو أ CSS3 قواعد. يمكنك تجريد أجزاء كبيرة من كلا القواعد إذا كنت لا تمانع بعض فكرة منطق CSS الداخلي لتكون قادرة على حل سمات الاختزال.

على المدى الطويل ، سيكون هذا بالتأكيد أ كثير عمل أقل من سلسلة Neverending من إصلاحات 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 لغة البرمجة و قطاع الذي يأخذ القيم (تشغيل/إيقاف) والنتيجة في param يسمى النص.

إليك عينة حول كيفية صنع ملف بعد المكالمة باستخدام 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 إلى XMLDOCUMITY.

يمكنك الركض بشكل متكرر من خلال العقد المستندات من قبل أطفالها وأيضًا البحث عن آبائهم (سيمكنك ذلك حتى القدرة على استخدام المختارين).

في كل عنصر تقوم بفحص نوع العنصر والمعرف والفئة. يمكنك بعد ذلك تصفح CSSDictionary لإضافة أي أنماط لهذا العنصر إلى سمة النمط (الممنوحة ، قد ترغب في وضعها حسب الحدوث إذا كان لديهم خصائص متداخلة (وأضف الأنماط المضمنة الحالية).

عند الانتهاء ، تنبعث من xmldocument كسلسلة وإزالة السطر الأول (<?xml version="1.0"?>) يجب أن يتركك هذا مع وثيقة HTML صالحة مع CSS المضمنة.

بالتأكيد ، قد يبدو نصفه مثل الاختراق ، لكن في النهاية أعتقد أنه حل قوي يضمن الاستقرار ويقوم بما يبدو أنك تبحث عنه.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top