Author: nbelaevski
Date: 2008-09-20 12:06:31 -0400 (Sat, 20 Sep 2008)
New Revision: 10519
Modified:
trunk/framework/impl/src/main/java/org/ajax4jsf/css/CssCompressor.java
trunk/framework/impl/src/test/java/org/ajax4jsf/css/CssCompressorTest.java
Log:
https://jira.jboss.org/jira/browse/RF-4498
Modified: trunk/framework/impl/src/main/java/org/ajax4jsf/css/CssCompressor.java
===================================================================
--- trunk/framework/impl/src/main/java/org/ajax4jsf/css/CssCompressor.java 2008-09-20
00:03:56 UTC (rev 10518)
+++ trunk/framework/impl/src/main/java/org/ajax4jsf/css/CssCompressor.java 2008-09-20
16:06:31 UTC (rev 10519)
@@ -15,142 +15,180 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.ajax4jsf.resource.CountingOutputWriter;
-
public class CssCompressor {
- private StringBuffer srcsb = null;
+ private StringBuffer srcsb = null;
- public CssCompressor(StringBuffer buffer) throws IOException {
- srcsb = buffer;
- }
+ public CssCompressor(StringBuffer buffer) throws IOException {
+ srcsb = buffer;
+ }
- public int compress(Writer out, int linebreakpos)
- throws IOException {
-
- Pattern p;
- Matcher m;
- String css;
- StringBuffer sb;
- int startIndex, endIndex;
- int bytesLength;
+ public int compress(Writer out, int linebreakpos) throws IOException {
- // Remove all comment blocks...
- sb = new StringBuffer(srcsb.toString());
- while ((startIndex = sb.indexOf("/*")) >= 0) {
- endIndex = sb.indexOf("*/", startIndex + 2);
- if (endIndex >= startIndex + 2)
- sb.delete(startIndex, endIndex + 2);
- }
+ Pattern p;
+ Matcher m;
+ String css;
+ StringBuffer sb;
+ int startIndex, endIndex;
- css = sb.toString();
+ // 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;
+ }
+ }
+ }
- // Normalize all whitespace strings to single spaces. Easier to work with that
way.
- css = css.replaceAll("\\s+", " ");
+ css = sb.toString();
- // 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___", ":");
+ // Normalize all whitespace strings to single spaces. Easier to work
+ // with that way.
+ css = css.replaceAll("\\s+", " ");
- // Remove the spaces after the things that should not have spaces after them.
- css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
+ // Make a pseudo class for the Box Model Hack
+ css = css.replaceAll("\"\\\\\"}\\\\\"\"",
"___PSEUDOCLASSBMH___");
- // Add the semicolon where it's missing.
- css = css.replaceAll("([^;\\}])}", "$1;}");
+ // 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___", ":");
- // Replace 0(px,em,%) with 0.
- css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)",
"$1$2");
+ // Remove the spaces after the things that should not have spaces after
+ // them.
+ css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
- // 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;");
+ // Add the semicolon where it's missing.
+ css = css.replaceAll("([^;\\}])}", "$1;}");
- // Replace 0.6 to .6, but only when preceded by : or a white-space
- css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
+ // Replace 0(px,em,%) with 0.
+ css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)",
"$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();
+ // 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;");
- // 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();
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
- // Remove empty rules.
- css = css.replaceAll("[^\\}]+\\{;\\}", "");
+ // 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();
- 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;
- }
- }
+ // 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();
- css = sb.toString();
- }
+ // Remove empty rules.
+ css = css.replaceAll("[^\\}]+\\{;\\}", "");
- // Trim the final string (for any leading or trailing white spaces)
- css = css.trim();
- bytesLength = css.length(); // * CountingOutputWriter.sizeOfChar;
- // Write the output...
- out.write(css);
- return bytesLength;
- }
+ 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(";;+", ";");
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.trim();
+
+ // Write the output...
+ out.write(css);
+
+ return css.length();
+ }
}
Modified: trunk/framework/impl/src/test/java/org/ajax4jsf/css/CssCompressorTest.java
===================================================================
--- trunk/framework/impl/src/test/java/org/ajax4jsf/css/CssCompressorTest.java 2008-09-20
00:03:56 UTC (rev 10518)
+++ trunk/framework/impl/src/test/java/org/ajax4jsf/css/CssCompressorTest.java 2008-09-20
16:06:31 UTC (rev 10519)
@@ -7,6 +7,7 @@
package org.ajax4jsf.css;
import java.io.IOException;
+import java.io.StringWriter;
import junit.framework.TestCase;
@@ -81,7 +82,24 @@
}
+ public void testSequentialComments() throws Exception {
+ StringBuffer cssBuffer = new StringBuffer("/* copyright */ body { color: red; }
/* abc *//* cde */ html { color: red; } /* copyright end */");
+ CssCompressor compressor = new CssCompressor(cssBuffer);
+ StringWriter stringWriter = new StringWriter();
+ compressor.compress(stringWriter, -1);
+ stringWriter.close();
+ assertEquals("body{color:red;}html{color:red;}",
stringWriter.toString());
+ }
-
+ public void testFakeComment() throws Exception {
+ //this test won't go as our CSS compressor is not aware of possible /**/ in url
+
+ //StringBuffer cssBuffer = new StringBuffer("/* copyright */ body { /* my style
*/ background-image: url(/*/); color: red; background-image: url(/****/); } /* copyright
end */");
+ //CssCompressor compressor = new CssCompressor(cssBuffer);
+ //StringWriter stringWriter = new StringWriter();
+ //compressor.compress(stringWriter, -1);
+ //stringWriter.close();
+
//assertEquals("body{background-image:url(/*/);color:red;background-image:url(/****/);}",
stringWriter.toString());
+ }
}