write - CSS en línea en C#
htmltextwriter using (8)
Aquí hay una idea, ¿por qué no hace una llamada a http://www.mailchimp.com/labs/inlinecss.php usando c #? del análisis con firebug parece que la llamada posterior necesita 2 parámetros html y tira que toma valores (activado / desactivado), el resultado está en un parámetro llamado texto.
Aquí hay una muestra de cómo hacer una llamada posterior usando c #
Necesito insertar CSS desde una hoja de estilo en c #.
Como cómo funciona esto.
http://www.mailchimp.com/labs/inlinecss.php
El css es simple, solo clases, no selectores de fantasía.
Estaba contemplando usar una expresión regular (?<rule>(?<selector>[^{}]+){(?<style>[^{}]+)})+
para quitar las reglas del css, y luego intentar para hacer cadenas simples reemplaza a donde se llaman las clases, pero algunos de los elementos html ya tienen una etiqueta de estilo, así que también debo tener en cuenta eso.
¿Hay un enfoque más simple? ¿O algo ya escrito en c #?
ACTUALIZACIÓN - 16 de septiembre de 2010
He podido crear un simple inliner CSS siempre que su html también sea válido xml. Utiliza una expresión regular para obtener todos los estilos en su elemento <style />
. Luego convierte los selectores css en expresiones xpath y agrega el estilo en línea a los elementos coincidentes, antes de cualquier estilo en línea preexistente.
Tenga en cuenta que el CssToXpath no está completamente implementado, hay algunas cosas que simplemente no puede hacer ... todavía.
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;
}
}
Y algunas pruebas
[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);
}
Hay más pruebas, pero importan archivos html para la entrada y la salida esperada y no estoy publicando todo eso.
¡Pero debería publicar los métodos de extensión Normalizar!
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());
}
Chad, ¿necesariamente tienes que agregar el CSS en línea? ¿O tal vez podría estar mejor si agrega un bloque <style>
a su <head>
? En esencia, esto reemplazará la necesidad de una referencia a un archivo CSS y además mantendrá la regla de que las reglas en línea reales anulan las establecidas en el encabezado / archivo css referenciado.
(Lo siento, se olvidó de agregar las citas para el código)
Como esta opción no está muy clara en las otras respuestas, creo que merece una respuesta directa.
Utilice github.com/milkshakesoftware/PreMailer.Net .
Todo lo que tienes que hacer es:
- Instalar PreMailer.NET a través de nuget.
Escribe esto:
var inlineStyles = PreMailer.Net.PreMailer.MoveCssInline(htmlSource, false); destination = inlineStyles.Html;
¡Y ya está!
Por cierto, es posible que desee agregar una directiva using
para acortar esa línea.
Más información de uso en el enlace de arriba, por supuesto.
Excelente pregunta.
No tengo idea de si hay una solución .NET, pero encontré un programa Ruby llamado Premailer que afirma estar en línea con CSS. Si quieres usarlo tienes un par de opciones:
- Reescriba Premailer en C # (o en cualquier lenguaje .NET con el que esté familiarizado)
- Usa IronRuby para ejecutar Ruby en .NET
Recomiendo usar un analizador de CSS real en lugar de Regexes. No es necesario analizar el lenguaje completo, ya que está interesado principalmente en la reproducción, pero en cualquier caso, dichos analizadores están disponibles (y también para .NET). Por ejemplo, eche un vistazo a la lista de gramáticas de antlr, específicamente una gramática CSS 2.1 o una gramática CSS3 . Posiblemente puede eliminar grandes partes de ambas gramáticas si no le importan los resultados subóptimos en los que los estilos en línea pueden incluir definiciones duplicadas, pero para hacer esto bien, necesitará una idea de la lógica interna de CSS para poder resolver los atributos de taquigrafía. .
Sin embargo, a largo plazo, esto supondrá mucho menos trabajo que una serie interminable de correcciones de expresiones regulares adhoc.
Tengo un proyecto en Github que hace CSS en línea. Es muy simple, y soporta estilos móviles. Lea más en mi blog: martinnormark.com/move-css-inline-premailer-net
Ya que ya está en el 90% de su implementación actual, ¿por qué no usa su marco existente pero reemplaza el análisis XML con un analizador HTML en su lugar? Uno de los más populares es el paquete de agilidad HTML . Admite consultas XPath e incluso tiene una interfaz LINQ similar a la interfaz estándar .NET provista para XML, por lo que debería ser un reemplazo bastante sencillo.
Yo recomendaría un diccionario como este:
private Dictionary<string, Dictionary<string, string>> cssDictionary = new Dictionary<string, Dictionary<string, string>();
Me gustaría analizar el css para llenar este cssDictionary.
(Añadiendo ''tipo de estilo'', ''propiedad de estilo'', ''valor''. En el ejemplo:
Dictionary<string,string> bodyStyleDictionary = new Dictionary<string, string();
bodyStyleDictionary.Add("background", "#000000");
cssDictionary.Add("body", bodyStyleDictionary);
Después de eso preferiría convertir el HTML a un XmlDocument.
Puede ejecutar recursivamente los nodos de documentos por parte de sus hijos y también buscar a sus padres (esto incluso le permitiría poder usar selectores).
En cada elemento se comprueba el tipo de elemento, el id y la clase. Luego navega a través del cssDictionary para agregar cualquier estilo para este elemento al atributo de estilo (Concedido, es posible que desee colocarlos en orden de ocurrencia si tienen propiedades superpuestas (y agregar los estilos en línea existentes la última).
Cuando haya terminado, emita xmlDocument como una cadena y elimine la primera línea ( <?xml version="1.0"?>
) Esto debería dejarlo con un documento html válido con css en línea.
Claro, puede que parezca un hack, pero al final creo que es una solución bastante sólida que garantiza la estabilidad y hace todo lo que parece que estás buscando.