All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.simschla.minify.css.CssMin Maven / Gradle / Ivy

package ch.simschla.minify.css;

import ch.simschla.minify.header.CustomHeaderWriter;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

import static ch.simschla.minify.io.IoConstants.EOF;
import static ch.simschla.minify.precondition.Preconditions.checkNotNull;

/**
 * This is a java port of Ryan Day's cssmin utility.
 *
 * Currently implemented Version: 95f998395f35ddccd7babd4256ce2d3dbc9f6fb5
 */
public final class CssMin {

	private final InputStream inputStream;
	private final OutputStream outputStream;

	private final CustomHeaderWriter headerWriter;
	private final String customHeader;

	private static enum State {
		STATE_FREE, STATE_ATRULE, STATE_SELECTOR, STATE_BLOCK, STATE_DECLARATION, STATE_COMMENT;
	}

	private int theLookahead = EOF;

	private State tmp_state;

	private State state = State.STATE_FREE;

	private State last_state = State.STATE_FREE;

	private boolean in_paren = false;

	private CssMin(Builder builder) {
		this.inputStream = builder.inputStream();
		this.outputStream = builder.outputStream();
		this.customHeader = builder.customHeader();
		this.headerWriter =
				CustomHeaderWriter.builder()
						.charset(builder.charset())
						.outputStream(builder.outputStream())
						.commentLinePrefix("/* ")
						.commentLinePostfix(" */")
						.build();
	}

	public void minify() {
		try {
			writeCustomHeader();
			cssmin();
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				outputStream.flush();
			} catch (IOException e) {
				//ignore silently
			}
		}
	}

	private void writeCustomHeader() {
		this.headerWriter.writeHeader(this.customHeader);
	}

	/* cssmin -- minify the css
	removes comments
	removes newlines and line feeds keeping
	removes last semicolon from last property
*/
	private void cssmin() throws IOException {
		for (;;) {
			int c = get();

			if (c == EOF) {
				return;
			}

			c = machine(c);

			if (c != 0) {
				outputStream.write(c);
			}
		}
	}

	/* 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 = theLookahead;
		theLookahead = EOF;
		if (c == EOF) {
			c = inputStream.read();
		}

		if (c >= ' ' || c == '\n' || c == EOF) {
			return c;
		}

		if (c == '\r') {
			return '\n';
		}

		return ' ';
	}

	/* peek -- get the next character without getting it.
*/

	private int peek() throws IOException {
		theLookahead = get();
		return theLookahead;
	}

	/* machine

*/
	private int machine(int c) throws IOException {

		if(state != State.STATE_COMMENT){
			if(c == '/' && peek() == '*'){
				tmp_state = state;
				state = State.STATE_COMMENT;
			}
		}

		switch (state){
			case STATE_FREE:
				if (c == ' ' && c == '\n' ) {
					c = 0;
				} else if (c == '@'){
					state = State.STATE_ATRULE;
					break;
				} else if(c > 0){
					//fprintf(stdout,"one to 3 - %c %i",c,c);
					state = State.STATE_SELECTOR;
				}
			case STATE_SELECTOR:
				if (c == '{') {
					state = State.STATE_BLOCK;
				} else if(c == '\n') {
					c = 0;
				} else if(c == '@'){
					state = State.STATE_ATRULE;
				} else if (c == ' ' && peek() == '{') {
					c = 0;
				}
				break;
			case STATE_ATRULE:
			/* support
				@import etc.
				@font-face{
			*/
				if (c == '\n' || c == ';') {
					c = ';';
					state = State.STATE_FREE;
				} else if(c == '{') {
					state = State.STATE_BLOCK;
				}
				break;
			case STATE_BLOCK:
				if (c == ' ' || c == '\n' ) {
					c = 0;
					break;
				} else if (c == '}') {
					state = State.STATE_FREE;
					//fprintf(stdout,"closing bracket found in block\n");
					break;
				} else {
					state = State.STATE_DECLARATION;
				}
			case STATE_DECLARATION:
				//support in paren because data can uris have ;
				if(c == '('){
					in_paren = true;
				}
				if(!in_paren){

					if( c == ';') {
						state = State.STATE_BLOCK;
						//could continue peeking through white space..
						if(peek() == '}'){
							c = 0;
						}
					} else if (c == '}') {
						//handle unterminated declaration
						state = State.STATE_FREE;
					} else if ( c == '\n') {
						//skip new lines
						c = 0;
					} else if (c == ' ' ) {
						//skip multiple spaces after each other
						if( peek() == c ) {
							c = 0;
						}
					}

				} else if (c == ')') {
					in_paren = false;
				}

				break;
			case STATE_COMMENT:
				if(c == '*' && peek() == '/'){
					theLookahead = EOF;
					state = tmp_state;
				}
				c = 0;
				break;
		}

		return c;
	}

	public static Builder builder() {
		return new Builder();
	}

	//--- inner classes

	public static final class Builder {
		private InputStream inputStream = System.in;
		private OutputStream outputStream = System.out;

		private String customHeader = "";

		private Charset charset = Charset.forName("UTF-8");

		public InputStream inputStream() {
			return this.inputStream;
		}

		public OutputStream outputStream() {
			return this.outputStream;
		}

		public Builder inputStream(final InputStream inputStream) {
			this.inputStream = checkNotNull(inputStream);
			return this;
		}

		public Builder outputStream(final OutputStream outputStream) {
			this.outputStream = checkNotNull(outputStream);
			return this;
		}

		public String customHeader() {
			return this.customHeader;
		}

		public Builder customHeader(final String customHeader) {
			this.customHeader = checkNotNull(customHeader);
			return this;
		}

		public Charset charset() {
			return this.charset;
		}

		public Builder charset(final Charset charset) {
			this.charset = checkNotNull(charset);
			return this;
		}


		public CssMin build() {
			return new CssMin(this);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy