summaryrefslogtreecommitdiff
path: root/inc/lib/minify/Minify/YUI/CssCompressor.java
diff options
context:
space:
mode:
Diffstat (limited to 'inc/lib/minify/Minify/YUI/CssCompressor.java')
-rwxr-xr-xinc/lib/minify/Minify/YUI/CssCompressor.java382
1 files changed, 382 insertions, 0 deletions
diff --git a/inc/lib/minify/Minify/YUI/CssCompressor.java b/inc/lib/minify/Minify/YUI/CssCompressor.java
new file mode 100755
index 00000000..3fb497a9
--- /dev/null
+++ b/inc/lib/minify/Minify/YUI/CssCompressor.java
@@ -0,0 +1,382 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Author: Isaac Schlueter - http://foohack.com/
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.ArrayList;
+
+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);
+ }
+ }
+
+ // Leave data urls alone to increase parse performance.
+ protected String extractDataUrls(String css, ArrayList preservedTokens) {
+
+ int maxIndex = css.length() - 1;
+ int appendIndex = 0;
+
+ StringBuffer sb = new StringBuffer();
+
+ Pattern p = Pattern.compile("url\\(\\s*([\"']?)data\\:");
+ 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() + 4; // "url(".length()
+ String terminator = m.group(1); // ', " or empty (not quoted)
+
+ if (terminator.length() == 0) {
+ terminator = ")";
+ }
+
+ boolean foundTerminator = false;
+
+ int endIndex = m.end() - 1;
+ while(foundTerminator == false && endIndex+1 <= maxIndex) {
+ endIndex = css.indexOf(terminator, endIndex+1);
+
+ if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
+ foundTerminator = true;
+ if (!")".equals(terminator)) {
+ endIndex = css.indexOf(")", endIndex);
+ }
+ }
+ }
+
+ // Enough searching, start moving stuff over to the buffer
+ sb.append(css.substring(appendIndex, m.start()));
+
+ if (foundTerminator) {
+ String token = css.substring(startIndex, endIndex);
+ token = token.replaceAll("\\s+", "");
+ preservedTokens.add(token);
+
+ String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
+ sb.append(preserver);
+
+ appendIndex = endIndex + 1;
+ } else {
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
+ sb.append(css.substring(m.start(), m.end()));
+ appendIndex = m.end();
+ }
+ }
+
+ sb.append(css.substring(appendIndex));
+
+ return sb.toString();
+ }
+
+ public void compress(Writer out, int linebreakpos)
+ throws IOException {
+
+ Pattern p;
+ Matcher m;
+ String css = srcsb.toString();
+
+ int startIndex = 0;
+ int endIndex = 0;
+ int i = 0;
+ int max = 0;
+ ArrayList preservedTokens = new ArrayList(0);
+ ArrayList comments = new ArrayList(0);
+ String token;
+ int totallen = css.length();
+ String placeholder;
+
+ css = this.extractDataUrls(css, preservedTokens);
+
+ StringBuffer sb = new StringBuffer(css);
+
+ // collect all comment blocks...
+ while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
+ endIndex = sb.indexOf("*/", startIndex + 2);
+ if (endIndex < 0) {
+ endIndex = totallen;
+ }
+
+ token = sb.substring(startIndex + 2, endIndex);
+ comments.add(token);
+ sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
+ startIndex += 2;
+ }
+ css = sb.toString();
+
+ // preserve strings so their content doesn't get accidentally minified
+ sb = new StringBuffer();
+ p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
+ m = p.matcher(css);
+ while (m.find()) {
+ token = m.group();
+ char quote = token.charAt(0);
+ token = token.substring(1, token.length() - 1);
+
+ // maybe the string contains a comment-like substring?
+ // 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());
+ }
+ }
+
+ // minify alpha opacity in filter strings
+ token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ preservedTokens.add(token);
+ String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
+ m.appendReplacement(sb, preserver);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+
+ // strings are safe, now wrestle the comments
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+
+ token = comments.get(i).toString();
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+ // ! in the first position of the comment means preserve
+ // so push to the preserved tokens while stripping the !
+ if (token.startsWith("!")) {
+ preservedTokens.add(token);
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // \ in the last position looks like hack for Mac/IE5
+ // shorten that to /*\*/ and the next one to /**/
+ if (token.endsWith("\\")) {
+ preservedTokens.add("\\");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ i = i + 1; // attn: advancing the loop
+ preservedTokens.add("");
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // keep empty comments after child selectors (IE7 hack)
+ // e.g. html >/**/ body
+ if (token.length() == 0) {
+ startIndex = css.indexOf(placeholder);
+ if (startIndex > 2) {
+ if (css.charAt(startIndex - 3) == '>') {
+ preservedTokens.add("");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ }
+ }
+ }
+
+ // in all other cases kill the comment
+ css = css.replace("/*" + placeholder + "*/", "");
+ }
+
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ css = css.replaceAll("\\s+", " ");
+
+ // 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(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
+ s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
+ m.appendReplacement(sb, s);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+ // Remove spaces before the things that should not have spaces before them.
+ css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
+ // bring back the colon
+ css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
+
+ // retain space for special IE6 cases
+ css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
+
+ // no space after the end of a preserved comment
+ css = css.replaceAll("\\*/ ", "*/");
+
+ // If there is a @charset, then only allow one, and push to the top of the file.
+ css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
+ css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
+
+ // Put the space back in some cases, to support stuff like
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
+ css = css.replaceAll("\\band\\(", "and (");
+
+ // Remove the spaces after the things that should not have spaces after them.
+ css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
+
+ // remove unnecessary semicolons
+ css = css.replaceAll(";+}", "}");
+
+ // 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$1");
+ css = css.replaceAll(":0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0(;|})", ":0$1");
+
+
+ // Replace background-position:0; with background-position:0 0;
+ // same for transform-origin
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // 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 (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.
+ // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
+ // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
+ 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();
+ int index = 0;
+
+ while (m.find(index)) {
+
+ sb.append(css.substring(index, m.start()));
+
+ boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
+
+ 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));
+ } 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());
+
+ } 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());
+ }
+ }
+
+ index = m.end(7);
+ }
+
+ sb.append(css.substring(index));
+ css = sb.toString();
+
+ // border: none -> border:0
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // shorter opacity IE filter
+ css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ // Remove empty rules.
+ css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
+
+ // TODO: Should this be after we re-insert tokens. These could alter the break points. However then
+ // we'd need to make sure we don't break in the middle of a string etc.
+ 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.
+ 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 multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ css = css.replaceAll(";;+", ";");
+
+ // restore preserved comments and strings
+ for(i = 0, max = preservedTokens.size(); i < max; i++) {
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
+ }
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.trim();
+
+ // Write the output...
+ out.write(css);
+ }
+}