Skip to content

Commit 18aea8d

Browse files
committed
Merge pull request #60 from pfeurean/master
improve splitting Css styles with semicolons not inside quotes
2 parents ecdf487 + c68f6c8 commit 18aea8d

File tree

2 files changed

+127
-100
lines changed

2 files changed

+127
-100
lines changed

PreMailer.Net/PreMailer.Net.Tests/CssParserTests.cs

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,85 +2,85 @@
22

33
namespace PreMailer.Net.Tests
44
{
5-
[TestClass]
6-
public class CssParserTests
7-
{
8-
[TestMethod]
9-
public void AddStylesheet_ContainsAtCharsetRule_ShouldStripRuleAndParseStylesheet()
10-
{
11-
var stylesheet = "@charset utf-8; div { width: 100% }";
5+
[TestClass]
6+
public class CssParserTests
7+
{
8+
[TestMethod]
9+
public void AddStylesheet_ContainsAtCharsetRule_ShouldStripRuleAndParseStylesheet()
10+
{
11+
var stylesheet = "@charset utf-8; div { width: 100% }";
1212

13-
var parser = new CssParser();
14-
parser.AddStyleSheet(stylesheet);
13+
var parser = new CssParser();
14+
parser.AddStyleSheet(stylesheet);
1515

16-
Assert.IsTrue(parser.Styles.ContainsKey("div"));
17-
}
16+
Assert.IsTrue(parser.Styles.ContainsKey("div"));
17+
}
1818

19-
[TestMethod]
20-
public void AddStylesheet_ContainsAtPageSection_ShouldStripRuleAndParseStylesheet()
21-
{
22-
var stylesheet = "@page :first { margin: 2in 3in; } div { width: 100% }";
19+
[TestMethod]
20+
public void AddStylesheet_ContainsAtPageSection_ShouldStripRuleAndParseStylesheet()
21+
{
22+
var stylesheet = "@page :first { margin: 2in 3in; } div { width: 100% }";
2323

24-
var parser = new CssParser();
25-
parser.AddStyleSheet(stylesheet);
24+
var parser = new CssParser();
25+
parser.AddStyleSheet(stylesheet);
2626

27-
Assert.AreEqual(1, parser.Styles.Count);
28-
Assert.IsTrue(parser.Styles.ContainsKey("div"));
29-
}
27+
Assert.AreEqual(1, parser.Styles.Count);
28+
Assert.IsTrue(parser.Styles.ContainsKey("div"));
29+
}
3030

31-
[TestMethod]
32-
public void AddStylesheet_ContainsUnsupportedMediaQuery_ShouldStrip()
33-
{
34-
var stylesheet = "@media print { div { width: 90%; } }";
31+
[TestMethod]
32+
public void AddStylesheet_ContainsUnsupportedMediaQuery_ShouldStrip()
33+
{
34+
var stylesheet = "@media print { div { width: 90%; } }";
3535

36-
var parser = new CssParser();
37-
parser.AddStyleSheet(stylesheet);
36+
var parser = new CssParser();
37+
parser.AddStyleSheet(stylesheet);
3838

39-
Assert.AreEqual(0, parser.Styles.Count);
40-
}
39+
Assert.AreEqual(0, parser.Styles.Count);
40+
}
4141

42-
[TestMethod]
43-
public void AddStylesheet_ContainsUnsupportedMediaQueryAndNormalRules_ShouldStripMediaQueryAndParseRules()
44-
{
45-
var stylesheet = "div { width: 600px; } @media only screen and (max-width:620px) { div { width: 100% } } p { font-family: serif; }";
42+
[TestMethod]
43+
public void AddStylesheet_ContainsUnsupportedMediaQueryAndNormalRules_ShouldStripMediaQueryAndParseRules()
44+
{
45+
var stylesheet = "div { width: 600px; } @media only screen and (max-width:620px) { div { width: 100% } } p { font-family: serif; }";
4646

47-
var parser = new CssParser();
48-
parser.AddStyleSheet(stylesheet);
47+
var parser = new CssParser();
48+
parser.AddStyleSheet(stylesheet);
4949

50-
Assert.AreEqual(2, parser.Styles.Count);
50+
Assert.AreEqual(2, parser.Styles.Count);
5151

52-
Assert.IsTrue(parser.Styles.ContainsKey("div"));
53-
Assert.AreEqual("600px", parser.Styles["div"].Attributes["width"].Value);
52+
Assert.IsTrue(parser.Styles.ContainsKey("div"));
53+
Assert.AreEqual("600px", parser.Styles["div"].Attributes["width"].Value);
5454

55-
Assert.IsTrue(parser.Styles.ContainsKey("p"));
56-
Assert.AreEqual("serif", parser.Styles["p"].Attributes["font-family"].Value);
57-
}
55+
Assert.IsTrue(parser.Styles.ContainsKey("p"));
56+
Assert.AreEqual("serif", parser.Styles["p"].Attributes["font-family"].Value);
57+
}
5858

59-
[TestMethod]
60-
public void AddStylesheet_ContainsSupportedMediaQuery_ShouldParseQueryRules()
61-
{
62-
var stylesheet = "@media only screen { div { width: 600px; } }";
59+
[TestMethod]
60+
public void AddStylesheet_ContainsSupportedMediaQuery_ShouldParseQueryRules()
61+
{
62+
var stylesheet = "@media only screen { div { width: 600px; } }";
6363

64-
var parser = new CssParser();
65-
parser.AddStyleSheet(stylesheet);
64+
var parser = new CssParser();
65+
parser.AddStyleSheet(stylesheet);
6666

67-
Assert.AreEqual(1, parser.Styles.Count);
67+
Assert.AreEqual(1, parser.Styles.Count);
6868

69-
Assert.IsTrue(parser.Styles.ContainsKey("div"));
70-
Assert.AreEqual("600px", parser.Styles["div"].Attributes["width"].Value);
71-
}
69+
Assert.IsTrue(parser.Styles.ContainsKey("div"));
70+
Assert.AreEqual("600px", parser.Styles["div"].Attributes["width"].Value);
71+
}
7272

73-
[TestMethod]
73+
[TestMethod]
7474
public void AddStylesheet_ContainsImportStatement_ShouldStripOutImportStatement()
75-
{
76-
var stylesheet = "@import url(http://google.com/stylesheet); div { width : 600px; }";
77-
var parser = new CssParser();
75+
{
76+
var stylesheet = "@import url(http://google.com/stylesheet); div { width : 600px; }";
77+
var parser = new CssParser();
7878
parser.AddStyleSheet(stylesheet);
79-
Assert.AreEqual(1, parser.Styles.Count);
79+
Assert.AreEqual(1, parser.Styles.Count);
8080

81-
Assert.IsTrue(parser.Styles.ContainsKey("div"));
81+
Assert.IsTrue(parser.Styles.ContainsKey("div"));
8282
Assert.AreEqual("600px", parser.Styles["div"].Attributes["width"].Value);
83-
}
83+
}
8484

8585

8686
[TestMethod]
@@ -142,5 +142,19 @@ public void AddStylesheet_ContainsMuiltpleImportStatementWithMediaQuerys_ShouldS
142142
Assert.IsTrue(parser.Styles.ContainsKey("div"));
143143
Assert.AreEqual("600px", parser.Styles["div"].Attributes["width"].Value);
144144
}
145-
}
145+
146+
[TestMethod]
147+
public void AddStylesheet_ContainsEncodedImage()
148+
{
149+
var stylesheet = @"#logo
150+
{
151+
content: url('data:image/jpeg; base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=');
152+
max-width: 200px;
153+
height: auto;
154+
}";
155+
var parser = new CssParser();
156+
parser.AddStyleSheet(stylesheet);
157+
var attributes = parser.Styles["#logo"].Attributes;
158+
}
159+
}
146160
}

