PreMailer.Net
PreMailer.Net copied to clipboard
Resolve css var's with their actual values?
Is there a way to resolve css variables into their actual values?
css
:root {
--my-css-color-var: blue;
}
.my-class {
color: var(--my-css-color-var);
}
html
<html>
<body>
<div class="my-class">Hello</div>
</body>
</html>
html > after inlining with PreMailer.net
<html style="--my-css-color-var: blue;">
<body>
<div style="color: var(--my-css-color-var)">Hello</div>
</body>
</html>
Which doesn't work with web mail (at least Gmail because they strip html attributes).
Also current Bootstrap versions are now predominantly using css variables which only adds to this issue and a need to resolve the variables into their actual values.
Any guidance would be appreciated.
I was able to inline the CSS variables with the following code - it's not perfect (expect issues if you mix var() with other CSS functions), so please let me know if you make improvements, but otherwise you can use the following under the MIT license. All the assemblies you need should be included with the latest PreMailer.NET release.
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using AngleSharp.Html;
using PreMailer.Net;
using System.Collections.Immutable;
using System.Text.RegularExpressions;
using AngleSharp.Html.Dom;
partial class EmailGenerationService
{
private readonly HtmlParser _htmlParser = new ();
private readonly CssParser _cssParser = new ();
public async Task<Stream> ToEmailReadyHtml(string htmlContent)
{
var result = PreMailer.Net.PreMailer.MoveCssInline(htmlContent, removeStyleElements: true, stripIdAndClassAttributes: true, removeComments: true);
var ms = new MemoryStream();
var streamWriter = new StreamWriter(ms);
InlineCssVariables(result.Html).ToHtml(streamWriter, HtmlMarkupFormatter.Instance);
await streamWriter.FlushAsync();
ms.Position = 0;
return ms;
}
private IHtmlDocument InlineCssVariables(string html)
{
var doc = _htmlParser.ParseDocument(html);
InlineCssVariables(doc.DocumentElement, ImmutableDictionary<string, string?>.Empty);
return doc;
}
private void InlineCssVariables(IElement documentElement, ImmutableDictionary<string, string?> cssVariables)
{
if (documentElement.GetAttribute("style") is string styles)
{
var styleProps = _cssParser.ParseStyleClass("inline", styles);
cssVariables = cssVariables.AddRange(
from cssStyle in styleProps.Attributes
where cssStyle.Style.StartsWith("--")
select new KeyValuePair<string, string?>(cssStyle.Style, Evaluate(cssStyle.Value))
);
foreach (var cssStyle in styleProps.Attributes.ToArray())
{
if (cssStyle.Style.StartsWith("--"))
{
styleProps.Attributes.Remove(cssStyle.Style);
continue;
}
var value = Evaluate(cssStyle.Value);
if (value == null)
styleProps.Attributes.Remove(cssStyle.Style);
else if (cssStyle.Value != value)
cssStyle.Value = value;
}
documentElement.SetAttribute("style", styleProps.ToString());
}
foreach (var elem in documentElement.Children)
InlineCssVariables(elem, cssVariables);
string? Evaluate(string value)
{
var match = CssVariableFunction().Match(value);
if (!match.Success)
return value;
return cssVariables.TryGetValue(match.Groups["varName"].Value, out var variableValue)
? variableValue
: match.Groups.TryGetValue("defaultValue", out var group) ? group.Value : null;
}
}
[GeneratedRegex("""var\((?<varName>--[^ ,]+)(, (?<defaultValue>.*))?\)""")]
private static partial Regex CssVariableFunction();
}
Successfuly implemented the solution in our PreMailer wrapper class. Since we only use simple variables (not the more complex functions) for email layouts, I don't expect to run into issues (yet). Thanks for the code.