Skip to content

Commit 549740e

Browse files
Fix binary compatibility issue with MoveCssInline method signatures (#435)
* Add backward-compatible method overloads for MoveCssInline to fix binary compatibility issue Co-authored-by: martinnormark <67565+martinnormark@users.noreply.github.com> * Revert target framework change * Clean up MoveCssInline XML-doc and tighten backward-compat tests - Fix duplicated/malformed <param name="baseUri"> XML-doc comments on the Uri-based MoveCssInline overloads (pre-existing typo now corrected across all four). - Extend the backward-compatibility tests to also assert that the 7/8-param overloads actually apply useEmailFormatter=false, by probing the &copy; entity decoding that differentiates the two formatter code paths. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: martinnormark <67565+martinnormark@users.noreply.github.com> Co-authored-by: Martin Høst Normark <m@martinnormark.com> Co-authored-by: Martin Høst Normark <martin.hoest.normark@lego.com>
1 parent be8edad commit 549740e

2 files changed

Lines changed: 168 additions & 4 deletions

File tree

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,5 +710,85 @@ public void MoveCssInline_EmptyTagsArePreserved()
710710
Assert.DoesNotContain("<u />", premailedOutput.Html);
711711
Assert.DoesNotContain("<u/>", premailedOutput.Html);
712712
}
713+
714+
[Fact]
715+
public void MoveCssInline_BackwardCompatibility_StaticMethod_WithoutUseEmailFormatter()
716+
{
717+
// Test that the old method signature (without useEmailFormatter) still works
718+
string input = "<html><head><style type=\"text/css\">.test { height: 100px; }</style></head><body><div class=\"test\" style=\"width: 100px;\">&copy;</div></body></html>";
719+
720+
// This should call the backward-compatible overload and default useEmailFormatter to false
721+
var premailedOutput = PreMailer.MoveCssInline(input, false, null, null, false, false, null, false);
722+
723+
Assert.Contains("<div class=\"test\" style=\"height: 100px;width: 100px", premailedOutput.Html);
724+
// useEmailFormatter=false => &copy; is decoded to ©
725+
Assert.Contains("©", premailedOutput.Html);
726+
Assert.DoesNotContain("&copy;", premailedOutput.Html);
727+
}
728+
729+
[Fact]
730+
public void MoveCssInline_BackwardCompatibility_InstanceMethod_WithoutUseEmailFormatter()
731+
{
732+
// Test that the old instance method signature (without useEmailFormatter) still works
733+
string input = "<html><head><style type=\"text/css\">.test { height: 100px; }</style></head><body><div class=\"test\" style=\"width: 100px;\">&copy;</div></body></html>";
734+
735+
var premailer = new PreMailer(input);
736+
// This should call the backward-compatible instance method overload and default useEmailFormatter to false
737+
var premailedOutput = premailer.MoveCssInline(false, null, null, false, false, null, false);
738+
739+
Assert.Contains("<div class=\"test\" style=\"height: 100px;width: 100px", premailedOutput.Html);
740+
Assert.Contains("©", premailedOutput.Html);
741+
Assert.DoesNotContain("&copy;", premailedOutput.Html);
742+
}
743+
744+
[Fact]
745+
public void MoveCssInline_BackwardCompatibility_StreamMethod_WithoutUseEmailFormatter()
746+
{
747+
// Test that the old Stream method signature (without useEmailFormatter) still works
748+
string input = "<html><head><style type=\"text/css\">.test { height: 100px; }</style></head><body><div class=\"test\" style=\"width: 100px;\">&copy;</div></body></html>";
749+
750+
using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(input)))
751+
{
752+
// This should call the backward-compatible Stream overload and default useEmailFormatter to false
753+
var premailedOutput = PreMailer.MoveCssInline(stream, false, null, null, false, false, null, false);
754+
755+
Assert.Contains("<div class=\"test\" style=\"height: 100px;width: 100px", premailedOutput.Html);
756+
Assert.Contains("©", premailedOutput.Html);
757+
Assert.DoesNotContain("&copy;", premailedOutput.Html);
758+
}
759+
}
760+
761+
[Fact]
762+
public void MoveCssInline_BackwardCompatibility_UriMethod_WithoutUseEmailFormatter()
763+
{
764+
// Test that the old Uri + string method signature (without useEmailFormatter) still works
765+
string input = "<html><head><style type=\"text/css\">.test { height: 100px; }</style></head><body><div class=\"test\" style=\"width: 100px;\">&copy;</div></body></html>";
766+
var baseUri = new Uri("http://example.com/");
767+
768+
// This should call the backward-compatible Uri overload and default useEmailFormatter to false
769+
var premailedOutput = PreMailer.MoveCssInline(baseUri, input, false, null, null, false, false, null, false);
770+
771+
Assert.Contains("<div class=\"test\" style=\"height: 100px;width: 100px", premailedOutput.Html);
772+
Assert.Contains("©", premailedOutput.Html);
773+
Assert.DoesNotContain("&copy;", premailedOutput.Html);
774+
}
775+
776+
[Fact]
777+
public void MoveCssInline_BackwardCompatibility_UriStreamMethod_WithoutUseEmailFormatter()
778+
{
779+
// Test that the old Uri + stream method signature (without useEmailFormatter) still works
780+
string input = "<html><head><style type=\"text/css\">.test { height: 100px; }</style></head><body><div class=\"test\" style=\"width: 100px;\">&copy;</div></body></html>";
781+
var baseUri = new Uri("http://example.com/");
782+
783+
using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(input)))
784+
{
785+
// This should call the backward-compatible Uri + Stream overload and default useEmailFormatter to false
786+
var premailedOutput = PreMailer.MoveCssInline(baseUri, stream, false, null, null, false, false, null, false);
787+
788+
Assert.Contains("<div class=\"test\" style=\"height: 100px;width: 100px", premailedOutput.Html);
789+
Assert.Contains("©", premailedOutput.Html);
790+
Assert.DoesNotContain("&copy;", premailedOutput.Html);
791+
}
792+
}
713793
}
714794
}

