001/* 002 * 003 * JSMin.java 2006-02-13 004 * 005 * Updated 2007-08-20 with updates from jsmin.c (2007-05-22) 006 * 007 * Copyright (c) 2006 John Reilly (www.inconspicuous.org) 008 * 009 * This work is a translation from C to Java of jsmin.c published by 010 * Douglas Crockford. Permission is hereby granted to use the Java 011 * version under the same conditions as the jsmin.c on which it is 012 * based. 013 * 014 * 015 * 016 * 017 * jsmin.c 2003-04-21 018 * 019 * Copyright (c) 2002 Douglas Crockford (www.crockford.com) 020 * 021 * Permission is hereby granted, free of charge, to any person obtaining a copy 022 * of this software and associated documentation files (the "Software"), to deal 023 * in the Software without restriction, including without limitation the rights 024 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 025 * copies of the Software, and to permit persons to whom the Software is 026 * furnished to do so, subject to the following conditions: 027 * 028 * The above copyright notice and this permission notice shall be included in 029 * all copies or substantial portions of the Software. 030 * 031 * The Software shall be used for Good, not Evil. 032 * 033 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 034 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 035 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 036 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 037 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 038 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 039 * SOFTWARE. 040 */ 041 042package org.nuxeo.theme.html; 043 044import java.io.FileInputStream; 045import java.io.FileNotFoundException; 046import java.io.IOException; 047import java.io.InputStream; 048import java.io.OutputStream; 049import java.io.PushbackInputStream; 050 051import org.apache.commons.logging.Log; 052import org.apache.commons.logging.LogFactory; 053import org.nuxeo.common.xmap.XAnnotatedList; 054 055public class JSMin { 056 057 private static final Log log = LogFactory.getLog(XAnnotatedList.class); 058 059 private static final int EOF = -1; 060 061 private PushbackInputStream in; 062 063 private OutputStream out; 064 065 private int theA; 066 067 private int theB; 068 069 public JSMin(InputStream in, OutputStream out) { 070 this.in = new PushbackInputStream(in); 071 this.out = out; 072 } 073 074 /** 075 * isAlphanum -- return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. 076 */ 077 static boolean isAlphanum(int c) { 078 return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' 079 || c == '\\' || c > 126); 080 } 081 082 /** 083 * get -- return the next character from stdin. Watch out for lookahead. If the character is a control character, 084 * translate it to a space or linefeed. 085 */ 086 int get() throws IOException { 087 int c = in.read(); 088 089 if (c >= ' ' || c == '\n' || c == EOF) { 090 return c; 091 } 092 093 if (c == '\r') { 094 return '\n'; 095 } 096 097 return ' '; 098 } 099 100 /** 101 * Get the next character without getting it. 102 */ 103 int peek() throws IOException { 104 int lookaheadChar = in.read(); 105 in.unread(lookaheadChar); 106 return lookaheadChar; 107 } 108 109 /** 110 * next -- get the next character, excluding comments. peek() is used to see if a '/' is followed by a '/' or '*'. 111 */ 112 int next() throws IOException, UnterminatedCommentException { 113 int c = get(); 114 if (c == '/') { 115 switch (peek()) { 116 case '/': 117 for (;;) { 118 c = get(); 119 if (c <= '\n') { 120 return c; 121 } 122 } 123 124 case '*': 125 get(); 126 for (;;) { 127 switch (get()) { 128 case '*': 129 if (peek() == '/') { 130 get(); 131 return ' '; 132 } 133 break; 134 case EOF: 135 throw new UnterminatedCommentException(); 136 } 137 } 138 139 default: 140 return c; 141 } 142 143 } 144 return c; 145 } 146 147 /** 148 * action -- do something! What you do is determined by the argument: 1 Output A. Copy B to A. Get the next B. 2 149 * Copy B to A. Get the next B. (Delete A). 3 Get the next B. (Delete B). action treats a string as a single 150 * character. Wow! action recognizes a regular expression if it is preceded by ( or , or =. 151 */ 152 153 void action(int d) throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, 154 UnterminatedStringLiteralException { 155 switch (d) { 156 case 1: 157 out.write(theA); 158 case 2: 159 theA = theB; 160 161 if (theA == '\'' || theA == '"') { 162 for (;;) { 163 out.write(theA); 164 theA = get(); 165 if (theA == theB) { 166 break; 167 } 168 if (theA <= '\n') { 169 throw new UnterminatedStringLiteralException(); 170 } 171 if (theA == '\\') { 172 out.write(theA); 173 theA = get(); 174 } 175 } 176 } 177 178 case 3: 179 theB = next(); 180 if (theB == '/' 181 && (theA == '(' || theA == ',' || theA == '=' || theA == ':' || theA == '[' || theA == '!' 182 || theA == '&' || theA == '|' || theA == '?' || theA == '{' || theA == '}' || theA == ';' || theA == '\n')) { 183 out.write(theA); 184 out.write(theB); 185 for (;;) { 186 theA = get(); 187 if (theA == '/') { 188 break; 189 } else if (theA == '\\') { 190 out.write(theA); 191 theA = get(); 192 } else if (theA <= '\n') { 193 throw new UnterminatedRegExpLiteralException(); 194 } 195 out.write(theA); 196 } 197 theB = next(); 198 } 199 } 200 } 201 202 /** 203 * jsmin -- Copy the input to the output, deleting the characters which are insignificant to JavaScript. Comments 204 * will be removed. Tabs will be replaced with spaces. Carriage returns will be replaced with linefeeds. Most spaces 205 * and linefeeds will be removed. 206 */ 207 public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, 208 UnterminatedStringLiteralException { 209 theA = '\n'; 210 action(3); 211 while (theA != EOF) { 212 switch (theA) { 213 case ' ': 214 if (isAlphanum(theB)) { 215 action(1); 216 } else { 217 action(2); 218 } 219 break; 220 case '\n': 221 switch (theB) { 222 case '{': 223 case '[': 224 case '(': 225 case '+': 226 case '-': 227 action(1); 228 break; 229 case ' ': 230 action(3); 231 break; 232 default: 233 if (isAlphanum(theB)) { 234 action(1); 235 } else { 236 action(2); 237 } 238 } 239 break; 240 default: 241 switch (theB) { 242 case ' ': 243 if (isAlphanum(theA)) { 244 action(1); 245 break; 246 } 247 action(3); 248 break; 249 case '\n': 250 switch (theA) { 251 case '}': 252 case ']': 253 case ')': 254 case '+': 255 case '-': 256 case '"': 257 case '\'': 258 action(1); 259 break; 260 default: 261 if (isAlphanum(theA)) { 262 action(1); 263 } else { 264 action(3); 265 } 266 } 267 break; 268 default: 269 action(1); 270 break; 271 } 272 } 273 } 274 out.flush(); 275 } 276 277 class UnterminatedCommentException extends Exception { 278 } 279 280 class UnterminatedStringLiteralException extends Exception { 281 } 282 283 class UnterminatedRegExpLiteralException extends Exception { 284 } 285 286 public static void main(String arg[]) { 287 try { 288 JSMin jsmin = new JSMin(new FileInputStream(arg[0]), System.out); 289 jsmin.jsmin(); 290 } catch (FileNotFoundException e) { 291 log.error(e, e); 292 } catch (ArrayIndexOutOfBoundsException e) { 293 log.error(e, e); 294 } catch (IOException e) { 295 log.error(e, e); 296 } catch (UnterminatedRegExpLiteralException e) { 297 log.error(e, e); 298 } catch (UnterminatedCommentException e) { 299 log.error(e, e); 300 } catch (UnterminatedStringLiteralException e) { 301 log.error(e, e); 302 } 303 } 304 305}