Skip to content

Commit cbe746a

Browse files
committed
Add new JS, CSS and HTML Minifiers
1 parent 93db301 commit cbe746a

3 files changed

Lines changed: 384 additions & 0 deletions

File tree

src/ServiceStack/Html/Minifiers.cs

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
using System;
2+
using System.IO;
3+
using System.Text;
4+
using System.Text.RegularExpressions;
5+
6+
namespace ServiceStack.Html
7+
{
8+
public static class Minifiers
9+
{
10+
public static string MinifyJavaScript(string js)
11+
{
12+
return new JSMinifier().Minify(js);
13+
}
14+
15+
//http://madskristensen.net/post/efficient-stylesheet-minification-in-c
16+
public static string MinifyCss(string css)
17+
{
18+
css = Regex.Replace(css, @"[a-zA-Z]+#", "#");
19+
css = Regex.Replace(css, @"[\n\r]+\s*", string.Empty);
20+
css = Regex.Replace(css, @"\s+", " ");
21+
css = Regex.Replace(css, @"\s?([:,;{}])\s?", "$1");
22+
css = css.Replace(";}", "}");
23+
css = Regex.Replace(css, @"([\s:]0)(px|pt|%|em)", "$1");
24+
25+
// Remove comments from CSS
26+
css = Regex.Replace(css, @"/\[\d\D]?\*/", string.Empty);
27+
28+
return css;
29+
}
30+
31+
static Regex BetweenScriptTagsRegEx = new Regex(@"<script[^>]*>[\w|\t|\r|\W]*?</script>", RegexOptions.Compiled);
32+
static Regex BetweenTagsRegex = new Regex(@"(?<=[^])\t{2,}|(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,11}(?=[<])|(?=[\n])\s{2,}|(?=[\r])\s{2,}", RegexOptions.Compiled);
33+
static Regex MatchBodyRegEx = new Regex(@"</body>", RegexOptions.Compiled);
34+
35+
public static string MinifyHtml(string html)
36+
{
37+
if (html == null)
38+
return html;
39+
40+
var mymatch = BetweenScriptTagsRegEx.Matches(html);
41+
html = BetweenScriptTagsRegEx.Replace(html, string.Empty);
42+
html = BetweenTagsRegex.Replace(html, string.Empty);
43+
44+
var str = string.Empty;
45+
foreach (Match match in mymatch)
46+
{
47+
str += match.ToString();
48+
}
49+
50+
html = MatchBodyRegEx.Replace(html, str + "</body>");
51+
return html;
52+
}
53+
}
54+
55+
/* https://github.com/extnet/Ext.NET.Utilities/blob/master/Ext.Net.Utilities/JavaScript/JSMin.cs
56+
* Originally written in 'C', this code has been converted to the C# language.
57+
* The author's copyright message is reproduced below.
58+
* All modifications from the original to C# are placed in the public domain.
59+
*/
60+
61+
/* jsmin.c
62+
2007-05-22
63+
Copyright (c) 2002 Douglas Crockford (www.crockford.com)
64+
Permission is hereby granted, free of charge, to any person obtaining a copy of
65+
this software and associated documentation files (the "Software"), to deal in
66+
the Software without restriction, including without limitation the rights to
67+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
68+
of the Software, and to permit persons to whom the Software is furnished to do
69+
so, subject to the following conditions:
70+
The above copyright notice and this permission notice shall be included in all
71+
copies or substantial portions of the Software.
72+
The Software shall be used for Good, not Evil.
73+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
74+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
75+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
76+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
77+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
78+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
79+
SOFTWARE.
80+
*/
81+
public class JSMinifier
82+
{
83+
const int EOF = -1;
84+
85+
TextReader sr;
86+
StringBuilder sb;
87+
int theA;
88+
int theB;
89+
int theLookahead = EOF;
90+
91+
public string Minify(string src) //removed the out file path
92+
{
93+
using (sr = new StringReader(src))
94+
{
95+
sb = new StringBuilder();
96+
jsmin();
97+
return sb.ToString(); // return the minified string
98+
99+
}
100+
}
101+
102+
/* jsmin -- Copy the input to the output, deleting the characters which are
103+
insignificant to JavaScript. Comments will be removed. Tabs will be
104+
replaced with spaces. Carriage returns will be replaced with linefeeds.
105+
Most spaces and linefeeds will be removed.
106+
*/
107+
void jsmin()
108+
{
109+
theA = '\n';
110+
action(3);
111+
while (theA != EOF)
112+
{
113+
switch (theA)
114+
{
115+
case ' ':
116+
{
117+
if (isAlphanum(theB))
118+
{
119+
action(1);
120+
}
121+
else
122+
{
123+
action(2);
124+
}
125+
break;
126+
}
127+
case '\n':
128+
{
129+
switch (theB)
130+
{
131+
case '{':
132+
case '[':
133+
case '(':
134+
case '+':
135+
case '-':
136+
{
137+
action(1);
138+
break;
139+
}
140+
case ' ':
141+
{
142+
action(3);
143+
break;
144+
}
145+
default:
146+
{
147+
if (isAlphanum(theB))
148+
{
149+
action(1);
150+
}
151+
else
152+
{
153+
action(2);
154+
}
155+
break;
156+
}
157+
}
158+
break;
159+
}
160+
default:
161+
{
162+
switch (theB)
163+
{
164+
case ' ':
165+
{
166+
if (isAlphanum(theA))
167+
{
168+
action(1);
169+
break;
170+
}
171+
action(3);
172+
break;
173+
}
174+
case '\n':
175+
{
176+
switch (theA)
177+
{
178+
case '}':
179+
case ']':
180+
case ')':
181+
case '+':
182+
case '-':
183+
case '"':
184+
case '\'':
185+
{
186+
action(1);
187+
break;
188+
}
189+
default:
190+
{
191+
if (isAlphanum(theA))
192+
{
193+
action(1);
194+
}
195+
else
196+
{
197+
action(3);
198+
}
199+
break;
200+
}
201+
}
202+
break;
203+
}
204+
default:
205+
{
206+
action(1);
207+
break;
208+
}
209+
}
210+
break;
211+
}
212+
}
213+
}
214+
}
215+
/* action -- do something! What you do is determined by the argument:
216+
1 Output A. Copy B to A. Get the next B.
217+
2 Copy B to A. Get the next B. (Delete A).
218+
3 Get the next B. (Delete B).
219+
action treats a string as a single character. Wow!
220+
action recognizes a regular expression if it is preceded by ( or , or =.
221+
*/
222+
void action(int d)
223+
{
224+
if (d <= 1)
225+
{
226+
put(theA);
227+
}
228+
if (d <= 2)
229+
{
230+
theA = theB;
231+
if (theA == '\'' || theA == '"')
232+
{
233+
for (; ; )
234+
{
235+
put(theA);
236+
theA = get();
237+
if (theA == theB)
238+
{
239+
break;
240+
}
241+
if (theA <= '\n')
242+
{
243+
throw new Exception(string.Format("Error: JSMIN unterminated string literal: {0}\n", theA));
244+
}
245+
if (theA == '\\')
246+
{
247+
put(theA);
248+
theA = get();
249+
}
250+
}
251+
}
252+
}
253+
if (d <= 3)
254+
{
255+
theB = next();
256+
if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
257+
theA == '[' || theA == '!' || theA == ':' ||
258+
theA == '&' || theA == '|' || theA == '?' ||
259+
theA == '{' || theA == '}' || theA == ';' ||
260+
theA == '\n'))
261+
{
262+
put(theA);
263+
put(theB);
264+
for (; ; )
265+
{
266+
theA = get();
267+
if (theA == '/')
268+
{
269+
break;
270+
}
271+
else if (theA == '\\')
272+
{
273+
put(theA);
274+
theA = get();
275+
}
276+
else if (theA <= '\n')
277+
{
278+
throw new Exception(string.Format("Error: JSMIN unterminated Regular Expression literal : {0}.\n", theA));
279+
}
280+
put(theA);
281+
}
282+
theB = next();
283+
}
284+
}
285+
}
286+
/* next -- get the next character, excluding comments. peek() is used to see
287+
if a '/' is followed by a '/' or '*'.
288+
*/
289+
int next()
290+
{
291+
int c = get();
292+
if (c == '/')
293+
{
294+
switch (peek())
295+
{
296+
case '/':
297+
{
298+
for (; ; )
299+
{
300+
c = get();
301+
if (c <= '\n')
302+
{
303+
return c;
304+
}
305+
}
306+
}
307+
case '*':
308+
{
309+
get();
310+
for (; ; )
311+
{
312+
switch (get())
313+
{
314+
case '*':
315+
{
316+
if (peek() == '/')
317+
{
318+
get();
319+
return ' ';
320+
}
321+
break;
322+
}
323+
case EOF:
324+
{
325+
throw new Exception("Error: JSMIN Unterminated comment.\n");
326+
}
327+
}
328+
}
329+
}
330+
default:
331+
{
332+
return c;
333+
}
334+
}
335+
}
336+
return c;
337+
}
338+
/* peek -- get the next character without getting it.
339+
*/
340+
int peek()
341+
{
342+
theLookahead = get();
343+
return theLookahead;
344+
}
345+
/* get -- return the next character from stdin. Watch out for lookahead. If
346+
the character is a control character, translate it to a space or
347+
linefeed.
348+
*/
349+
int get()
350+
{
351+
int c = theLookahead;
352+
theLookahead = EOF;
353+
if (c == EOF)
354+
{
355+
c = sr.Read();
356+
}
357+
if (c >= ' ' || c == '\n' || c == EOF)
358+
{
359+
return c;
360+
}
361+
if (c == '\r')
362+
{
363+
return '\n';
364+
}
365+
return ' ';
366+
}
367+
368+
void put(int c)
369+
{
370+
sb.Append((char)c);
371+
}
372+
/* isAlphanum -- return true if the character is a letter, digit, underscore,
373+
dollar sign, or non-ASCII character.
374+
*/
375+
bool isAlphanum(int c)
376+
{
377+
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
378+
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
379+
c > 126);
380+
}
381+
}
382+
}

src/ServiceStack/ServiceStack.Signed.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
</Compile>
106106
<Compile Include="Configuration\EnvironmentVariableSettings.cs" />
107107
<Compile Include="Configuration\MultiAppSettings.cs" />
108+
<Compile Include="Html\Minifiers.cs" />
108109
<Compile Include="NativeTypes\Swift\SwiftGenerator.cs" />
109110
<Compile Include="NativeTypes\TypeScript\TypeScriptGenerator.cs" />
110111
<Compile Include="NativeTypes\VbNet\VbNetGenerator.cs" />

src/ServiceStack/ServiceStack.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
</Compile>
100100
<Compile Include="Configuration\EnvironmentVariableSettings.cs" />
101101
<Compile Include="Configuration\MultiAppSettings.cs" />
102+
<Compile Include="Html\Minifiers.cs" />
102103
<Compile Include="NativeTypes\Swift\SwiftGenerator.cs" />
103104
<Compile Include="NativeTypes\TypeScript\TypeScriptGenerator.cs" />
104105
<Compile Include="NativeTypes\VbNet\VbNetGenerator.cs" />

0 commit comments

Comments
 (0)