Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize memory usage in CssCompressor + API extensions #157

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 50 additions & 41 deletions src/com/yahoo/platform/yui/compressor/CssCompressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,37 @@
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.CharBuffer;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;

public class CssCompressor {

private StringBuffer srcsb = new StringBuffer();
private final CharSequence srcsb;

public CssCompressor(CharSequence in) {
this.srcsb = in;
}

public CssCompressor(Reader in) throws IOException {
// Read the stream...
int c;
while ((c = in.read()) != -1) {
srcsb.append((char) c);
final StringBuilder sb = new StringBuilder();
final CharBuffer buf = CharBuffer.allocate(2048);
while (in.read(buf) != -1) {
buf.flip();
sb.append(buf);
buf.clear();
}
srcsb = sb;
}

// Leave data urls alone to increase parse performance.
protected String extractDataUrls(String css, ArrayList preservedTokens) {
protected String extractDataUrls(String css, ArrayList<String> preservedTokens) {

int maxIndex = css.length() - 1;
int appendIndex = 0;

StringBuffer sb = new StringBuffer();
final StringBuilder sb = new StringBuilder(css.length());

Pattern p = Pattern.compile("(?i)url\\(\\s*([\"']?)data\\:");
Matcher m = p.matcher(css);
Expand Down Expand Up @@ -79,8 +87,7 @@ protected String extractDataUrls(String css, ArrayList preservedTokens) {
token = token.replaceAll("\\s+", "");
preservedTokens.add(token);

String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
sb.append(preserver);
sb.append("url(___YUICSSMIN_PRESERVED_TOKEN_").append(preservedTokens.size() - 1).append("___)");

appendIndex = endIndex + 1;
} else {
Expand All @@ -95,7 +102,7 @@ protected String extractDataUrls(String css, ArrayList preservedTokens) {
return sb.toString();
}

private String preserveOldIESpecificMatrixDefinition(String css, ArrayList preservedTokens) {
private String preserveOldIESpecificMatrixDefinition(String css, ArrayList<String> preservedTokens) {
StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile("\\s*filter:\\s*progid:DXImageTransform.Microsoft.Matrix\\(([^\\)]+)\\);");
Matcher m = p.matcher(css);
Expand All @@ -111,7 +118,11 @@ private String preserveOldIESpecificMatrixDefinition(String css, ArrayList prese

public void compress(Writer out, int linebreakpos)
throws IOException {
// Write the output...
out.write(compress(linebreakpos));
}

public String compress(int linebreakpos) {
Pattern p;
Matcher m;
String css = srcsb.toString();
Expand All @@ -120,15 +131,15 @@ public void compress(Writer out, int linebreakpos)
int endIndex = 0;
int i = 0;
int max = 0;
ArrayList preservedTokens = new ArrayList(0);
ArrayList comments = new ArrayList(0);
ArrayList<String> preservedTokens = new ArrayList<String>();
ArrayList<String> comments = new ArrayList<String>();
String token;
int totallen = css.length();
String placeholder;

css = this.extractDataUrls(css, preservedTokens);

StringBuffer sb = new StringBuffer(css);
final StringBuffer sb = new StringBuffer(css);

// collect all comment blocks...
while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
Expand All @@ -145,7 +156,7 @@ public void compress(Writer out, int linebreakpos)
css = sb.toString();

// preserve strings so their content doesn't get accidentally minified
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
m = p.matcher(css);
while (m.find()) {
Expand All @@ -157,7 +168,7 @@ public void compress(Writer out, int linebreakpos)
// one, maybe more? put'em back then
if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
for (i = 0, max = comments.size(); i < max; i += 1) {
token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i));
}
}

Expand All @@ -175,7 +186,7 @@ public void compress(Writer out, int linebreakpos)
// strings are safe, now wrestle the comments
for (i = 0, max = comments.size(); i < max; i += 1) {

token = comments.get(i).toString();
token = comments.get(i);
placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";

// ! in the first position of the comment means preserve
Expand Down Expand Up @@ -222,7 +233,7 @@ public void compress(Writer out, int linebreakpos)
// 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();
sb.setLength(0);
p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
m = p.matcher(css);
while (m.find()) {
Expand All @@ -241,7 +252,7 @@ public void compress(Writer out, int linebreakpos)
css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");

// retain space for special IE6 cases
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i):first\\-(line|letter)(\\{|,)");
m = p.matcher(css);
while (m.find()) {
Expand All @@ -254,7 +265,7 @@ public void compress(Writer out, int linebreakpos)
css = css.replaceAll("\\*/ ", "*/");

// If there are multiple @charset directives, push them to the top of the file.
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)");
m = p.matcher(css);
while (m.find()) {
Expand All @@ -264,7 +275,7 @@ public void compress(Writer out, int linebreakpos)
css = sb.toString();

// When all @charset are at the top, remove the second and after (as they are completely ignored).
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i)^((\\s*)(@charset)( [^;]+;\\s*))+");
m = p.matcher(css);
while (m.find()) {
Expand All @@ -274,38 +285,38 @@ public void compress(Writer out, int linebreakpos)
css = sb.toString();

// lowercase some popular @directives (@charset is done right above)
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i)@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, '@' + m.group(1).toLowerCase());
}
m.appendTail(sb);
css = sb.toString();

// lowercase some more common pseudo-elements
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i):(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, ':' + m.group(1).toLowerCase());
}
m.appendTail(sb);
css = sb.toString();

// lowercase some more common functions
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i):(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\\(");
m = p.matcher(css);
while (m.find()) {
m.appendReplacement(sb, ':' + m.group(1).toLowerCase() + '(');
}
m.appendTail(sb);
css = sb.toString();

// lower case some common function that can be values
// NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i)([:,\\( ]\\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)");
m = p.matcher(css);
while (m.find()) {
Expand Down Expand Up @@ -337,7 +348,7 @@ public void compress(Writer out, int linebreakpos)

// Replace background-position:0; with background-position:0 0;
// same for transform-origin
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i)(background-position|webkit-mask-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
m = p.matcher(css);
while (m.find()) {
Expand All @@ -353,10 +364,10 @@ public void compress(Writer out, int linebreakpos)
// 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();
sb.setLength(0);
while (m.find()) {
String[] rgbcolors = m.group(1).split(",");
StringBuffer hexcolor = new StringBuffer("#");
StringBuilder hexcolor = new StringBuilder("#");
for (i = 0; i < rgbcolors.length; i++) {
int val = Integer.parseInt(rgbcolors[i]);
if (val < 16) {
Expand Down Expand Up @@ -386,7 +397,7 @@ public void compress(Writer out, int linebreakpos)
p = Pattern.compile("(\\=\\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])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");

m = p.matcher(css);
sb = new StringBuffer();
sb.setLength(0);
int index = 0;

while (m.find(index)) {
Expand All @@ -397,19 +408,19 @@ public void compress(Writer out, int linebreakpos)

if (isFilter) {
// Restore, as is. Compression will break filters
sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
sb.append(m.group(1)).append("#").append(m.group(2)).append(m.group(3)).append(m.group(4)).append(m.group(5)).append(m.group(6)).append(m.group(7));
} else {
if( m.group(2).equalsIgnoreCase(m.group(3)) &&
m.group(4).equalsIgnoreCase(m.group(5)) &&
m.group(6).equalsIgnoreCase(m.group(7))) {

// #AABBCC pattern
sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
sb.append("#").append((m.group(3) + m.group(5) + m.group(7)).toLowerCase());

} else {

// Non-compressible color, restore, but lower case.
sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
sb.append("#").append((m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
}
}

Expand All @@ -432,7 +443,7 @@ public void compress(Writer out, int linebreakpos)
css = css.replaceAll("(:|\\s)(#800000)(;|})", "$1maroon$3");

// border: none -> border:0
sb = new StringBuffer();
sb.setLength(0);
p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|})");
m = p.matcher(css);
while (m.find()) {
Expand Down Expand Up @@ -462,7 +473,8 @@ public void compress(Writer out, int linebreakpos)
// that case to split long lines after a specific column.
i = 0;
int linestartpos = 0;
sb = new StringBuffer(css);
sb.setLength(0);
sb.append(css);
while (i < sb.length()) {
char c = sb.charAt(i++);
if (c == '}' && i - linestartpos > linebreakpos) {
Expand All @@ -480,13 +492,10 @@ public void compress(Writer out, int linebreakpos)

// restore preserved comments and strings
for(i = preservedTokens.size() - 1; i >= 0 ; i--) {
css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i));
}

// Trim the final string (for any leading or trailing white spaces)
css = css.trim();

// Write the output...
out.write(css);
return css.trim();
}
}