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+ }
0 commit comments