PreMailer.Net/PreMailer.Net/PreMailer.cs

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ public PreMailer(Stream stream, Uri baseUri = null)
6868
_cssSelectorParser = new CssSelectorParser();
6969
}
7070

71+
/// <summary>
72+
/// In-lines the CSS within the HTML given.
73+
/// </summary>
74+
/// <param name="html">The HTML input.</param>
75+
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
76+
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
77+
/// <param name="css">A string containing a style-sheet for inlining.</param>
78+
/// <param name="stripIdAndClassAttributes">True to strip ID and class attributes</param>
79+
/// <param name="removeComments">True to remove comments, false to leave them intact</param>
80+
/// <param name="customFormatter">Custom formatter to use</param>
81+
/// <param name="preserveMediaQueries">If set to true and removeStyleElements is true, it will instead preserve unsupported media queries in the style node and remove the other css, instead of removing the whole style node</param>
82+
/// <returns>Returns the html input, with styles moved to inline attributes.</returns>
83+
public static InlineResult MoveCssInline(string html, bool removeStyleElements, string ignoreElements, string css, bool stripIdAndClassAttributes, bool removeComments, IMarkupFormatter customFormatter, bool preserveMediaQueries)
84+
{
85+
return new PreMailer(html).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, false);
86+
}
87+
7188
/// <summary>
7289
/// In-lines the CSS within the HTML given.
7390
/// </summary>
@@ -86,6 +103,23 @@ public static InlineResult MoveCssInline(string html, bool removeStyleElements =
86103
return new PreMailer(html).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, useEmailFormatter);
87104
}
88105

