diff --git a/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java b/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java index be124fc2b2..673646b9fb 100644 --- a/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java +++ b/tapestry-webresources/src/main/java/com/yahoo/platform/yui/compressor/CssCompressor.java @@ -14,9 +14,9 @@ import java.io.IOException; import java.io.Reader; import java.io.Writer; -import java.util.ArrayList; -import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; public class CssCompressor { @@ -30,28 +30,33 @@ public CssCompressor(Reader in) throws IOException { } } - // Leave data urls alone to increase parse performance. - protected String extractDataUrls(String css, ArrayList preservedTokens) { + /** + * @param css - full css string + * @param preservedToken - token to preserve + * @param tokenRegex - regex to find token + * @param removeWhiteSpace - remove any white space in the token + * @param preservedTokens - array of token values + * @return + */ + protected String preserveToken(String css, String preservedToken, + String tokenRegex, boolean removeWhiteSpace, ArrayList preservedTokens) { int maxIndex = css.length() - 1; int appendIndex = 0; StringBuffer sb = new StringBuffer(); - Pattern p = Pattern.compile("(?i)url\\(\\s*([\"']?)data\\:"); + Pattern p = Pattern.compile(tokenRegex); Matcher m = p.matcher(css); - /* - * Since we need to account for non-base64 data urls, we need to handle - * ' and ) being part of the data string. Hence switching to indexOf, - * to determine whether or not we have matching string terminators and - * handling sb appends directly, instead of using matcher.append* methods. - */ - while (m.find()) { + int startIndex = m.start() + (preservedToken.length() + 1); + String terminator = m.group(1); - int startIndex = m.start() + 4; // "url(".length() - String terminator = m.group(1); // ', " or empty (not quoted) + // skip this, if CSS was already copied to "sb" upto this position + if (m.start() < appendIndex) { + continue; + } if (terminator.length() == 0) { terminator = ")"; @@ -63,7 +68,9 @@ protected String extractDataUrls(String css, ArrayList preservedTokens) { while(foundTerminator == false && endIndex+1 <= maxIndex) { endIndex = css.indexOf(terminator, endIndex+1); - if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) { + if (endIndex <= 0) { + break; + } else if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) { foundTerminator = true; if (!")".equals(terminator)) { endIndex = css.indexOf(")", endIndex); @@ -76,10 +83,11 @@ protected String extractDataUrls(String css, ArrayList preservedTokens) { if (foundTerminator) { String token = css.substring(startIndex, endIndex); - token = token.replaceAll("\\s+", ""); + if(removeWhiteSpace) + token = token.replaceAll("\\s+", ""); preservedTokens.add(token); - String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)"; + String preserver = preservedToken + "(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)"; sb.append(preserver); appendIndex = endIndex + 1; @@ -95,20 +103,6 @@ protected String extractDataUrls(String css, ArrayList preservedTokens) { return sb.toString(); } - private String preserveOldIESpecificMatrixDefinition(String css, ArrayList preservedTokens) { - StringBuffer sb = new StringBuffer(); - Pattern p = Pattern.compile("\\s*filter:\\s*progid:DXImageTransform.Microsoft.Matrix\\(([^\\)]+)\\);"); - Matcher m = p.matcher(css); - while (m.find()) { - String token = m.group(1); - preservedTokens.add(token); - String preserver = "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"; - m.appendReplacement(sb, "filter:progid:DXImageTransform.Microsoft.Matrix(" + preserver + ");"); - } - m.appendTail(sb); - return sb.toString(); - } - public void compress(Writer out, int linebreakpos) throws IOException { @@ -126,7 +120,6 @@ public void compress(Writer out, int linebreakpos) int totallen = css.length(); String placeholder; - css = this.extractDataUrls(css, preservedTokens); StringBuffer sb = new StringBuffer(css); @@ -144,6 +137,12 @@ public void compress(Writer out, int linebreakpos) } css = sb.toString(); + + css = this.preserveToken(css, "url", "(?i)url\\(\\s*([\"']?)data\\:", true, preservedTokens); + css = this.preserveToken(css, "calc", "(?i)calc\\(\\s*([\"']?)", false, preservedTokens); + css = this.preserveToken(css, "progid:DXImageTransform.Microsoft.Matrix", "(?i)progid:DXImageTransform.Microsoft.Matrix\\s*([\"']?)", false, preservedTokens); + + // preserve strings so their content doesn't get accidentally minified sb = new StringBuffer(); p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')"); @@ -213,17 +212,21 @@ public void compress(Writer out, int linebreakpos) css = css.replace("/*" + placeholder + "*/", ""); } - + // preserve \9 IE hack + final String backslash9 = "\\9"; + while (css.indexOf(backslash9) > -1) { + preservedTokens.add(backslash9); + css = css.replace(backslash9, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + } + // Normalize all whitespace strings to single spaces. Easier to work with that way. css = css.replaceAll("\\s+", " "); - css = this.preserveOldIESpecificMatrixDefinition(css, preservedTokens); - // 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("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)"); + p = Pattern.compile("(^|\\})((^|([^\\{:])+):)+([^\\{]*\\{)"); m = p.matcher(css); while (m.find()) { String s = m.group(); @@ -258,7 +261,8 @@ public void compress(Writer out, int linebreakpos) p = Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)"); m = p.matcher(css); while (m.find()) { - m.appendReplacement(sb, m.group(2).toLowerCase() + m.group(3) + m.group(1)); + String s = m.group(1).replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\\\$"); + m.appendReplacement(sb, m.group(2).toLowerCase() + m.group(3) + s); } m.appendTail(sb); css = sb.toString(); @@ -325,12 +329,29 @@ public void compress(Writer out, int linebreakpos) css = css.replaceAll(";+}", "}"); // Replace 0(px,em,%) with 0. - css = css.replaceAll("(?i)(^|[^0-9])(?:0?\\.)?0(?:px|em|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)", "$10"); + String oldCss; + p = Pattern.compile("(?i)(^|: ?)((?:[0-9a-z-.]+ )*?)?(?:0?\\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)"); + do { + oldCss = css; + m = p.matcher(css); + css = m.replaceAll("$1$20"); + } while (!(css.equals(oldCss))); + + // Replace 0(px,em,%) with 0 inside groups (e.g. -MOZ-RADIAL-GRADIENT(CENTER 45DEG, CIRCLE CLOSEST-SIDE, ORANGE 0%, RED 100%)) + p = Pattern.compile("(?i)\\( ?((?:[0-9a-z-.]+[ ,])*)?(?:0?\\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)"); + do { + oldCss = css; + m = p.matcher(css); + css = m.replaceAll("($10"); + } while (!(css.equals(oldCss))); + + // Replace x.0(px,em,%) with x(px,em,%). + css = css.replaceAll("([0-9])\\.0(px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz| |;)", "$1$2"); // Replace 0 0 0 0; with 0. css = css.replaceAll(":0 0 0 0(;|})", ":0$1"); css = css.replaceAll(":0 0 0(;|})", ":0$1"); - css = css.replaceAll(":0 0(;|})", ":0$1"); + css = css.replaceAll("(?= 0 ; i--) { + for(i = 0, max = preservedTokens.size(); i < max; i++) { css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString()); } + + // Add spaces back in between operators for css calc function + // https://developer.mozilla.org/en-US/docs/Web/CSS/calc + // Added by Eric Arnol-Martin (earnolmartin@gmail.com) + sb = new StringBuffer(); + p = Pattern.compile("calc\\([^\\)]*\\)"); + m = p.matcher(css); + while (m.find()) { + String s = m.group(); + + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\+", " + "); + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\-", " - "); + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\*", " * "); + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\/", " / "); + + m.appendReplacement(sb, s); + } + m.appendTail(sb); + css = sb.toString(); // Trim the final string (for any leading or trailing white spaces) css = css.trim(); @@ -487,4 +527,4 @@ public void compress(Writer out, int linebreakpos) // Write the output... out.write(css); } -} \ No newline at end of file +}