From 310a491f9805c2c2968e1ecc9ec58e375f032f99 Mon Sep 17 00:00:00 2001 From: Liam Farnan Date: Wed, 27 Oct 2021 19:39:17 +1030 Subject: [PATCH 1/5] add parameter to preserve unsupported media queries when removing style nodes --- PreMailer.Net/PreMailer.Net/CssParser.cs | 11 +++++++ PreMailer.Net/PreMailer.Net/PreMailer.cs | 39 ++++++++++++++++-------- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/PreMailer.Net/PreMailer.Net/CssParser.cs b/PreMailer.Net/PreMailer.Net/CssParser.cs index e147b5af..8d5ae23a 100644 --- a/PreMailer.Net/PreMailer.Net/CssParser.cs +++ b/PreMailer.Net/PreMailer.Net/CssParser.cs @@ -125,5 +125,16 @@ private static string CleanupMediaQueries(string s) { return MediaQueryRegex.Replace(s, m => SupportedMediaQueriesRegex.IsMatch(m.Groups["query"].Value.Trim()) ? m.Groups["styles"].Value.Trim() : string.Empty); } + + public static IEnumerable GetUnsupportedMediaQueries(string s) + { + foreach (Match match in MediaQueryRegex.Matches(s)) + { + if (!SupportedMediaQueriesRegex.IsMatch(match.Value)) + { + yield return match.Value; + } + } + } } } diff --git a/PreMailer.Net/PreMailer.Net/PreMailer.cs b/PreMailer.Net/PreMailer.Net/PreMailer.cs index 579d7120..e81d09fb 100644 --- a/PreMailer.Net/PreMailer.Net/PreMailer.cs +++ b/PreMailer.Net/PreMailer.Net/PreMailer.cs @@ -74,9 +74,9 @@ public PreMailer(Stream stream, Uri baseUri = null) /// True to strip ID and class attributes /// True to remove comments, false to leave them intact /// Returns the html input, with styles moved to inline attributes. - public static InlineResult MoveCssInline(string html, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null) + public static InlineResult MoveCssInline(string html, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { - return new PreMailer(html).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter); + return new PreMailer(html).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries); } /// @@ -89,9 +89,9 @@ public static InlineResult MoveCssInline(string html, bool removeStyleElements = /// True to strip ID and class attributes /// True to remove comments, false to leave them intact /// Returns the html input, with styles moved to inline attributes. - public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null) + public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { - return new PreMailer(stream).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter); + return new PreMailer(stream).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries); } /// @@ -106,9 +106,9 @@ public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements /// True to strip ID and class attributes /// True to remove comments, false to leave them intact /// Returns the html input, with styles moved to inline attributes. - public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null) + public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { - return new PreMailer(html, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter); + return new PreMailer(html, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries); } /// @@ -123,9 +123,9 @@ public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeSt /// True to strip ID and class attributes /// True to remove comments, false to leave them intact /// Returns the html input, with styles moved to inline attributes. - public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null) + public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { - return new PreMailer(stream, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter); + return new PreMailer(stream, baseUri).MoveCssInline(removeStyleElements, ignoreElements, css, stripIdAndClassAttributes, removeComments, customFormatter, preserveMediaQueries); } /// @@ -137,7 +137,7 @@ public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool remove /// True to strip ID and class attributes /// True to remove comments, false to leave them intact /// Returns the html input, with styles moved to inline attributes. - public InlineResult MoveCssInline(bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null) + public InlineResult MoveCssInline(bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { // Store the variables used for inlining the CSS _removeStyleElements = removeStyleElements; @@ -155,7 +155,7 @@ public InlineResult MoveCssInline(bool removeStyleElements = false, string ignor if (_removeStyleElements) { - RemoveStyleElements(cssSourceNodes); + RemoveStyleElements(cssSourceNodes, preserveMediaQueries); RemoveStyleElements(cssLinkNodes); } @@ -317,11 +317,26 @@ private IEnumerable CssLinkNodes() } - private void RemoveStyleElements(IEnumerable cssSourceNodes) + private void RemoveStyleElements(IEnumerable cssSourceNodes, bool preserveMediaQueries = false) { foreach (var node in cssSourceNodes) { - node.Remove(); + if (preserveMediaQueries) + { + var unsupportedMediaQueries = CssParser.GetUnsupportedMediaQueries(node.InnerHtml); + if (unsupportedMediaQueries.Any()) + { + node.InnerHtml = $"\n{string.Join("\n", unsupportedMediaQueries)}\n"; + } + else + { + node.Remove(); + } + } + else + { + node.Remove(); + } } } From 30033ff2cdf1ffdb01e7e706f4699b9f83b618b9 Mon Sep 17 00:00:00 2001 From: Liam Farnan Date: Wed, 27 Oct 2021 19:56:31 +1030 Subject: [PATCH 2/5] docs --- PreMailer.Net/PreMailer.Net/PreMailer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PreMailer.Net/PreMailer.Net/PreMailer.cs b/PreMailer.Net/PreMailer.Net/PreMailer.cs index e81d09fb..f6b7fd3b 100644 --- a/PreMailer.Net/PreMailer.Net/PreMailer.cs +++ b/PreMailer.Net/PreMailer.Net/PreMailer.cs @@ -73,6 +73,7 @@ public PreMailer(Stream stream, Uri baseUri = null) /// A string containing a style-sheet for inlining. /// True to strip ID and class attributes /// True to remove comments, false to leave them intact + /// 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 /// Returns the html input, with styles moved to inline attributes. public static InlineResult MoveCssInline(string html, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { @@ -88,6 +89,7 @@ public static InlineResult MoveCssInline(string html, bool removeStyleElements = /// A string containing a style-sheet for inlining. /// True to strip ID and class attributes /// True to remove comments, false to leave them intact + /// 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 /// Returns the html input, with styles moved to inline attributes. public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { @@ -105,6 +107,7 @@ public static InlineResult MoveCssInline(Stream stream, bool removeStyleElements /// A string containing a style-sheet for inlining. /// True to strip ID and class attributes /// True to remove comments, false to leave them intact + /// 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 /// Returns the html input, with styles moved to inline attributes. public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { @@ -122,6 +125,7 @@ public static InlineResult MoveCssInline(Uri baseUri, string html, bool removeSt /// A string containing a style-sheet for inlining. /// True to strip ID and class attributes /// True to remove comments, false to leave them intact + /// 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 /// Returns the html input, with styles moved to inline attributes. public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { @@ -136,6 +140,7 @@ public static InlineResult MoveCssInline(Uri baseUri, Stream stream, bool remove /// A string containing a style-sheet for inlining. /// True to strip ID and class attributes /// True to remove comments, false to leave them intact + /// 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 /// Returns the html input, with styles moved to inline attributes. public InlineResult MoveCssInline(bool removeStyleElements = false, string ignoreElements = null, string css = null, bool stripIdAndClassAttributes = false, bool removeComments = false, IMarkupFormatter customFormatter = null, bool preserveMediaQueries = false) { From 24bd0d98ab16eedd5e3d0307ae950642786a6139 Mon Sep 17 00:00:00 2001 From: Liam Farnan Date: Tue, 2 Nov 2021 13:08:38 +1030 Subject: [PATCH 3/5] unnecessary newlines --- PreMailer.Net/PreMailer.Net/PreMailer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PreMailer.Net/PreMailer.Net/PreMailer.cs b/PreMailer.Net/PreMailer.Net/PreMailer.cs index f6b7fd3b..a7e3577b 100644 --- a/PreMailer.Net/PreMailer.Net/PreMailer.cs +++ b/PreMailer.Net/PreMailer.Net/PreMailer.cs @@ -331,7 +331,7 @@ private void RemoveStyleElements(IEnumerable cssSourceNodes, bool pres var unsupportedMediaQueries = CssParser.GetUnsupportedMediaQueries(node.InnerHtml); if (unsupportedMediaQueries.Any()) { - node.InnerHtml = $"\n{string.Join("\n", unsupportedMediaQueries)}\n"; + node.InnerHtml = $"{string.Join("\n", unsupportedMediaQueries)}"; } else { From bd379d9751c9bb3f04a34363a82777b41620ef9e Mon Sep 17 00:00:00 2001 From: Liam Farnan Date: Tue, 2 Nov 2021 13:10:50 +1030 Subject: [PATCH 4/5] handle null string --- PreMailer.Net/PreMailer.Net/CssParser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PreMailer.Net/PreMailer.Net/CssParser.cs b/PreMailer.Net/PreMailer.Net/CssParser.cs index 8d5ae23a..6acb10b2 100644 --- a/PreMailer.Net/PreMailer.Net/CssParser.cs +++ b/PreMailer.Net/PreMailer.Net/CssParser.cs @@ -128,6 +128,10 @@ private static string CleanupMediaQueries(string s) public static IEnumerable GetUnsupportedMediaQueries(string s) { + if (string.IsNullOrWhiteSpace(s)) + { + yield break; + } foreach (Match match in MediaQueryRegex.Matches(s)) { if (!SupportedMediaQueriesRegex.IsMatch(match.Value)) From 22ad181d62de7c8309efbc2269ae49e126b94456 Mon Sep 17 00:00:00 2001 From: Liam Farnan Date: Tue, 2 Nov 2021 13:15:35 +1030 Subject: [PATCH 5/5] add unit tests for preserveMediaQueries --- .../PreMailer.Net.Tests/PreMailerTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs b/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs index 683bac5f..2b9ac1ef 100644 --- a/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs +++ b/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs @@ -137,6 +137,26 @@ public void MoveCssInline_KeepStyleElementsIgnoreElementsMatchesStyleElement_Doe Assert.Contains("
test
"; + + var premailedOutput = PreMailer.MoveCssInline(input, removeStyleElements: true, preserveMediaQueries: true); + + Assert.DoesNotContain("
test
"; + + var premailedOutput = PreMailer.MoveCssInline(input, removeStyleElements: true, preserveMediaQueries: true); + + Assert.Contains("", premailedOutput.Html); + } + [Fact] public void MoveCssInline_MultipleSelectors_HonorsIndividualSpecificity() {