106+
/// <summary>
107+
/// In-lines the CSS within the HTML given.
108+
/// </summary>
109+
/// <param name="stream">The Stream input.</param>
110+
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
111+
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
112+
/// <param name="css">A string containing a style-sheet for inlining.</param>
113+
/// <param name="stripIdAndClassAttributes">True to strip ID and class attributes</param>
114+
/// <param name="removeComments">True to remove comments, false to leave them intact</param>
115+
/// <param name="customFormatter">Custom formatter to use</param>
116+
/// <param name="preserveMediaQueries">If set to true and removeStyleElements is true, it will instead preserve unsupported media queries in the style node and remove the other css, instead of removing the whole style node</param>
117+
/// <returns>Returns the html input, with styles moved to inline attributes.</returns>
118+
public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements, string ignoreElements, string css, bool stripIdAndClassAttributes, bool removeComments, IMarkupFormatter customFormatter, bool preserveMediaQueries)
119+
{
120+
return new PreMailer(stream).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, false);
121+
}
122+
89123
/// <summary>
90124
/// In-lines the CSS within the HTML given.
91125
/// </summary>
@@ -107,8 +141,25 @@ public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements
107141
/// <summary>
108142
/// In-lines the CSS within the HTML given.
109143
/// </summary>
110-
/// /// <param name="baseUri">The base url that will be used to resolve any relative urls</param>
111-
/// <param name="baseUri">The Url that all relative urls will be off of.</param>
144+
/// <param name="baseUri">The base url that will be used to resolve any relative urls.</param>
145+
/// <param name="html">The HTML input.</param>
146+
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
147+
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
148+
/// <param name="css">A string containing a style-sheet for inlining.</param>
149+
/// <param name="stripIdAndClassAttributes">True to strip ID and class attributes</param>
150+
/// <param name="removeComments">True to remove comments, false to leave them intact</param>
151+
/// <param name="customFormatter">Custom formatter to use</param>
152+
/// <param name="preserveMediaQueries">If set to true and removeStyleElements is true, it will instead preserve unsupported media queries in the style node and remove the other css, instead of removing the whole style node</param>
153+
/// <returns>Returns the html input, with styles moved to inline attributes.</returns>
154+
public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeStyleElements, string ignoreElements, string css, bool stripIdAndClassAttributes, bool removeComments, IMarkupFormatter customFormatter, bool preserveMediaQueries)
155+
{
156+
return new PreMailer(html, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, false);
157+
}
158+
159+
/// <summary>
160+
/// In-lines the CSS within the HTML given.
161+
/// </summary>
162+
/// <param name="baseUri">The base url that will be used to resolve any relative urls.</param>
112163
/// <param name="html">The HTML input.</param>
113164
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
114165
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
@@ -127,8 +178,25 @@ public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeSt
127178
/// <summary>
128179
/// In-lines the CSS within the HTML given.
129180
/// </summary>
130-
/// /// <param name="baseUri">The base url that will be used to resolve any relative urls</param>
131-
/// <param name="baseUri">The Url that all relative urls will be off of.</param>
181+
/// <param name="baseUri">The base url that will be used to resolve any relative urls.</param>
182+
/// <param name="stream">The HTML input.</param>
183+
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
184+
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
185+
/// <param name="css">A string containing a style-sheet for inlining.</param>
186+
/// <param name="stripIdAndClassAttributes">True to strip ID and class attributes</param>
187+
/// <param name="removeComments">True to remove comments, false to leave them intact</param>
188+
/// <param name="customFormatter">Custom formatter to use</param>
189+
/// <param name="preserveMediaQueries">If set to true and removeStyleElements is true, it will instead preserve unsupported media queries in the style node and remove the other css, instead of removing the whole style node</param>
190+
/// <returns>Returns the html input, with styles moved to inline attributes.</returns>
191+
public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool removeStyleElements, string ignoreElements, string css, bool stripIdAndClassAttributes, bool removeComments, IMarkupFormatter customFormatter, bool preserveMediaQueries)
192+
{
193+
return new PreMailer(stream, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, false);
194+
}
195+
196+
/// <summary>
197+
/// In-lines the CSS within the HTML given.
198+
/// </summary>
199+
/// <param name="baseUri">The base url that will be used to resolve any relative urls.</param>
132200
/// <param name="stream">The HTML input.</param>
133201
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
134202
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
@@ -144,6 +212,22 @@ public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool remove
144212
return new PreMailer(stream, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, useEmailFormatter);
145213
}
146214

215+
/// <summary>
216+
/// In-lines the CSS for the current HTML
217+
/// </summary>
218+
/// <param name="removeStyleElements">If set to <c>true</c> the style elements are removed.</param>
219+
/// <param name="ignoreElements">CSS selector for STYLE elements to ignore (e.g. mobile-specific styles etc.)</param>
220+
/// <param name="css">A string containing a style-sheet for inlining.</param>
221+
/// <param name="stripIdAndClassAttributes">True to strip ID and class attributes</param>
222+
/// <param name="removeComments">True to remove comments, false to leave them intact</param>
223+
/// <param name="customFormatter">Custom formatter to use</param>
224+
/// <param name="preserveMediaQueries">If set to true and removeStyleElements is true, it will instead preserve unsupported media queries in the style node and remove the other css, instead of removing the whole style node</param>
225+
/// <returns>Returns the html input, with styles moved to inline attributes.</returns>
226+
public InlineResult MoveCssInline(bool removeStyleElements, string ignoreElements, string css, bool stripIdAndClassAttributes, bool removeComments, IMarkupFormatter customFormatter, bool preserveMediaQueries)
227+
{
228+
return MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries, false);
229+
}
230+
147231
/// <summary>
148232
/// In-lines the CSS for the current HTML
149233
/// </summary>

0 commit comments

Comments
 (0)