Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve css var's with their actual values? #339

Open
jmalatia opened this issue Feb 2, 2023 · 2 comments
Open

Resolve css var's with their actual values? #339

jmalatia opened this issue Feb 2, 2023 · 2 comments

Comments

@jmalatia
Copy link

jmalatia commented Feb 2, 2023

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.

@mdekrey
Copy link

mdekrey commented Dec 24, 2024

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

@whorchner
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants