Skip to content

Commit a34e7a7

Browse files
Fix #410: Preserve !important tag in inlined styles (#424)
* Fix #410: Preserve !important tag in inlined styles Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Fix #410: Minor code formatting in PreMailer.cs Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Address PR feedback: Fix code style issues Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Add test case for issue #410 to prove the need for additional logic Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Add dedicated test case for issue #410 Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Address PR feedback: Move test from Issue410Tests to PreMailerTests with descriptive name Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Address PR feedback: Move !important preservation logic to CssElementStyleResolver Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Simplify !important preservation logic in CssElementStyleResolver and add comprehensive tests Co-Authored-By: m@martinnormark.com <m@martinnormark.com> * Further simplify CssElementStyleResolver.GetAllStyles() by avoiding else if block Co-Authored-By: m@martinnormark.com <m@martinnormark.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: m@martinnormark.com <m@martinnormark.com>
1 parent ed6781a commit a34e7a7

6 files changed

Lines changed: 125 additions & 29 deletions

File tree

PreMailer.Net/PreMailer.Net.Tests/CssElementStyleResolverTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,39 @@ public void GetAllStylesForElement()
2222
Assert.Equal("style", result.ElementAt(0).AttributeName);
2323
Assert.Equal("bgcolor", result.ElementAt(1).AttributeName);
2424
}
25+
26+
[Fact]
27+
public void GetAllStyles_PreservesImportantFlagsInInlineStyles()
28+
{
29+
var document = new HtmlParser().ParseDocument("<div style=\"font-weight: bold !important;\"></div>");
30+
var element = document.Body.FirstElementChild;
31+
32+
var styleClass = new StyleClass();
33+
styleClass.Attributes["color"] = CssAttribute.FromRule("color: red");
34+
35+
var result = CssElementStyleResolver.GetAllStyles(element, styleClass);
36+
37+
var styleAttribute = result.FirstOrDefault(a => a.AttributeName == "style");
38+
Assert.NotNull(styleAttribute);
39+
Assert.Contains("font-weight: bold !important", styleAttribute.CssValue);
40+
Assert.Contains("color: red", styleAttribute.CssValue);
41+
}
42+
43+
[Fact]
44+
public void GetAllStyles_PreservesImportantFlagsWhenMergingStyles()
45+
{
46+
var document = new HtmlParser().ParseDocument("<div style=\"font-weight: bold !important;\"></div>");
47+
var element = document.Body.FirstElementChild;
48+
49+
var styleClass = new StyleClass();
50+
styleClass.Attributes["font-weight"] = CssAttribute.FromRule("font-weight: normal");
51+
52+
var result = CssElementStyleResolver.GetAllStyles(element, styleClass);
53+
54+
var styleAttribute = result.FirstOrDefault(a => a.AttributeName == "style");
55+
Assert.NotNull(styleAttribute);
56+
Assert.Contains("font-weight: bold !important", styleAttribute.CssValue);
57+
Assert.DoesNotContain("font-weight: normal", styleAttribute.CssValue);
58+
}
2559
}
2660
}

PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,23 @@ public void MoveCssInline_ImportantFlag_HonorsImportantFlagInline()
188188
Assert.Contains("<div style=\"color: red", premailedOutput.Html);
189189
}
190190

191+
[Fact]
192+
public void MoveCssInline_ShouldPreserveImportantFlagInInlineStyles_WhenApplyingClassStyles()
193+
{
194+
string input = @"<style>
195+
.test {
196+
color:red;
197+
}
198+
</style>
199+
<body>
200+
<p class=""test"" style=""font-weight: bold !important;"">test</p>
201+
</body>";
202+
203+
var premailedOutput = PreMailer.MoveCssInline(input);
204+
205+
Assert.Contains("font-weight: bold !important", premailedOutput.Html);
206+
}
207+
191208
[Fact]
192209
public void MoveCssInline_AbsoluteBackgroundUrl_ShouldNotBeCleanedAsComment()
193210
{

PreMailer.Net/PreMailer.Net.Tests/StyleClassApplierTests.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,24 @@ public void ApplyInlineStylesWithoutImportant()
5858

5959
var result = StyleClassApplier.ApplyAllStyles(elementDictionary);
6060

61-
Assert.Equal("<div style=\"color: #000\"></div>", result.ElementAt(0).Key.OuterHtml);
61+
Assert.Equal("<div style=\"color: #000 !important\"></div>", result.ElementAt(0).Key.OuterHtml);
62+
}
63+
64+
[Fact]
65+
public void ApplyInlineStylesWithImportant()
66+
{
67+
var document = new HtmlParser().ParseDocument("<div style=\"font-weight: bold !important;\"></div>");
68+
69+
var clazz = new StyleClass();
70+
clazz.Attributes["color"] = CssAttribute.FromRule("color: #000");
71+
72+
var elementDictionary = new Dictionary<IElement, StyleClass> {
73+
{document.Body.FirstElementChild, clazz}
74+
};
75+
76+
var result = StyleClassApplier.ApplyAllStyles(elementDictionary);
77+
78+
Assert.Equal("<div style=\"color: #000;font-weight: bold !important\"></div>", result.ElementAt(0).Key.OuterHtml);
6279
}
6380
}
6481
}

