Skip to content

Commit 7a1b59b

Browse files
committed
Added minify formatter
1 parent 0d8e2fe commit 7a1b59b

File tree

3 files changed

+227
-17
lines changed

3 files changed

+227
-17
lines changed

src/AngleSharp.Css.Tests/CssConstructionFunctions.cs

+4-17
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,7 @@ namespace AngleSharp.Css.Tests
99

1010
static class CssConstructionFunctions
1111
{
12-
internal static IHtmlDocument ParseDocument(String source)
13-
{
14-
var context = BrowsingContext.New(Configuration.Default.WithCss());
15-
var parser = context.GetService<IHtmlParser>();
16-
return parser.ParseDocument(source);
17-
}
18-
19-
internal static IHtmlDocument ParseDocument(String source, CssParserOptions options)
12+
internal static IHtmlDocument ParseDocument(String source, CssParserOptions options = default)
2013
{
2114
var context = BrowsingContext.New(Configuration.Default.WithCss(options));
2215
var parser = context.GetService<IHtmlParser>();
@@ -35,19 +28,13 @@ internal static IFeatureValidator CreateMediaFeatureValidator(String name)
3528
return factory.Create(name);
3629
}
3730

38-
internal static CssStyleSheet ParseStyleSheet(String source)
31+
internal static CssStyleSheet ParseStyleSheet(Stream source, CssParserOptions options = default)
3932
{
40-
var parser = new CssParser();
41-
return parser.ParseStyleSheet(source) as CssStyleSheet;
42-
}
43-
44-
internal static CssStyleSheet ParseStyleSheet(Stream source)
45-
{
46-
var parser = new CssParser();
33+
var parser = new CssParser(options);
4734
return parser.ParseStyleSheet(source) as CssStyleSheet;
4835
}
4936

50-
internal static CssStyleSheet ParseStyleSheet(String source, CssParserOptions options)
37+
internal static CssStyleSheet ParseStyleSheet(String source, CssParserOptions options = default)
5138
{
5239
var parser = new CssParser(options);
5340
return parser.ParseStyleSheet(source) as CssStyleSheet;

src/AngleSharp.Css.Tests/Styling/BasicStyling.cs

+56
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,61 @@ public void CssStyleDeclarationBoundInboundDirection()
154154
Assert.AreEqual("background-color: rgb(255, 0, 0); color: rgb(0, 0, 0)", element.GetAttribute("style"));
155155
Assert.AreEqual(2, element.GetStyle().Length);
156156
}
157+
158+
[Test]
159+
public void MinifyRemovesComment()
160+
{
161+
var sheet = ParseStyleSheet("h1 /* this is a comment */ { color: red; /*another comment*/ }");
162+
var result = sheet.ToCss(new MinifyStyleFormatter());
163+
Assert.AreEqual("h1{color:rgba(255, 0, 0, 1)}", result);
164+
}
165+
166+
[Test]
167+
public void MinifyRemovesEmptyStyleRule()
168+
{
169+
var sheet = ParseStyleSheet("h1 { }");
170+
var result = sheet.ToCss(new MinifyStyleFormatter());
171+
Assert.AreEqual("", result);
172+
}
173+
174+
[Test]
175+
public void MinifyRemovesEmptyStyleRuleKeepsOtherRule()
176+
{
177+
var sheet = ParseStyleSheet("h1 { } h2 { font-size:0; }");
178+
var result = sheet.ToCss(new MinifyStyleFormatter());
179+
Assert.AreEqual("h2{font-size:0}", result);
180+
}
181+
182+
[Test]
183+
public void MinifyRemovesEmptyMediaRule()
184+
{
185+
var sheet = ParseStyleSheet("@media screen { h1 { } }");
186+
var result = sheet.ToCss(new MinifyStyleFormatter());
187+
Assert.AreEqual("", result);
188+
}
189+
190+
[Test]
191+
public void MinifyDoesNotRemovesMediaRuleIfOneStyleIsInThere()
192+
{
193+
var sheet = ParseStyleSheet("@media screen { h1 { } h2 { top:0} }");
194+
var result = sheet.ToCss(new MinifyStyleFormatter());
195+
Assert.AreEqual("@media screen{h2{top:0}}", result);
196+
}
197+
198+
[Test]
199+
public void MinifyWorksWithNestedMediaRules()
200+
{
201+
var sheet = ParseStyleSheet("@media screen { @media screen{h1{}}div{border-top : none} }");
202+
var result = sheet.ToCss(new MinifyStyleFormatter());
203+
Assert.AreEqual("@media screen{div{border-top:none}}", result);
204+
}
205+
206+
[Test]
207+
public void MinifyWithMultipleDeclarations()
208+
{
209+
var sheet = ParseStyleSheet("h1 { top:0 ; left: 2px; border: none; } h2 { border: 1px solid red;} h3{}");
210+
var result = sheet.ToCss(new MinifyStyleFormatter());
211+
Assert.AreEqual("h1{top:0;left:2px;border:none}h2{border:1px solid rgba(255, 0, 0, 1)}", result);
212+
}
157213
}
158214
}
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
namespace AngleSharp.Css
2+
{
3+
using AngleSharp.Css.Dom;
4+
using AngleSharp.Text;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
10+
/// <summary>
11+
/// Represents the an CSS3 markup formatter with minimal code.
12+
/// </summary>
13+
public class MinifyStyleFormatter : IStyleFormatter
14+
{
15+
#region Properties
16+
17+
/// <summary>
18+
/// Gets or sets if comments should be preserved.
19+
/// </summary>
20+
public Boolean ShouldKeepComments { get; set; }
21+
22+
/// <summary>
23+
/// Gets or sets if empty (zero-length) rules should be kept.
24+
/// </summary>
25+
public Boolean ShouldKeepEmptyRules { get; set; }
26+
27+
#endregion
28+
29+
#region Methods
30+
31+
String IStyleFormatter.Sheet(IEnumerable<IStyleFormattable> rules)
32+
{
33+
if (ShouldKeepEmptyRules || IsNotEmpty(rules))
34+
{
35+
var sb = StringBuilderPool.Obtain();
36+
37+
using (var writer = new StringWriter(sb))
38+
{
39+
foreach (var rule in rules)
40+
{
41+
rule.ToCss(writer, this);
42+
}
43+
}
44+
45+
return sb.ToPool();
46+
}
47+
48+
return String.Empty;
49+
}
50+
51+
String IStyleFormatter.BlockRules(IEnumerable<IStyleFormattable> rules)
52+
{
53+
if (ShouldKeepEmptyRules || IsNotEmpty(rules))
54+
{
55+
var sb = StringBuilderPool.Obtain().Append(Symbols.CurlyBracketOpen);
56+
57+
using (var writer = new StringWriter(sb))
58+
{
59+
foreach (var rule in rules)
60+
{
61+
rule.ToCss(writer, this);
62+
}
63+
}
64+
65+
return sb.Append(Symbols.CurlyBracketClose).ToPool();
66+
}
67+
68+
return String.Empty;
69+
}
70+
71+
String IStyleFormatter.Declaration(String name, String value, Boolean important) =>
72+
String.Concat(name, ":", String.Concat(value, important ? "!important" : String.Empty));
73+
74+
String IStyleFormatter.BlockDeclarations(IEnumerable<IStyleFormattable> declarations)
75+
{
76+
if (ShouldKeepEmptyRules || declarations.OfType<ICssProperty>().Any())
77+
{
78+
var sb = StringBuilderPool.Obtain().Append(Symbols.CurlyBracketOpen);
79+
80+
using (var writer = new StringWriter(sb))
81+
{
82+
foreach (var declaration in declarations)
83+
{
84+
declaration.ToCss(writer, this);
85+
writer.Write(Symbols.Semicolon);
86+
}
87+
88+
if (sb.Length > 1)
89+
{
90+
sb.Remove(sb.Length - 1, 1);
91+
}
92+
}
93+
94+
return sb.Append(Symbols.CurlyBracketClose).ToPool();
95+
}
96+
97+
return String.Empty;
98+
}
99+
100+
String IStyleFormatter.Rule(String name, String value) =>
101+
CssStyleFormatter.Instance.Rule(name, value);
102+
103+
String IStyleFormatter.Rule(String name, String prelude, String rules) =>
104+
String.IsNullOrEmpty(rules) ? String.Empty : String.Concat(name, String.IsNullOrEmpty(prelude) ? String.Empty : " " + prelude, rules);
105+
106+
String IStyleFormatter.Comment(String data) =>
107+
ShouldKeepComments ? CssStyleFormatter.Instance.Comment(data) : String.Empty;
108+
109+
#endregion
110+
111+
#region Helpers
112+
113+
private static Boolean IsNotEmpty(IEnumerable<IStyleFormattable> rules)
114+
{
115+
foreach (var rule in rules.OfType<ICssRule>())
116+
{
117+
switch (rule.Type)
118+
{
119+
case CssRuleType.Document:
120+
case CssRuleType.Supports:
121+
case CssRuleType.Media:
122+
if (IsNotEmpty(((ICssGroupingRule)rule).Rules))
123+
{
124+
return true;
125+
}
126+
break;
127+
case CssRuleType.Keyframes:
128+
if (IsNotEmpty(((ICssKeyframesRule)rule).Rules))
129+
{
130+
return true;
131+
}
132+
break;
133+
case CssRuleType.FontFace:
134+
if (((ICssFontFaceRule)rule).Style.Any())
135+
{
136+
return true;
137+
}
138+
break;
139+
case CssRuleType.Page:
140+
if (((ICssPageRule)rule).Style.Any())
141+
{
142+
return true;
143+
}
144+
break;
145+
case CssRuleType.Style:
146+
if (((ICssStyleRule)rule).Style.Any())
147+
{
148+
return true;
149+
}
150+
break;
151+
case CssRuleType.Keyframe:
152+
if (((ICssKeyframeRule)rule).Style.Any())
153+
{
154+
return true;
155+
}
156+
break;
157+
default:
158+
return true;
159+
}
160+
}
161+
162+
return false;
163+
}
164+
165+
#endregion
166+
}
167+
}

0 commit comments

Comments
 (0)