diff --git a/pom.xml b/pom.xml index d2e8f47..d443ed4 100644 --- a/pom.xml +++ b/pom.xml @@ -51,12 +51,6 @@ commons-lang3 3.4 - - com.google.javascript - closure-compiler - v20151015 - true - jar diff --git a/src/main/java/j2html/tags/InlineStaticResource.java b/src/main/java/j2html/tags/InlineStaticResource.java index fab7906..abe2f8c 100644 --- a/src/main/java/j2html/tags/InlineStaticResource.java +++ b/src/main/java/j2html/tags/InlineStaticResource.java @@ -14,7 +14,7 @@ public class InlineStaticResource { if(fileString != null) { switch(format) { case CSS_MIN : return style().with(unsafeHtml(compressCss(fileString))); - case JS_MIN : return script().with(unsafeHtml(compressJs(fileString, path))); + case JS_MIN : return script().with(unsafeHtml(compressJs(fileString))); case CSS : return style().with(unsafeHtml(fileString)); case JS : return script().with(unsafeHtml(fileString)); default : return errorAlert; @@ -35,8 +35,8 @@ public class InlineStaticResource { return CSSMin.compress(code); } - private static String compressJs(String code, String debugPath) { - return JSMin.compressJs(code, debugPath); + private static String compressJs(String code) { + return JSMin.compressJs(code); } } diff --git a/src/main/java/j2html/utils/JSMin.java b/src/main/java/j2html/utils/JSMin.java index 4d26763..9580b5b 100644 --- a/src/main/java/j2html/utils/JSMin.java +++ b/src/main/java/j2html/utils/JSMin.java @@ -1,32 +1,305 @@ package j2html.utils; +/* + * JSMin.java 2006-02-13 + * + * Copyright (c) 2006 John Reilly (www.inconspicuous.org) + * + * This work is a translation from C to Java of jsmin.c published by + * Douglas Crockford. Permission is hereby granted to use the Java + * version under the same conditions as the jsmin.c on which it is + * based. + * + * + * + * + * jsmin.c 2003-04-21 + * + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// package org.inconspicuous.jsmin; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; -import com.google.javascript.jscomp.*; -import com.google.javascript.jscomp.Compiler; public class JSMin { - - private JSMin() {} - public static String compressJs(String code, String sourcePath) { - return isPresent("com.google.javascript.jscomp.Compiler") ? compressJsUsingClosureCompiler(code, sourcePath) : code; - } - - private static boolean isPresent(String className) { + /** + * Compress a JS-string + * + * @param code the js-code you want to compress + * @return the compressed code + */ + public static String compressJs(String code) { + InputStream inStream = new ByteArrayInputStream(code.getBytes()); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + JSMin jsmin = new JSMin(inStream, outStream); try { - Class.forName(className); - return true; - } catch (Exception e) { - return false; + jsmin.jsmin(); + return outStream.toString().trim(); + } catch (IOException | UnterminatedRegExpLiteralException | UnterminatedCommentException | UnterminatedStringLiteralException e) { + e.printStackTrace(); + return ""; } } - - private static String compressJsUsingClosureCompiler(String code, String sourcePath) { - com.google.javascript.jscomp.Compiler compiler = new Compiler(); - CompilerOptions options = new CompilerOptions(); - CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options); - SourceFile extern = SourceFile.fromCode("externs.js", ""); - SourceFile input = SourceFile.fromCode(sourcePath, code); - compiler.compile(extern, input, options); - return compiler.toSource(); + + private static final int EOF = -1; + + private PushbackInputStream in; + private OutputStream out; + + private int theA; + private int theB; + + private JSMin(InputStream in, OutputStream out) { + this.in = new PushbackInputStream(in); + this.out = out; } -} + + /** + * isAlphanum -- return true if the character is a letter, digit, + * underscore, dollar sign, or non-ASCII character. + */ + private static boolean isAlphanum(int c) { + return ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + c == '_' || + c == '$' || + c == '\\' || + c > 126); + } + + /** + * get -- return the next character from stdin. Watch out for lookahead. If + * the character is a control character, translate it to a space or + * linefeed. + */ + private int get() throws IOException { + int c = in.read(); + + if (c >= ' ' || c == '\n' || c == EOF) { + return c; + } + + if (c == '\r') { + return '\n'; + } + + return ' '; + } + + + /** + * Get the next character without getting it. + */ + private int peek() throws IOException { + int lookaheadChar = in.read(); + in.unread(lookaheadChar); + return lookaheadChar; + } + + /** + * next -- get the next character, excluding comments. peek() is used to see + * if a '/' is followed by a '/' or '*'. + */ + private int next() throws IOException, UnterminatedCommentException { + int c = get(); + if (c == '/') { + switch (peek()) { + case '/': + for (; ; ) { + c = get(); + if (c <= '\n') { + return c; + } + } + + case '*': + get(); + for (; ; ) { + switch (get()) { + case '*': + if (peek() == '/') { + get(); + return ' '; + } + break; + case EOF: + throw new UnterminatedCommentException(); + } + } + + default: + return c; + } + + } + return c; + } + + /** + * action -- do something! What you do is determined by the argument: 1 + * Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B. + * (Delete A). 3 Get the next B. (Delete B). action treats a string as a + * single character. Wow! action recognizes a regular expression if it is + * preceded by ( or , or =. + */ + + private void action(int d) throws IOException, UnterminatedRegExpLiteralException, + UnterminatedCommentException, UnterminatedStringLiteralException { + switch (d) { + case 1: + out.write(theA); + case 2: + theA = theB; + + if (theA == '\'' || theA == '"') { + for (; ; ) { + out.write(theA); + theA = get(); + if (theA == theB) { + break; + } + if (theA <= '\n') { + throw new UnterminatedStringLiteralException(); + } + if (theA == '\\') { + out.write(theA); + theA = get(); + } + } + } + + case 3: + theB = next(); + if (theB == '/' && (theA == '(' || theA == ',' || theA == '=')) { + out.write(theA); + out.write(theB); + for (; ; ) { + theA = get(); + if (theA == '/') { + break; + } else if (theA == '\\') { + out.write(theA); + theA = get(); + } else if (theA <= '\n') { + throw new UnterminatedRegExpLiteralException(); + } + out.write(theA); + } + theB = next(); + } + } + } + + /** + * jsmin -- Copy the input to the output, deleting the characters which are + * insignificant to JavaScript. Comments will be removed. Tabs will be + * replaced with spaces. Carriage returns will be replaced with linefeeds. + * Most spaces and linefeeds will be removed. + */ + public void jsmin() throws IOException, UnterminatedRegExpLiteralException, UnterminatedCommentException, UnterminatedStringLiteralException { + theA = '\n'; + action(3); + while (theA != EOF) { + switch (theA) { + case ' ': + if (isAlphanum(theB)) { + action(1); + } else { + action(2); + } + break; + case '\n': + switch (theB) { + case '{': + case '[': + case '(': + case '+': + case '-': + action(1); + break; + case ' ': + action(3); + break; + default: + if (isAlphanum(theB)) { + action(1); + } else { + action(2); + } + } + break; + default: + switch (theB) { + case ' ': + if (isAlphanum(theA)) { + action(1); + break; + } + action(3); + break; + case '\n': + switch (theA) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case '\'': + action(1); + break; + default: + if (isAlphanum(theA)) { + action(1); + } else { + action(3); + } + } + break; + default: + action(1); + break; + } + } + } + out.flush(); + } + + private class UnterminatedCommentException extends Exception { + } + + private class UnterminatedStringLiteralException extends Exception { + } + + private class UnterminatedRegExpLiteralException extends Exception { + } + +} \ No newline at end of file diff --git a/src/test/java/j2html/tags/TagCreatorTest.java b/src/test/java/j2html/tags/TagCreatorTest.java index a8bb25d..83e2bf7 100644 --- a/src/test/java/j2html/tags/TagCreatorTest.java +++ b/src/test/java/j2html/tags/TagCreatorTest.java @@ -70,7 +70,7 @@ public class TagCreatorTest { assertEquals(text("\"").render(), "<script> and "</script>""); assertEquals(unsafeHtml(""); + assertEquals(scriptWithInlineFile_min("/test.js").render(), ""); assertEquals(fileAsString("/test.html").render(), "" + EOL + " Any content" + EOL + "" + EOL); assertEquals(fileAsEscapedString("/test.html").render(), "<body>" + EOL + " Any content" + EOL + "</body>" + EOL); assertEquals(fileAsString("/AnyContent.java").render(), "public class AnyContent{}" + EOL);