PreMailer.Net/PreMailer.Net/CssElementStyleResolver.cs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,47 @@ public static class CssElementStyleResolver
88
{
99
private const string premailerAttributePrefix = "-premailer-";
1010

11-
public static IEnumerable<AttributeToCss> GetAllStyles(IElement domElement, StyleClass styleClass)
11+
public static IEnumerable<AttributeToCss> GetAllStyles(IElement domElement, StyleClass styleClass)
12+
{
13+
var attributeCssList = new List<AttributeToCss>();
14+
var originalStyleAttr = domElement.Attributes["style"];
15+
16+
AddSpecialPremailerAttributes(attributeCssList, styleClass);
17+
18+
var mergedStyleClass = new StyleClass();
19+
20+
if (styleClass.Attributes.Count > 0)
1221
{
13-
var attributeCssList = new List<AttributeToCss>();
14-
15-
AddSpecialPremailerAttributes(attributeCssList, styleClass);
16-
17-
if (styleClass.Attributes.Count > 0)
18-
attributeCssList.Add(new AttributeToCss { AttributeName = "style", CssValue = styleClass.ToString() });
19-
20-
attributeCssList.AddRange(CssStyleEquivalence.FindEquivalent(domElement, styleClass));
21-
22-
return attributeCssList;
22+
mergedStyleClass.Merge(styleClass, true);
23+
}
24+
25+
if (originalStyleAttr != null)
26+
{
27+
var parser = new CssParser();
28+
var originalStyleClass = parser.ParseStyleClass("inline", originalStyleAttr.Value);
29+
30+
foreach (var attr in originalStyleClass.Attributes)
31+
{
32+
if (attr.Important)
33+
{
34+
mergedStyleClass.Attributes.Merge(attr);
35+
}
36+
else if (!mergedStyleClass.Attributes.TryGetValue(attr.Style, out var existing) || !existing.Important)
37+
{
38+
mergedStyleClass.Attributes.Merge(attr);
39+
}
40+
}
2341
}
42+
43+
if (mergedStyleClass.Attributes.Count > 0)
44+
{
45+
attributeCssList.Add(new AttributeToCss { AttributeName = "style", CssValue = mergedStyleClass.ToString(emitImportant: true) });
46+
}
47+
48+
attributeCssList.AddRange(CssStyleEquivalence.FindEquivalent(domElement, styleClass));
49+
50+
return attributeCssList;
51+
}
2452

2553
private static void AddSpecialPremailerAttributes(List<AttributeToCss> attributeCssList, StyleClass styleClass)
2654
{
@@ -44,4 +72,4 @@ private static void AddSpecialPremailerAttributes(List<AttributeToCss> attribute
4472
}
4573
}
4674
}
47-
}
75+
}

PreMailer.Net/PreMailer.Net/PreMailer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,4 +537,4 @@ public void Dispose()
537537
_document.Dispose();
538538
}
539539
}
540-
}
540+
}

PreMailer.Net/PreMailer.Net/StyleClassApplier.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ public static Dictionary<IElement, StyleClass> ApplyAllStyles(Dictionary<IElemen
1515
return elementDictionary;
1616
}
1717

18-
private static IElement ApplyStyles(IElement domElement, StyleClass clazz)
19-
{
20-
var styles = CssElementStyleResolver.GetAllStyles(domElement, clazz);
21-
22-
foreach (var attributeToCss in styles)
23-
{
24-
SetAttribute(domElement, attributeToCss);
25-
}
18+
private static IElement ApplyStyles(IElement domElement, StyleClass clazz)
19+
{
20+
var styles = CssElementStyleResolver.GetAllStyles(domElement, clazz);
2621

27-
var styleAttr = domElement.Attributes["style"];
28-
if (styleAttr == null || string.IsNullOrEmpty(styleAttr.Value))
29-
{
30-
domElement.RemoveAttribute("style");
31-
}
22+
foreach (var attributeToCss in styles)
23+
{
24+
SetAttribute(domElement, attributeToCss);
25+
}
3226

33-
return domElement;
27+
var styleAttr = domElement.Attributes["style"];
28+
if (styleAttr == null || string.IsNullOrEmpty(styleAttr.Value))
29+
{
30+
domElement.RemoveAttribute("style");
3431
}
3532

33+
return domElement;
34+
}
35+
3636
private static void SetAttribute(IElement domElement, AttributeToCss attributeToCss)
3737
{
3838
string name = attributeToCss.AttributeName;
@@ -56,4 +56,4 @@ private static void SetAttribute(IElement domElement, AttributeToCss attributeTo
5656
domElement.SetAttribute(name, value);
5757
}
5858
}
59-
}
59+
}

0 commit comments

Comments
 (0)