PreMailer.Net/PreMailer.Net/CssParser.cs

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,55 @@
1-
using System.Linq;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
2+
using System.Linq;
33
using System.Text.RegularExpressions;
44

5-
namespace PreMailer.Net {
6-
public class CssParser {
5+
namespace PreMailer.Net
6+
{
7+
public class CssParser
8+
{
79
private readonly List<string> _styleSheets;
810
private SortedList<string, StyleClass> _scc;
911

10-
public SortedList<string, StyleClass> Styles {
12+
public SortedList<string, StyleClass> Styles
13+
{
1114
get { return _scc; }
1215
set { _scc = value; }
1316
}
1417

15-
public CssParser() {
18+
public CssParser()
19+
{
1620
_styleSheets = new List<string>();
1721
_scc = new SortedList<string, StyleClass>();
1822
}
1923

20-
public void AddStyleSheet(string styleSheetContent) {
24+
public void AddStyleSheet(string styleSheetContent)
25+
{
2126
_styleSheets.Add(styleSheetContent);
2227
ProcessStyleSheet(styleSheetContent);
2328
}
2429

25-
public string GetStyleSheet(int index) {
30+
public string GetStyleSheet(int index)
31+
{
2632
return _styleSheets[index];
2733
}
2834

29-
public StyleClass ParseStyleClass(string className, string style) {
35+
public StyleClass ParseStyleClass(string className, string style)
36+
{
3037
var sc = new StyleClass { Name = className };
3138

3239
FillStyleClass(sc, className, style);
3340

3441
return sc;
3542
}
3643

37-
private void ProcessStyleSheet(string styleSheetContent) {
44+
private void ProcessStyleSheet(string styleSheetContent)
45+
{
3846
string content = CleanUp(styleSheetContent);
3947
string[] parts = content.Split('}');
4048

41-
foreach (string s in parts) {
42-
if (s.IndexOf('{') > -1) {
49+
foreach (string s in parts)
50+
{
51+
if (s.IndexOf('{') > -1)
52+
{
4353
FillStyleClassFromBlock(s);
4454
}
4555
}
@@ -49,47 +59,50 @@ private void ProcessStyleSheet(string styleSheetContent) {
4959
/// Fills the style class.
5060
/// </summary>
5161
/// <param name="s">The style block.</param>
52-
private void FillStyleClassFromBlock(string s)
53-
{
54-
string[] parts = s.Split('{');
55-
var cleaned = parts[0].Trim();
56-
var styleNames = cleaned.Split(',').Select(x => x.Trim());
57-
58-
foreach (var styleName in styleNames)
59-
{
60-
StyleClass sc;
61-
if (_scc.ContainsKey(styleName))
62-
{
63-
sc = _scc[styleName];
64-
_scc.Remove(styleName);
65-
}
66-
else
67-
{
68-
sc = new StyleClass();
69-
}
70-
71-
FillStyleClass(sc, styleName, parts[1]);
72-
73-
_scc.Add(sc.Name, sc);
74-
}
75-
}
62+
private void FillStyleClassFromBlock(string s)
63+
{
64+
string[] parts = s.Split('{');
65+
var cleaned = parts[0].Trim();
66+
var styleNames = cleaned.Split(',').Select(x => x.Trim());
67+
68+
foreach (var styleName in styleNames)
69+
{
70+
StyleClass sc;
71+
if (_scc.ContainsKey(styleName))
72+
{
73+
sc = _scc[styleName];
74+
_scc.Remove(styleName);
75+
}
76+
else
77+
{
78+
sc = new StyleClass();
79+
}
80+
81+
FillStyleClass(sc, styleName, parts[1]);
82+
83+
_scc.Add(sc.Name, sc);
84+
}
85+
}
7686

7787
/// <summary>
7888
/// Fills the style class.
7989
/// </summary>
8090
/// <param name="sc">The style class.</param>
8191
/// <param name="styleName">Name of the style.</param>
8292
/// <param name="style">The styles.</param>
83-
private void FillStyleClass(StyleClass sc, string styleName, string style) {
93+
private void FillStyleClass(StyleClass sc, string styleName, string style)
94+
{
8495
sc.Name = styleName;
8596

8697
//string[] atrs = style.Split(';');
87-
string[] atrs = CleanUp(style).Split(';');
98+
//string[] atrs = CleanUp(style).Split(';');
99+
string[] atrs = Regex.Split(CleanUp(style), @"(;)(?=(?:[^""']|[""|'][^""']*"")*$)", RegexOptions.Multiline | RegexOptions.Compiled);
88100

89-
foreach (string a in atrs) {
90-
var attribute = CssAttribute.FromRule(a);
101+
foreach (string a in atrs)
102+
{
103+
var attribute = CssAttribute.FromRule(a);
91104

92-
if (attribute != null) sc.Attributes[attribute.Style] = attribute;
105+
if (attribute != null) sc.Attributes[attribute.Style] = attribute;
93106
}
94107
}
95108

@@ -98,7 +111,7 @@ private void FillStyleClass(StyleClass sc, string styleName, string style) {
98111
private string CleanUp(string s)
99112
{
100113
string temp = s;
101-
const string cssCommentRegex = @"(?:/\*(.|[\r\n])*?\*/)|(?:(?<!url\s*\([^)]*)//.*)";
114+
const string cssCommentRegex = @"(?:/\*(.|[\r\n])*?\*/)|(?:(?<!url\s*\([^)]*)//.*)";
102115
const string unsupportedAtRuleRegex = @"(?:@charset [^;]*;)|(?:@(page|font-face)[^{]*{[^}]*})|@import.+?;";
103116

104117
temp = Regex.Replace(temp, cssCommentRegex, "");

0 commit comments

Comments
 (0)