From 3df7dfcb88e44d23cb2ae7e38f774910908be347 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Fri, 29 Apr 2011 15:01:21 -0700 Subject: [PATCH] CSSEmbedTask.java - Added native Ant task for running CSSEmbed, allows for much faster builds - Replicates some of the functionality of CSSEmbed.java but in such a different style I didn't think reconciling them would necessarily be possible. CSSURLEmbedder.java - Count conversions correctly (uri -> datauri wasn't incrementing conversions counter) - Clean up some formatting around opening curly braces & "if ()" blocks - Wrap total conversion [INFO] line in a verbosity check so it doesn't always output --- .../nczonline/web/cssembed/CSSEmbedTask.java | 240 ++++++++++++++++++ .../web/cssembed/CSSURLEmbedder.java | 25 +- 2 files changed, 252 insertions(+), 13 deletions(-) create mode 100644 src/net/nczonline/web/cssembed/CSSEmbedTask.java diff --git a/src/net/nczonline/web/cssembed/CSSEmbedTask.java b/src/net/nczonline/web/cssembed/CSSEmbedTask.java new file mode 100644 index 0000000..782ee58 --- /dev/null +++ b/src/net/nczonline/web/cssembed/CSSEmbedTask.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2009 Nicholas C. Zakas. All rights reserved. + * http://www.nczonline.net/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package net.nczonline.web.cssembed; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.BuildException; + +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.FileProvider; +import org.apache.tools.ant.types.resources.FileResource; + +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; + +import java.io.*; + +import java.util.Vector; +import java.util.Iterator; + +//Define a custom Ant Task that calls into the CSS Embedder +public class CSSEmbedTask extends Task { + + //attribute options + private String charset = "UTF-8"; + private String root; + private boolean mhtml; + private String mhtmlRoot; + private boolean skipMissing; + private boolean verbose = false; + private int maxUriLength = 0; + private int maxImageSize = 0; + private File srcFile; + private File destFile; + + //support nested resource collections & mappers + private Mapper mapperElement = null; + private Vector rcs = new Vector(); + + //Simple Setters + public void setCharset(String charset) { + this.charset = charset; + } + + public void setRoot(String root) { + this.root = root; + } + + public void setMhtml(boolean mhtml) { + this.mhtml = mhtml; + } + + public void setMhtmlRoot(String mhtmlRoot) { + this.mhtmlRoot = mhtmlRoot; + } + + public void setSkipMissing(boolean skipMissing) { + this.skipMissing = skipMissing; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void setMaxUriLength(int maxUriLength) { + this.maxUriLength = maxUriLength; + } + + public void setMaxImageSize(int maxImageSize) { + this.maxImageSize = maxImageSize; + } + + public void setSrcFile(File srcFile) { + this.srcFile = srcFile; + } + + public void setDestFile(File destFile) { + this.destFile = destFile; + } + + //More complicated setters for nested elements... + + //add a collection of resources to copy + public void add(ResourceCollection res) { + rcs.add(res); + } + + //mapper takes source files & converts them to dest files + public Mapper createMapper() throws BuildException { + if (mapperElement != null) { + throw new BuildException("Cannot define more than one mapper", getLocation()); + } + mapperElement = new Mapper(getProject()); + return mapperElement; + } + + //support multiple types of filename mappers being added + public void add(FileNameMapper fileNameMapper) { + createMapper().add(fileNameMapper); + } + + //returns the mapper to use based on nested elements, defaults to IdentityMapper + private FileNameMapper getMapper() { + FileNameMapper mapper = null; + if (mapperElement != null) { + mapper = mapperElement.getImplementation(); + } else { + mapper = new IdentityMapper(); + } + return mapper; + } + + //ensure that attributes are legit + protected void validateAttributes() throws BuildException { + //if there's no nested resource containers make sure that a srcFile/destFile are set + if(this.rcs == null || this.rcs.size() == 0) { + if (this.srcFile == null || !this.srcFile.exists()) { + throw new BuildException("Must specify an input file or at least one nested resource", getLocation()); + } + + if(this.destFile == null) { + throw new BuildException("Must specify an output file or at least one nested resource", getLocation()); + } + } + + if(this.mhtml && this.mhtmlRoot == null) { + throw new BuildException("Must specify mhtmlRoot in mhtml mode", getLocation()); + } + + if(this.mhtmlRoot != null && !this.mhtml) { + log("mhtmlRoot has no effect if mhtml mode is not activated", Project.MSG_WARN); + } + } + + //run the task + public void execute () throws BuildException { + validateAttributes(); + + //set options flags + int options = (this.mhtml) ? CSSURLEmbedder.MHTML_OPTION : CSSURLEmbedder.DATAURI_OPTION; + if(skipMissing) { + options = options | CSSURLEmbedder.SKIP_MISSING_OPTION; + } + + if(srcFile != null && srcFile.exists()) { + try { + embed(srcFile, destFile, options); + } catch(IOException ex) { + throw new BuildException(ex.getMessage(), ex); + } + } + + FileNameMapper mapper = getMapper(); + + for(Iterator it = this.rcs.iterator(); it.hasNext();) { + ResourceCollection rc = (ResourceCollection) it.next(); + + for(Iterator rcit = rc.iterator(); rcit.hasNext();) { + FileResource fr = (FileResource) rcit.next(); + File in = fr.getFile(); + + String[] mapped = mapper.mapFileName(in.getName()); + if (mapped != null && mapped.length > 0) { + for(int k = 0; k < mapped.length; k++) { + File out = getProject().resolveFile(in.getParent() + File.separator + mapped[k]); + + try { + embed(in, out, options); + } catch(IOException ex) { + throw new BuildException(ex.getMessage(), ex); + } + } + } + } + } + } + + private void embed(File input, File output, int options) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + Reader in = new InputStreamReader(new FileInputStream(input), charset); + Writer out = new OutputStreamWriter(bytes, charset); + String pathRoot = root; + + if(pathRoot == null) { + pathRoot = input.getCanonicalPath(); + pathRoot = pathRoot.substring(0, pathRoot.lastIndexOf(File.separator)); + } + + if (!pathRoot.endsWith(File.separator)){ + pathRoot += File.separator; + } + + if(verbose) { + log("[INFO] embedding images from '" + input + "'"); + } + + CSSURLEmbedder embedder = new CSSURLEmbedder(in, options, verbose, maxUriLength, maxImageSize); + + if(mhtml) { + embedder.setMHTMLRoot(mhtmlRoot); + embedder.setFilename(output.getName()); + } + + embedder.embedImages(out, pathRoot); + + in.close(); + out.close(); + + if(bytes.size() > 0) { + if(verbose) { + log("[INFO] Writing to file: " + output); + } + + bytes.writeTo(new FileOutputStream(output)); + } + } +} \ No newline at end of file diff --git a/src/net/nczonline/web/cssembed/CSSURLEmbedder.java b/src/net/nczonline/web/cssembed/CSSURLEmbedder.java index 1cce781..67c9b47 100644 --- a/src/net/nczonline/web/cssembed/CSSURLEmbedder.java +++ b/src/net/nczonline/web/cssembed/CSSURLEmbedder.java @@ -215,7 +215,6 @@ public void embedImages(Writer out, String root) throws IOException { } foundMedia.put(url, lineNum); - //Begin processing URL String newUrl = url; if (verbose){ @@ -230,18 +229,16 @@ public void embedImages(Writer out, String root) throws IOException { //get the data URI format String uriString = getImageURIString(newUrl, url); - + //if it doesn't begin with data:, it's not a data URI if (uriString.startsWith("data:")){ - - - if (maxUriLength > 0 && uriString.length() > maxUriLength) { + if (maxUriLength > 0 && uriString.length() > maxUriLength){ if (verbose){ System.err.println("[WARNING] File " + newUrl + " creates a data URI larger than " + maxUriLength + " bytes. Skipping."); } builder.append(url); } else if (maxUriLength > 0 && uriString.length() > maxUriLength){ - if (verbose) { + if (verbose){ System.err.println("[INFO] File " + newUrl + " creates a data URI longer than " + maxUriLength + " characters. Skipping."); } builder.append(url); @@ -252,7 +249,6 @@ public void embedImages(Writer out, String root) throws IOException { * have both a data URI and MHTML in the same file. */ if (hasOption(MHTML_OPTION)){ - String entryName = getFilename(url); //create MHTML header entry @@ -272,6 +268,7 @@ public void embedImages(Writer out, String root) throws IOException { conversions++; } else if (hasOption(DATAURI_OPTION)){ builder.append(uriString); + conversions++; } } } else { @@ -306,8 +303,10 @@ public void embedImages(Writer out, String root) throws IOException { mhtmlHeader.append("*/\n"); out.write(mhtmlHeader.toString()); } - - System.err.println("[INFO] Converted " + conversions + " images to data URIs."); + + if (verbose){ + System.err.println("[INFO] Converted " + conversions + " images to data URIs."); + } out.write(builder.toString()); } @@ -353,9 +352,9 @@ private String getImageURIString(String url, String originalUrl) throws IOExcept } //check file size if we've been asked to - if(this.maxImageSize > 0 && file.length() > this.maxImageSize) { - if(verbose) { - System.err.println("[INFO] File " + originalUrl + " is larger than " + this.maxImageSize + " bytes. Skipping."); + if (maxImageSize > 0 && file.length() > maxImageSize){ + if (verbose){ + System.err.println("[INFO] File '" + originalUrl + "' is larger than " + maxImageSize + " bytes. Skipping."); } writer.write(originalUrl); @@ -369,7 +368,7 @@ private String getImageURIString(String url, String originalUrl) throws IOExcept System.err.println("[INFO] Generated data URI for '" + url + "'."); } } catch (FileNotFoundException e){ - if(hasOption(SKIP_MISSING_OPTION)) { + if (hasOption(SKIP_MISSING_OPTION)){ System.err.println("[INFO] Could not find file. " + e.getMessage() + " Skipping."); writer.write(originalUrl);