Mega Code Archive

 
Categories / Java / Development Class
 

YUI CSS Compressor

//package org.zkoss.maven.yuicompressor; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.regex.Matcher; import java.util.regex.Pattern; /*  * YUI Compressor  * Author: Julien Lecomte <jlecomte@yahoo-inc.com>  * Copyright (c) 2007, Yahoo! Inc. All rights reserved.  * Code licensed under the BSD License:  *     http://developer.yahoo.net/yui/license.txt  *  * This code is a port of Isaac Schlueter's cssmin utility.  *  *----  * This code is a port of com.yahoo.platform.yui.compressor.CssCompressor  * we modify the version for zk css.dsp compress. (To avoid that $ make error happen.);  */ public class CssCompressor {   private StringBuffer srcsb = new StringBuffer();   public CssCompressor(Reader in) throws IOException {     // Read the stream...     int c;     while ((c = in.read()) != -1) {       srcsb.append((char) c);     }   }   public String getSrc(){     return srcsb.toString();   }   public void compress(Writer out, int linebreakpos) throws IOException {     Pattern p;     Matcher m;     String css;     StringBuffer sb;     int startIndex, endIndex;     // Remove all comment blocks...     startIndex = 0;     boolean iemac = false;     boolean preserve = false;     sb = new StringBuffer(srcsb.toString());     while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {       preserve = sb.length() > startIndex + 2 && sb.charAt(startIndex + 2) == '!';       endIndex = sb.indexOf("*/", startIndex + 2);       if (endIndex < 0) {         if (!preserve) {           sb.delete(startIndex, sb.length());         }       } else if (endIndex >= startIndex + 2) {         if (sb.charAt(endIndex - 1) == '\\') {           // Looks like a comment to hide rules from IE Mac.           // Leave this comment, and the following one, alone...           startIndex = endIndex + 2;           iemac = true;         } else if (iemac) {           startIndex = endIndex + 2;           iemac = false;         } else if (!preserve) {           sb.delete(startIndex, endIndex + 2);         } else {           startIndex = endIndex + 2;         }       }     }     css = sb.toString();     // Normalize all whitespace strings to single spaces. Easier to work     // with that way.     css = css.replaceAll("\\s+", " ");     // Make a pseudo class for the Box Model Hack     css = css.replaceAll("\"\\\\\"}\\\\\"\"", "___PSEUDOCLASSBMH___");     // ---------where zk modify it     sb = new StringBuffer();     p = Pattern.compile("\\$\\{([^\\}]+)\\}");     Matcher m1 = p.matcher(css);     while (m1.find()) {       String s1 = m1.group();       s1 = s1.replaceAll("\\$\\{", "__EL__");       s1 = s1.replaceAll(":", "__ELSP__");       s1 = s1.replaceAll("\\}", "__ELEND__");       m1.appendReplacement(sb, s1);     }     m1.appendTail(sb);     css = sb.toString();     // ---------where zk modify it----end     // Remove the spaces before the things that should not have spaces     // before them.     // But, be careful not to turn "p :link {...}" into "p:link{...}"     // Swap out any pseudo-class colons with the token, and then swap back.     sb = new StringBuffer();     p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");     m = p.matcher(css);     while (m.find()) {       String s = m.group();       s = s.replaceAll(":", "___PSEUDOCLASSCOLON___");       m.appendReplacement(sb, s);     }     m.appendTail(sb);     css = sb.toString();     css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");     css = css.replaceAll("___PSEUDOCLASSCOLON___", ":");     // Remove the spaces after the things that should not have spaces after     // them.     css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");     // Add the semicolon where it's missing.     css = css.replaceAll("([^;\\}])}", "$1;}");     // Replace 0(px,em,%) with 0.     css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");     // Replace 0 0 0 0; with 0.     css = css.replaceAll(":0 0 0 0;", ":0;");     css = css.replaceAll(":0 0 0;", ":0;");     css = css.replaceAll(":0 0;", ":0;");     // Replace background-position:0; with background-position:0 0;     css = css.replaceAll("background-position:0;", "background-position:0 0;");     // Replace 0.6 to .6, but only when preceded by : or a white-space     css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");     // Shorten colors from rgb(51,102,153) to #336699     // This makes it more likely that it'll get further compressed in the     // next step.     p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");     m = p.matcher(css);     sb = new StringBuffer();     while (m.find()) {       String[] rgbcolors = m.group(1).split(",");       StringBuffer hexcolor = new StringBuffer("#");       for (int i = 0; i < rgbcolors.length; i++) {         int val = Integer.parseInt(rgbcolors[i]);         if (val < 16) {           hexcolor.append("0");         }         hexcolor.append(Integer.toHexString(val));       }       m.appendReplacement(sb, hexcolor.toString());     }     m.appendTail(sb);     css = sb.toString();     // Shorten colors from #AABBCC to #ABC. Note that we want to make sure     // the color is not preceded by either ", " or =. Indeed, the property     // filter: chroma(color="#FFFFFF");     // would become     // filter: chroma(color="#FFF");     // which makes the filter break in IE.     p = Pattern         .compile("([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])");     m = p.matcher(css);     sb = new StringBuffer();     while (m.find()) {       // Test for AABBCC pattern       if (m.group(3).equalsIgnoreCase(m.group(4)) && m.group(5).equalsIgnoreCase(m.group(6))           && m.group(7).equalsIgnoreCase(m.group(8))) {         m.appendReplacement(sb, m.group(1) + m.group(2) + "#" + m.group(3) + m.group(5) + m.group(7));       } else {         m.appendReplacement(sb, m.group());       }     }     m.appendTail(sb);     css = sb.toString();     // Remove empty rules.     css = css.replaceAll("[^\\}]+\\{;\\}", "");     if (linebreakpos >= 0) {       // Some source control tools don't like it when files containing       // lines longer       // than, say 8000 characters, are checked in. The linebreak option       // is used in       // that case to split long lines after a specific column.       int i = 0;       int linestartpos = 0;       sb = new StringBuffer(css);       while (i < sb.length()) {         char c = sb.charAt(i++);         if (c == '}' && i - linestartpos > linebreakpos) {           sb.insert(i, '\n');           linestartpos = i;         }       }       css = sb.toString();     }     // Replace the pseudo class for the Box Model Hack     css = css.replaceAll("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"");     // Replace multiple semi-colons in a row by a single one     // See SF bug #1980989     css = css.replaceAll(";;+", ";");     // ---------where zk modify it     css = css.replaceAll("__EL__", "\\$\\{");     css = css.replaceAll("__ELSP__", ":");     css = css.replaceAll("__ELEND__", "\\}");     // ---------where zk modify it----end     // Trim the final string (for any leading or trailing white spaces)     css = css.trim();     // Write the output...     out.write(css);   } }