forked from nzakas/cssembed
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding CssTransformer - a metaphor to transform input to output css w…
…ith the help of a Transform a) CssTransformer parses the input css and generates CssToken's. BackgroundImage is one such token b) These tokens are then sent to a Transform implementation. c) Adding several unit tests to verify css parsing Yet to add concrete implementations of Transform
- Loading branch information
1 parent
0aa75e0
commit 58fa43d
Showing
8 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/main/java/net/nczonline/web/cssembed/BackgroundImage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,40 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
public class BackgroundImage implements CssToken { | |||
private final String url; | |||
|
|||
BackgroundImage(String url) { | |||
this.url = url; | |||
} | |||
public String getUrl() { | |||
return url; | |||
} | |||
|
|||
@Override | |||
public int hashCode() { | |||
final int prime = 31; | |||
int result = 1; | |||
result = prime * result + ((url == null) ? 0 : url.hashCode()); | |||
return result; | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (this == obj) | |||
return true; | |||
if (obj == null) | |||
return false; | |||
if (getClass() != obj.getClass()) | |||
return false; | |||
BackgroundImage other = (BackgroundImage) obj; | |||
if (url == null) { | |||
if (other.url != null) | |||
return false; | |||
} else if (!url.equals(other.url)) | |||
return false; | |||
return true; | |||
} | |||
|
|||
public String toCss() { | |||
return " background-image:url(\"" + url + "\"); "; | |||
} | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,5 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
public interface CssToken { | |||
public String toCss(); | |||
} |
83 changes: 83 additions & 0 deletions
83
src/main/java/net/nczonline/web/cssembed/CssTransformer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,83 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
import java.io.IOException; | |||
import java.io.Reader; | |||
import java.io.Writer; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
|
|||
import org.apache.commons.io.IOUtils; | |||
|
|||
public class CssTransformer { | |||
|
|||
private static final String URL_PATTERN_REGEX = "([\\\\/\\.a-zA-Z0-9%\\?\\-_:=&]+)"; | |||
private static final String WHITE_SPACE = "\\s*"; | |||
private static final String OPTIONAL_QUOTE = "(['\"]{0,1})"; | |||
private static final String MATCH_OPENING_QUOTE = "\\1"; | |||
private static final String CASE_INSENSITIVE = "(?i)"; | |||
private static final String OPTIONAL_SEMI_COLON = "[;]{0,1}"; | |||
|
|||
static final int URL_GROUP = 2; | |||
static final Pattern BACKGROUND_IMAGE = getBackgroundImagePattern(); | |||
|
|||
|
|||
static final Pattern getBackgroundImagePattern() { | |||
StringBuilder regex = new StringBuilder(); | |||
regex | |||
.append(CASE_INSENSITIVE) | |||
.append(rightWhiteSpace("background-image")) | |||
.append(rightWhiteSpace(":")) | |||
.append(rightWhiteSpace("url")) | |||
.append(rightWhiteSpace("\\(")) | |||
.append(OPTIONAL_QUOTE) | |||
.append(URL_PATTERN_REGEX) | |||
.append(MATCH_OPENING_QUOTE) | |||
.append(leftWhiteSpace("\\)")) | |||
.append(leftWhiteSpace(OPTIONAL_SEMI_COLON)); | |||
|
|||
return Pattern.compile(regex.toString()); | |||
} | |||
|
|||
private static String leftWhiteSpace(String literal) { | |||
return WHITE_SPACE + literal; | |||
} | |||
|
|||
private static String rightWhiteSpace(String literal) { | |||
return literal + WHITE_SPACE; | |||
} | |||
|
|||
/** | |||
* Reads a CSS file from input, transforms it according to transformation, and then writes the transformed css to output | |||
* | |||
* This method parses the input files into 'Tokens'. For each token, the appropriate transform() method | |||
* is called on the transformation. The output of the transformation is written to the output <em>instead of</em> the token | |||
* | |||
* | |||
* @param input the source css file | |||
* @param t the transformation to apply on the CSS | |||
* @param output the output css file | |||
* @throws IOException If the input can't be read, or the output can't be written for some reason | |||
*/ | |||
public void transform(Reader input, Transform t, Writer output) throws IOException { | |||
String source = IOUtils.toString(input); | |||
|
|||
source = t.preTransform(source); | |||
|
|||
/* | |||
* At present, we only generate background image tokens | |||
*/ | |||
Matcher m = BACKGROUND_IMAGE.matcher(source); | |||
StringBuffer sb = new StringBuffer(); | |||
while(m.find()) { | |||
|
|||
String url = m.group(URL_GROUP); | |||
BackgroundImage image = new BackgroundImage(url); | |||
String replacement = t.transform(image); | |||
m.appendReplacement(sb, replacement); | |||
} | |||
m.appendTail(sb); | |||
|
|||
String finalOutput = t.postTransform(sb.toString()); | |||
IOUtils.write(finalOutput, output); | |||
} | |||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/net/nczonline/web/cssembed/DefaultTransform.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,23 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
public class DefaultTransform implements Transform { | |||
|
|||
public String preTransform(String source) { | |||
return source; | |||
} | |||
|
|||
public String transform(CssToken token) { | |||
if(token instanceof BackgroundImage) { | |||
transform((BackgroundImage)token); | |||
} | |||
throw new IllegalArgumentException("Unknown token type - " + token.getClass()); | |||
} | |||
|
|||
protected String transform(BackgroundImage image) { | |||
return image.toCss(); | |||
} | |||
|
|||
public String postTransform(String source) { | |||
return source; | |||
} | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,11 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
public interface Transform { | |||
|
|||
String preTransform(String source); | |||
|
|||
String transform(CssToken token); | |||
|
|||
String postTransform(String source); | |||
|
|||
} |
78 changes: 78 additions & 0 deletions
78
src/test/java/net/nczonline/web/cssembed/BackgroundImageRegexTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,78 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
import java.util.regex.Matcher; | |||
|
|||
import org.junit.Test; | |||
import static junit.framework.Assert.*; | |||
|
|||
public class BackgroundImageRegexTest { | |||
|
|||
@Test | |||
public void testRegex() { | |||
RegexTest[] positive_matches = { | |||
new RegexTest("No quotes", "background-image:url(image.png)", "image.png"), | |||
new RegexTest("Single quotes", "background-image:url('image.png')", "image.png"), | |||
new RegexTest("Double quotes", "background-image:url(\"image.png\")", "image.png"), | |||
|
|||
new RegexTest("With semi-colon at end", "background-image:url(\"image.png\") ;", "image.png"), | |||
|
|||
new RegexTest("With spaces", "background-image: url( \"image.png\" )", "image.png"), | |||
new RegexTest("With tabs", "background-image\t:\turl(\"image.png\")", "image.png"), | |||
new RegexTest("Multiple lines", "background-image:\nurl(\n\t'image.png')", "image.png"), | |||
|
|||
new RegexTest("Mixed case", "Background-Image:Url('image.png')", "image.png"), | |||
new RegexTest("Case preserved in url", "Background-Image:Url('ImagE.png')", "ImagE.png"), | |||
|
|||
new RegexTest("Forward slashes", "background-image:url(/path/to/image.png)", "/path/to/image.png"), | |||
new RegexTest("Back slashes", "background-image:url(\\path\\to\\image.png)", "\\path\\to\\image.png"), | |||
|
|||
new RegexTest("Absolute URL", "background-image:url(http://some.server.com/path/to/image.png)", | |||
"http://some.server.com/path/to/image.png"), | |||
|
|||
new RegexTest("URL with port number", "background-image:url(http://some.server.com:8080/path/to/image.png)", | |||
"http://some.server.com:8080/path/to/image.png"), | |||
|
|||
new RegexTest("URL with query parameters", "background-image:url(http://some.server.com/path/to/image.png?key=value&key2=value2)", | |||
"http://some.server.com/path/to/image.png?key=value&key2=value2"), | |||
|
|||
new RegexTest("Absolute URL in double quotes", "background-image:url(\"http://some.server.com/path/to/image.png?key=value&key2=value2\")", | |||
"http://some.server.com/path/to/image.png?key=value&key2=value2"), | |||
|
|||
new RegexTest("Absolute URL in single quotes", "background-image:url('http://some.server.com/path/to/image.png?key=value&key2=value2')", | |||
"http://some.server.com/path/to/image.png?key=value&key2=value2"), | |||
|
|||
new RegexTest("Absolute URL with % encoding", "background-image:url('http://some.server.com/image.png?key=value%20with%20space')", | |||
"http://some.server.com/image.png?key=value%20with%20space"), | |||
|
|||
}; | |||
|
|||
String negative_matches[][] = { | |||
{"Mismatched quotes", "background-image:url(\"image.png')"}, | |||
{"Path with spaces", "background-image:url(\"my images/image.png')"}, | |||
}; | |||
|
|||
for(RegexTest test : positive_matches) { | |||
Matcher matcher = CssTransformer.BACKGROUND_IMAGE.matcher(test.css); | |||
assertTrue(test.description, matcher.matches()); | |||
assertEquals(test.description + " - URL does not match", test.expectedUrl, matcher.group(CssTransformer.URL_GROUP)); | |||
} | |||
|
|||
for(int i=0; i<negative_matches.length; i++) { | |||
String message = negative_matches[i][0]; | |||
String css = negative_matches[i][1]; | |||
assertFalse(message, CssTransformer.BACKGROUND_IMAGE.matcher(css).matches()); | |||
} | |||
} | |||
|
|||
static class RegexTest { | |||
String description; | |||
String css; | |||
String expectedUrl; | |||
|
|||
RegexTest(String description, String css, String expectedUrl) { | |||
this.description = description; | |||
this.css = css; | |||
this.expectedUrl = expectedUrl; | |||
} | |||
} | |||
} |
66 changes: 66 additions & 0 deletions
66
src/test/java/net/nczonline/web/cssembed/CssTransformerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,66 @@ | |||
package net.nczonline.web.cssembed; | |||
|
|||
import static junit.framework.Assert.*; | |||
|
|||
import java.io.IOException; | |||
import java.io.StringReader; | |||
import java.io.StringWriter; | |||
|
|||
import org.junit.Test; | |||
|
|||
public class CssTransformerTest { | |||
|
|||
private CssTransformer transformer = new CssTransformer(); | |||
|
|||
@Test | |||
public void testTransformationCallbacksWithoutBackgroundImages() throws IOException { | |||
final String css = ".someclass { font-size:small;}"; | |||
final String expectedOutput = ".someotherclass { font-size:large;}"; | |||
|
|||
Transform t = new Transform() { | |||
|
|||
public String transform(CssToken token) { | |||
throw new IllegalStateException("transform should not be called"); | |||
} | |||
|
|||
public String preTransform(String source) { | |||
return source.replaceAll("someclass", "someotherclass"); | |||
} | |||
|
|||
public String postTransform(String source) { | |||
return source.replaceAll("small", "large"); | |||
} | |||
}; | |||
|
|||
String output = transform(css, t); | |||
assertEquals("Pre and Post transformation not called", expectedOutput, output); | |||
} | |||
|
|||
@Test | |||
public void testMultipleBackgroundImages() throws IOException { | |||
final String css = ".firstclass {background-image:url('first.png')} .secondclass {background-image:url('second.png'); .thirdclass{}}"; | |||
final String expectedOutput = ".firstclass {background-image:url('first.png?version=12');} " + | |||
".secondclass {background-image:url('second.png?version=12'); .thirdclass{}}"; | |||
|
|||
Transform t = new DefaultTransform() { | |||
@Override | |||
public String transform(CssToken token) { | |||
if(token instanceof BackgroundImage) { | |||
BackgroundImage image = (BackgroundImage)token; | |||
return "background-image:url('" + image.getUrl() + "?version=12');"; | |||
} | |||
throw new IllegalStateException("Unknown token " + token); | |||
} | |||
}; | |||
|
|||
String output = transform(css, t); | |||
assertEquals(expectedOutput, output); | |||
} | |||
|
|||
private String transform(String css, Transform t) throws IOException { | |||
StringWriter writer = new StringWriter(); | |||
transformer.transform(new StringReader(css), t, writer); | |||
return writer.toString(); | |||
} | |||
|
|||
} |