/* Copyright 2015 CrushPaper.com. This file is part of CrushPaper. CrushPaper is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU Affero General Public License as published by the Free Software Foundation. CrushPaper is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with CrushPaper. If not, see <http://www.gnu.org/licenses/>. */ package com.crushpaper; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /** * This minifier removes all comments preserving any embedded line feeds. The * intention is that the minified output preserves all line number to aid in * debugging. */ public class JsMinifier { /** This is the interface method of the class. */ public StringBuilder minify(InputStream inputStream) throws IOException { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(inputStream)); StringBuilder minified = new StringBuilder(); Boolean startOfLine = true; while (true) { int cInt = bufferedReader.read(); if (cInt == -1) { break; } char c = (char) cInt; if (c == '/') { int c1 = bufferedReader.read(); if (c1 == '/') { skipUntilEndOfSingleLineComment(bufferedReader, minified); startOfLine = true; } else if (c1 == '*') { skipUntilEndOfMultiLineComment(bufferedReader, minified); } else { minified.append(c); if (c1 != -1) { minified.append((char) c1); } } continue; } else if (c == '\'' || c == '"') { minified.append(c); includeUntilAfter(bufferedReader, minified, c); startOfLine = false; continue; } if (startOfLine && (c == ' ' || c == '\t')) { continue; } startOfLine = (c == '\n'); minified.append(c); } bufferedReader.close(); return minified; } /** * Appends every character up until the terminginated matching character * `end`. Understands escapes with '\'. */ private void includeUntilAfter(BufferedReader bufferedReader, StringBuilder minified, char end) throws IOException { char cPrevious = 0; boolean previousSet = false; while (true) { int cInt = bufferedReader.read(); if (cInt == -1) { return; } char c = (char) cInt; minified.append(c); if (c == end && (!previousSet || cPrevious != '\\')) { return; } previousSet = true; cPrevious = c; } } /** * Skips all characters until the end of the single line comment and appends * a line feed if one was found. */ private void skipUntilEndOfSingleLineComment(BufferedReader bufferedReader, StringBuilder minified) throws IOException { while (true) { int cInt = bufferedReader.read(); if (cInt == -1) { return; } char c = (char) cInt; if (c == '\n') { minified.append('\n'); return; } } } /** Skips all characters until the end of the multi line comment and. */ private void skipUntilEndOfMultiLineComment(BufferedReader bufferedReader, StringBuilder minified) throws IOException { char cPrevious = 0; boolean previousSet = false; while (true) { int cInt = bufferedReader.read(); if (cInt == -1) { return; } char c = (char) cInt; if (c == '/' && previousSet && cPrevious == '*') { return; } if (c == '\n') { minified.append('\n'); } previousSet = true; cPrevious = c; } } }