質問
C#のStyleSheetからCSSをインライン化する必要があります。
これがどのように機能するかのように。
http://www.mailchimp.com/labs/inlinecss.php
CSSはシンプルで、クラスだけで、派手なセレクターはありません。
私は正規表現を使って考えていました (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+
CSSからルールを削除してから、クラスが呼び出される場所で単純な文字列を置き換えようとすると、HTML要素の一部にはすでにスタイルタグがありますので、それも説明する必要があります。
より簡単なアプローチはありますか?または、すでにC#で書かれているもの?
更新 - 2010年9月16日
あなたのHTMLが有効なXMLでもある場合、私は簡単なCSSインレーナーを思いつくことができました。それは正規表現を使用してあなたのすべてのスタイルを取得します <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パーサーに置き換えてみませんか?そこにあるより人気のあるものの1つは HTMLアジリティパック. 。 XPathクエリをサポートし、XMLに提供される標準の.NETインターフェイスと同様のLINQインターフェイスを備えているため、かなり簡単な交換である必要があります。
他のヒント
CSSをインラインにするプロジェクトがGitHubにあります。非常にシンプルで、モバイルスタイルをサポートしています。私のブログでもっと読む: http://martinnormark.com/move-css-inline-premailer-net
正規表現ではなく、実際のCSSパーサーを使用することをお勧めします。主に複製に興味があるため、完全な言語を解析する必要はありませんが、いずれにしても(および.NETのために)そのようなパーサーも利用できます。たとえば、ANTLRをご覧ください 文法のリスト, 、具体的にはa CSS 2.1文法 またはa CSS3 文法。インラインスタイルが重複する定義が含まれる場合がある場合に最適な結果を気にしない場合、両方の文法の大部分を剥ぐことができますが、これをうまく行うには、必要です いくつかの 内部CSSロジックのアイデアは、速記の属性を解決できるようにします。
しかし、長期的には、これは確かに 多く 無効な一連のアドホック復geの修正よりも少ない作業。
このオプションは他の返信ではあまり明確ではないため、簡単な答えに値すると思います。
使用する Prainer.net.
あなたがしなければならないのは:
- nuget経由でpremailer.netをインストールします。
これを入力してください:
var inlineStyles = PreMailer.Net.PreMailer.MoveCssInline(htmlSource, false); destination = inlineStyles.Html;
そして、あなたは終わりました!
ところで、あなたはaを追加することをお勧めします using
そのラインを短縮する指令。
もちろん、上記のリンクのより多くの使用情報。
これがアイデアです、なぜあなたはポストコールをしないのですか http://www.mailchimp.com/labs/inlinecss.php C#を使用します。 FireBugを使用した分析から、ポストコールには2パラメーションが必要であるように見えます HTML と ストリップ 値を取る(オン/オフ)結果は、テキストと呼ばれるパラマリです。
以下は、aの作成方法に関するサンプルです 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をxmldocumentに変換できます。
子供たちがドキュメントノードを再帰的に実行することができます。また、両親を調べることもできます(これにより、セレクターを使用できるようになります)。
各要素で、要素タイプ、ID、クラスをチェックします。次に、cssdictionaryを参照して、この要素のスタイルをスタイル属性に追加します(付与されます、それらが重複するプロパティがある場合は、それらを発生する順に配置することができます(そして最後に既存のインラインスタイルを追加します)。
完了したら、Xmldocumentを文字列として放出し、最初の行を削除します(<?xml version="1.0"?>
)これにより、インラインCSSを備えた有効なHTMLドキュメントが残ります。
確かに、それは半分のハックのように見えるかもしれませんが、最終的には安定性を保証し、あなたが探していると思われるものをかなり行うかなり固形ソリューションだと思います。