//package werkzeugkasten.resource.synchronizer.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Locale;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Various Javascript code utilities. The escape classes were taken from
* jakarta-commons-lang which in turn borrowed from Turbine and other projects.
* The list of authors below is almost certainly far too long, but I'm not sure
* who really wrote these methods.
*
* @author Joe Walker [joe at getahead dot ltd dot uk]
* @author Apache Jakarta Turbine
* @author GenerationJavaCore library
* @author Purple Technology
* @author Henri Yandell
* @author Alexander Day Chaffee
* @author Antony Riley
* @author Helge Tesgaard
* @author Sean Brown
* @author Gary Gregory
* @author Phil Steitz
* @author Pete Gieser
*/
public class JavascriptUtil {
/**
* Flag for use in javascript compression: Remove single line comments. For
* ease of use you may wish to use one of the LEVEL_* compression levels.
*
* @noinspection PointlessBitwiseExpression
*/
public static final int COMPRESS_STRIP_SL_COMMENTS = 1 << 0;
/**
* Flag for use in javascript compression: Remove multi line comments. For
* ease of use you may wish to use one of the LEVEL_* compression levels.
*/
public static final int COMPRESS_STRIP_ML_COMMENTS = 1 << 1;
/**
* Flag for use in javascript compression: Remove whitespace at the start
* and end of a line. For ease of use you may wish to use one of the LEVEL_*
* compression levels.
*/
public static final int COMPRESS_TRIM_LINES = 1 << 2;
/**
* Flag for use in javascript compression: Remove blank lines. This option
* will make the javascript harder to debug because line number references
* are likely be altered. For ease of use you may wish to use one of the
* LEVEL_* compression levels.
*/
public static final int COMPRESS_STRIP_BLANKLINES = 1 << 3;
/**
* Flag for use in javascript compression: Shrink variable names. This
* option is currently un-implemented. For ease of use you may wish to use
* one of the LEVEL_* compression levels.
*/
public static final int COMPRESS_SHRINK_VARS = 1 << 4;
/**
* Flag for use in javascript compression: Remove all lines endings.
* Warning: Javascript can add semi-colons in for you. If you make use of
* this feature then removing newlines may well break. For ease of use you
* may wish to use one of the LEVEL_* compression levels.
*/
public static final int COMPRESS_REMOVE_NEWLINES = 1 << 5;
/**
* Compression level that leaves the source un-touched.
*/
public static final int LEVEL_NONE = 0;
/**
* Basic compression that leaves the source fully debuggable. This includes
* removing all comments and extraneous whitespace.
*/
public static final int LEVEL_DEBUGGABLE = COMPRESS_STRIP_SL_COMMENTS
| COMPRESS_STRIP_ML_COMMENTS | COMPRESS_TRIM_LINES;
/**
* Normal compression makes all changes that will work for generic
* javascript. This adds variable name compression and blank line removal in
* addition to the compressions done by LEVEL_DEBUGGABLE.
*/
public static final int LEVEL_NORMAL = LEVEL_DEBUGGABLE
| COMPRESS_STRIP_BLANKLINES | COMPRESS_SHRINK_VARS;
/**
* LEVEL_ULTRA performs additional compression that makes some assumptions
* about the style of javascript. Specifically it assumes that you are not
* using javascripts ability to infer where the ; should go.
*/
public static final int LEVEL_ULTRA = LEVEL_NORMAL
| COMPRESS_REMOVE_NEWLINES;
/**
* Compress the source code by removing java style comments and removing
* leading and trailing spaces.
*
* @param text
* The javascript (or java) program to compress
* @param level
* The compression level - see LEVEL_* and COMPRESS_* constants.
* @return The compressed version
*/
public static String compress(String text, int level) {
String reply = text;
// First we strip multi line comments. I think this is important:
if ((level & COMPRESS_STRIP_ML_COMMENTS) != 0) {
reply = stripMultiLineComments(text);
}
if ((level & COMPRESS_STRIP_SL_COMMENTS) != 0) {
reply = stripSingleLineComments(reply);
}
if ((level & COMPRESS_TRIM_LINES) != 0) {
reply = trimLines(reply);
}
if ((level & COMPRESS_STRIP_BLANKLINES) != 0) {
reply = stripBlankLines(reply);
}
if ((level & COMPRESS_SHRINK_VARS) != 0) {
reply = shrinkVariableNames(reply);
}
if ((level & COMPRESS_REMOVE_NEWLINES) != 0) {
reply = stripNewlines(reply);
}
return reply;
}
/**
* Remove any leading or trailing spaces from a line of code. This function
* could be improved by making it strip unnecessary double spaces, but since
* we would need to leave double spaces inside strings this is not simple
* and since the benefit is small, we'll leave it for now
*
* @param text
* The javascript program to strip spaces from.
* @return The stripped program
*/
public static String trimLines(String text) {
if (text == null) {
return null;
}
try {
StringBuffer output = new StringBuffer();
// First we strip multi line comments. I think this is important:
BufferedReader in = new BufferedReader(new StringReader(text));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
output.append(line.trim());
output.append('\n');
}
return output.toString();
} catch (IOException ex) {
// log.error("IOExecption unexpected.", ex);
throw new IllegalArgumentException("IOExecption unexpected.");
}
}
/**
* Remove all the single-line comments from a block of text
*
* @param text
* The text to remove single-line comments from
* @return The single-line comment free text
*/
public static String stripSingleLineComments(String text) {
if (text == null) {
return null;
}
try {
StringBuffer output = new StringBuffer();
BufferedReader in = new BufferedReader(new StringReader(text));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
// Skip @DWR comments
if (line.indexOf(COMMENT_RETAIN) == -1) {
int cstart = line.indexOf(COMMENT_SL_START);
if (cstart >= 0) {
line = line.substring(0, cstart);
}
}
output.append(line);
output.append('\n');
}
return output.toString();
} catch (IOException ex) {
// log.error("IOExecption unexpected.", ex);
throw new IllegalArgumentException("IOExecption unexpected.");
}
}
/**
* Remove all the multi-line comments from a block of text
*
* @param text
* The text to remove multi-line comments from
* @return The multi-line comment free text
*/
public static String stripMultiLineComments(String text) {
if (text == null) {
return null;
}
try {
StringBuffer output = new StringBuffer();
// Comment rules:
/*
* / This is still a comment /* /*
*/// Comments do not nest
// /* */ This is in a comment
/* // */// The second // is needed to make this a comment.
// First we strip multi line comments. I think this is important:
boolean inMultiLine = false;
BufferedReader in = new BufferedReader(new StringReader(text));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
if (!inMultiLine) {
// We are not in a multi-line comment, check for a start
int cstart = line.indexOf(COMMENT_ML_START);
if (cstart >= 0) {
// This could be a MLC on one line ...
int cend = line.indexOf(COMMENT_ML_END, cstart
+ COMMENT_ML_START.length());
if (cend >= 0) {
// A comment that starts and ends on one line
// BUG: you can have more than 1 multi-line comment
// on a line
line = line.substring(0, cstart)
+ SPACE
+ line.substring(cend
+ COMMENT_ML_END.length());
} else {
// A real multi-line comment
inMultiLine = true;
line = line.substring(0, cstart) + SPACE;
}
} else {
// We are not in a multi line comment and we havn't
// started one so we are going to ignore closing
// comments even if they exist.
}
} else {
// We are in a multi-line comment, check for the end
int cend = line.indexOf(COMMENT_ML_END);
if (cend >= 0) {
// End of comment
line = line.substring(cend + COMMENT_ML_END.length());
inMultiLine = false;
} else {
// The comment continues
line = SPACE;
}
}
output.append(line);
output.append('\n');
}
return output.toString();
} catch (IOException ex) {
// log.error("IOExecption unexpected.", ex);
throw new IllegalArgumentException("IOExecption unexpected.");
}
}
/**
* Remove all blank lines from a string. A blank line is defined to be a
* line where the only characters are whitespace. We always ensure that the
* line contains a newline at the end.
*
* @param text
* The string to strip blank lines from
* @return The blank line stripped reply
*/
public static String stripBlankLines(String text) {
if (text == null) {
return null;
}
try {
StringBuffer output = new StringBuffer();
BufferedReader in = new BufferedReader(new StringReader(text));
boolean doneOneLine = false;
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
if (line.trim().length() > 0) {
output.append(line);
output.append('\n');
doneOneLine = true;
}
}
if (!doneOneLine) {
output.append('\n');
}
return output.toString();
} catch (IOException ex) {
// log.error("IOExecption unexpected.", ex);
throw new IllegalArgumentException("IOExecption unexpected.");
}
}
/**
* Remove all newline characters from a string.
*
* @param text
* The string to strip newline characters from
* @return The stripped reply
*/
public static String stripNewlines(String text) {
if (text == null) {
return null;
}
try {
StringBuffer output = new StringBuffer();
BufferedReader in = new BufferedReader(new StringReader(text));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
output.append(line);
output.append(SPACE);
}
output.append('\n');
return output.toString();
} catch (IOException ex) {
// log.error("IOExecption unexpected.", ex);
throw new IllegalArgumentException("IOExecption unexpected.");
}
}
/**
* Shrink variable names to a minimum.
*
* @param text
* The javascript program to shrink the variable names in.
* @return The shrunk version of the javascript program.
*/
public static String shrinkVariableNames(String text) {
if (text == null) {
return null;
}
throw new UnsupportedOperationException(
"Variable name shrinking is not supported");
}
/**
*
* Escapes the characters in a String
using JavaScript String
* rules.
*
*
* Escapes any values it finds into their JavaScript String form. Deals
* correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)
*
*
*
* So a tab becomes the characters '\\'
and 't'
.
*
*
*
* The only difference between Java strings and JavaScript strings is that
* in JavaScript, a single quote must be escaped.
*
*
*
* Example:
*
*
* input string: He didn't say, "Stop!"
* output string: He didn\'t say, \"Stop!\"
*
*
*
*
* @param str
* String to escape values in, may be null
* @return String with escaped values, null
if null string
* input
*/
public static String escapeJavaScript(String str) {
if (str == null) {
return null;
}
StringBuffer writer = new StringBuffer(str.length() * 2);
int sz = str.length();
for (int i = 0; i < sz; i++) {
char ch = str.charAt(i);
// handle unicode
if (ch > 0xfff) {
writer.append("\\u");
writer.append(hex(ch));
} else if (ch > 0xff) {
writer.append("\\u0");
writer.append(hex(ch));
} else if (ch > 0x7f) {
writer.append("\\u00");
writer.append(hex(ch));
} else if (ch < 32) {
switch (ch) {
case '\b':
writer.append('\\');
writer.append('b');
break;
case '\n':
writer.append('\\');
writer.append('n');
break;
case '\t':
writer.append('\\');
writer.append('t');
break;
case '\f':
writer.append('\\');
writer.append('f');
break;
case '\r':
writer.append('\\');
writer.append('r');
break;
default:
if (ch > 0xf) {
writer.append("\\u00");
writer.append(hex(ch));
} else {
writer.append("\\u000");
writer.append(hex(ch));
}
break;
}
} else {
switch (ch) {
case '\'':
// If we wanted to escape for Java strings then we would
// not need this next line.
writer.append('\\');
writer.append('\'');
break;
case '"':
writer.append('\\');
writer.append('"');
break;
case '\\':
writer.append('\\');
writer.append('\\');
break;
default:
writer.append(ch);
break;
}
}
}
return writer.toString();
}
/**
*
* Returns an upper case hexadecimal String
for the given
* character.
*
*
* @param ch
* The character to convert.
* @return An upper case hexadecimal String
*/
private static String hex(char ch) {
return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
}
/**
*
* Unescapes any JavaScript literals found in the String
.
*
*
* For example, it will turn a sequence of '\'
and
* 'n'
into a newline character, unless the '\'
is
* preceded by another '\'
.
*
*
* @param str
* the String
to unescape, may be null
* @return A new unescaped String
, null
if null
* string input
*/
public static String unescapeJavaScript(String str) {
if (str == null) {
return null;
}
StringBuffer writer = new StringBuffer(str.length());
int sz = str.length();
StringBuffer unicode = new StringBuffer(4);
boolean hadSlash = false;
boolean inUnicode = false;
for (int i = 0; i < sz; i++) {
char ch = str.charAt(i);
if (inUnicode) {
// if in unicode, then we're reading unicode
// values in somehow
unicode.append(ch);
if (unicode.length() == 4) {
// unicode now contains the four hex digits
// which represents our unicode chacater
try {
int value = Integer.parseInt(unicode.toString(), 16);
writer.append((char) value);
unicode.setLength(0);
inUnicode = false;
hadSlash = false;
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(
"Unable to parse unicode value: " + unicode
+ " cause: " + nfe);
}
}
continue;
}
if (hadSlash) {
// handle an escaped value
hadSlash = false;
switch (ch) {
case '\\':
writer.append('\\');
break;
case '\'':
writer.append('\'');
break;
case '\"':
writer.append('"');
break;
case 'r':
writer.append('\r');
break;
case 'f':
writer.append('\f');
break;
case 't':
writer.append('\t');
break;
case 'n':
writer.append('\n');
break;
case 'b':
writer.append('\b');
break;
case 'u':
// uh-oh, we're in unicode country....
inUnicode = true;
break;
default:
writer.append(ch);
break;
}
continue;
} else if (ch == '\\') {
hadSlash = true;
continue;
}
writer.append(ch);
}
if (hadSlash) {
// then we're in the weird case of a \ at the end of the
// string, let's output it anyway.
writer.append('\\');
}
return writer.toString();
}
/**
* Check to see if the given word is reserved or a bad idea in any known
* version of JavaScript.
*
* @param name
* The word to check
* @return false if the word is not reserved
*/
public static boolean isReservedWord(String name) {
return reserved.contains(name);
}
/**
* The array of javascript reserved words
*/
private static final String[] RESERVED_ARRAY = new String[] {
// Reserved and used at ECMAScript 4
"as", "break", "case", "catch", "class", "const", "continue",
"default", "delete", "do", "else", "export", "extends", "false",
"finally", "for", "function", "if", "import", "in", "instanceof",
"is", "namespace", "new", "null", "package", "private", "public",
"return", "super", "switch", "this", "throw", "true", "try",
"typeof", "use", "var", "void",
"while",
"with",
// Reserved for future use at ECMAScript 4
"abstract", "debugger", "enum", "goto", "implements", "interface",
"native", "protected", "synchronized", "throws", "transient",
"volatile",
// Reserved in ECMAScript 3, unreserved at 4 best to avoid anyway
"boolean", "byte", "char", "double", "final", "float", "int",
"long", "short", "static",
// I have seen the folowing list as 'best avoided for function names'
// but it seems way to all encompassing, so I'm not going to include it
/*
* "alert", "anchor", "area", "arguments", "array", "assign", "blur",
* "boolean", "button", "callee", "caller", "captureevents", "checkbox",
* "clearinterval", "cleartimeout", "close", "closed", "confirm",
* "constructor", "date", "defaultstatus", "document", "element", "escape",
* "eval", "fileupload", "find", "focus", "form", "frame", "frames",
* "getclass", "hidden", "history", "home", "image", "infinity",
* "innerheight", "isfinite", "innerwidth", "isnan", "java", "javaarray",
* "javaclass", "javaobject", "javapackage", "length", "link", "location",
* "locationbar", "math", "menubar", "mimetype", "moveby", "moveto", "name",
* "nan", "navigate", "navigator", "netscape", "number", "object", "onblur",
* "onerror", "onfocus", "onload", "onunload", "open", "opener", "option",
* "outerheight", "outerwidth", "packages", "pagexoffset", "pageyoffset",
* "parent", "parsefloat", "parseint", "password", "personalbar", "plugin",
* "print", "prompt", "prototype", "radio", "ref", "regexp",
* "releaseevents", "reset", "resizeby", "resizeto", "routeevent", "scroll",
* "scrollbars", "scrollby", "scrollto", "select", "self", "setinterval",
* "settimeout", "status", "statusbar", "stop", "string", "submit", "sun",
* "taint", "text", "textarea", "toolbar", "top", "tostring", "unescape",
* "untaint", "unwatch", "valueof", "watch", "window",
*/
};
private static SortedSet reserved = new TreeSet();
/**
* For easy access ...
*/
static {
// The Javascript reserved words array so we don't generate illegal
// javascript
reserved.addAll(Arrays.asList(RESERVED_ARRAY));
}
private static final String SPACE = " ";
/**
* How does a multi line comment start?
*/
private static final String COMMENT_ML_START = "/*";
/**
* How does a multi line comment end?
*/
private static final String COMMENT_ML_END = "*/";
/**
* How does a single line comment start?
*/
private static final String COMMENT_SL_START = "//";
/**
* Sometimes we need to retain the comment because it has special meaning
*/
private static final String COMMENT_RETAIN = "#DWR";
/**
* The log stream
*/
// private static final Logger log = Logger.getLogger(JavascriptUtil.class);
}