/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.tools.anttasks;
// Ant
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.GlobPatternMapper;
// Java
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.List;
// FOP
import org.apache.fop.apps.InputHandler;
import org.apache.fop.apps.FOFileHandler;
import org.apache.fop.apps.Driver;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
// Avalon
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.ConsoleLogger;
import org.apache.avalon.framework.logger.Logger;
/**
* Wrapper for FOP which allows it to be accessed from within an Ant task.
* Accepts the inputs:
*
* - fofile -> formatting objects file to be transformed
* - format -> MIME type of the format to generate ex. "application/pdf"
* - outfile -> output filename
* - baseDir -> directory to work from
* - relativebase -> (true | false) control whether to use each FO's
* directory as base directory. false uses the baseDir parameter.
* - userconfig -> file with user configuration (same as the "-c" command
* line option)
* - messagelevel -> (error | warn | info | verbose | debug) level to output
* non-error messages
* - logFiles -> Controls whether the names of the files that are processed
* are logged or not
*
*/
public class Fop extends Task {
private File foFile;
private List filesets = new java.util.ArrayList();
private File outFile;
private File outDir;
private String format; //MIME type
private File baseDir;
private File userConfig;
private int messageType = Project.MSG_VERBOSE;
private boolean logFiles = true;
private boolean force = false;
private boolean relativebase = false;
/**
* Sets the filename for the userconfig.xml.
* @param userConfig Configuration to use
*/
public void setUserconfig(File userConfig) {
this.userConfig = userConfig;
}
/**
* Returns the file for the userconfig.xml.
* @return the userconfig.xml file
*/
public File getUserconfig() {
return this.userConfig;
}
/**
* Sets the input XSL-FO file.
* @param foFile input XSL-FO file
*/
public void setFofile(File foFile) {
this.foFile = foFile;
}
/**
* Gets the input XSL-FO file.
* @return input XSL-FO file
*/
public File getFofile() {
return foFile;
}
/**
* Adds a set of XSL-FO files (nested fileset attribute).
* @param set a fileset
*/
public void addFileset(FileSet set) {
filesets.add(set);
}
/**
* Returns the current list of filesets.
* @return the filesets
*/
public List getFilesets() {
return this.filesets;
}
/**
* Set whether to include files (external-grpahics, instream-foreign-object)
* from a path relative to the .fo file (true) or the working directory (false, default)
* only useful for filesets
*
* @param relbase true if paths are relative to file.
*/
public void setRelativebase(boolean relbase) {
this.relativebase = relbase;
}
/**
* Gets the relative base attribute
* @return the relative base attribute
*/
public boolean getRelativebase() {
return relativebase;
}
/**
* Set whether to check dependencies, or to always generate;
* optional, default is false.
*
* @param force true if always generate.
*/
public void setForce(boolean force) {
this.force = force;
}
/**
* Gets the force attribute
* @return the force attribute
*/
public boolean getForce() {
return force;
}
/**
* Sets the output file.
* @param outFile File to output to
*/
public void setOutfile(File outFile) {
this.outFile = outFile;
}
/**
* Gets the output file.
* @return the output file
*/
public File getOutfile() {
return this.outFile;
}
/**
* Sets the output directory.
* @param outDir Directory to output to
*/
public void setOutdir(File outDir) {
this.outDir = outDir;
}
/**
* Gets the output directory.
* @return the output directory
*/
public File getOutdir() {
return this.outDir;
}
/**
* Sets output format (MIME type).
* @param format the output format
*/
public void setFormat(String format) {
this.format = format;
}
/**
* Gets the output format (MIME type).
* @return the output format
*/
public String getFormat() {
return this.format;
}
/**
* Sets the message level to be used while processing.
* @param messageLevel (error | warn| info | verbose | debug)
*/
public void setMessagelevel(String messageLevel) {
if (messageLevel.equalsIgnoreCase("info")) {
messageType = Project.MSG_INFO;
} else if (messageLevel.equalsIgnoreCase("verbose")) {
messageType = Project.MSG_VERBOSE;
} else if (messageLevel.equalsIgnoreCase("debug")) {
messageType = Project.MSG_DEBUG;
} else if (messageLevel.equalsIgnoreCase("err")
|| messageLevel.equalsIgnoreCase("error")) {
messageType = Project.MSG_ERR;
} else if (messageLevel.equalsIgnoreCase("warn")) {
messageType = Project.MSG_WARN;
} else {
log("messagelevel set to unknown value \"" + messageLevel
+ "\"", Project.MSG_ERR);
throw new BuildException("unknown messagelevel");
}
}
/**
* Returns the message type corresponding to Project.MSG_*
* representing the current message level.
* @see org.apache.tools.ant.Project
*/
public int getMessageType() {
return messageType;
}
/**
* Sets the base directory; currently ignored.
* @param baseDir File to use as a working directory
*/
public void setBasedir(File baseDir) {
this.baseDir = baseDir;
}
/**
* Gets the base directory.
* @return the base directory
*/
public File getBasedir() {
return (baseDir != null) ? baseDir : getProject().resolveFile(".");
}
/**
* Controls whether the filenames of the files that are processed are logged
* or not.
* @param logFiles True if the feature should be enabled
*/
public void setLogFiles(boolean logFiles) {
this.logFiles = logFiles;
}
/**
* Returns True if the filename of each file processed should be logged.
* @return True if the filenames should be logged.
*/
public boolean getLogFiles() {
return this.logFiles;
}
/**
* @see org.apache.tools.ant.Task#execute()
*/
public void execute() throws BuildException {
int logLevel = ConsoleLogger.LEVEL_INFO;
switch (getMessageType()) {
case Project.MSG_DEBUG : logLevel = ConsoleLogger.LEVEL_DEBUG; break;
case Project.MSG_INFO : logLevel = ConsoleLogger.LEVEL_INFO; break;
case Project.MSG_WARN : logLevel = ConsoleLogger.LEVEL_WARN; break;
case Project.MSG_ERR : logLevel = ConsoleLogger.LEVEL_ERROR; break;
case Project.MSG_VERBOSE: logLevel = ConsoleLogger.LEVEL_DEBUG; break;
}
Logger log = new ConsoleLogger(logLevel);
try {
FOPTaskStarter starter = new FOPTaskStarter(this);
starter.enableLogging(log);
starter.run();
} catch (FOPException ex) {
throw new BuildException(ex);
}
}
}
class FOPTaskStarter extends AbstractLogEnabled {
private Fop task;
private String baseURL = null;
FOPTaskStarter(Fop task) throws FOPException {
this.task = task;
}
private int determineRenderer(String format) {
if ((format == null)
|| format.equalsIgnoreCase("application/pdf")
|| format.equalsIgnoreCase("pdf")) {
return Driver.RENDER_PDF;
} else if (format.equalsIgnoreCase("application/postscript")
|| format.equalsIgnoreCase("ps")) {
return Driver.RENDER_PS;
} else if (format.equalsIgnoreCase("application/vnd.mif")
|| format.equalsIgnoreCase("mif")) {
return Driver.RENDER_MIF;
} else if (format.equalsIgnoreCase("application/msword")
|| format.equalsIgnoreCase("application/rtf")
|| format.equalsIgnoreCase("rtf")) {
return Driver.RENDER_RTF;
} else if (format.equalsIgnoreCase("application/vnd.hp-PCL")
|| format.equalsIgnoreCase("pcl")) {
return Driver.RENDER_PCL;
} else if (format.equalsIgnoreCase("text/plain")
|| format.equalsIgnoreCase("txt")) {
return Driver.RENDER_TXT;
} else if (format.equalsIgnoreCase("text/xml")
|| format.equalsIgnoreCase("at")
|| format.equalsIgnoreCase("xml")) {
return Driver.RENDER_XML;
} else {
String err = "Couldn't determine renderer to use: " + format;
throw new BuildException(err);
}
}
private String determineExtension(int renderer) {
switch (renderer) {
case Driver.RENDER_PDF:
return ".pdf";
case Driver.RENDER_PS:
return ".ps";
case Driver.RENDER_MIF:
return ".mif";
case Driver.RENDER_RTF:
return ".rtf";
case Driver.RENDER_PCL:
return ".pcl";
case Driver.RENDER_TXT:
return ".txt";
case Driver.RENDER_XML:
return ".xml";
default:
String err = "Unknown renderer: " + renderer;
throw new BuildException(err);
}
}
private File replaceExtension(File file, String expectedExt,
String newExt) {
String name = file.getName();
if (name.toLowerCase().endsWith(expectedExt)) {
name = name.substring(0, name.length() - expectedExt.length());
}
name = name.concat(newExt);
return new File(file.getParentFile(), name);
}
/**
* @see org.apache.fop.apps.Starter#run()
*/
public void run() throws FOPException {
//Setup configuration
if (task.getUserconfig() != null) {
/**@todo implement me */
}
//Set base directory
if (task.getBasedir() != null) {
try {
this.baseURL = task.getBasedir().toURL().toExternalForm();
} catch (MalformedURLException mfue) {
getLogger().error("Error creating base URL from base directory", mfue);
}
} else {
try {
if (task.getFofile() != null) {
this.baseURL = task.getFofile().getParentFile().toURL().
toExternalForm();
}
} catch (MalformedURLException mfue) {
getLogger().error("Error creating base URL from XSL-FO input file", mfue);
}
}
task.log("Using base URL: " + baseURL, Project.MSG_DEBUG);
int rint = determineRenderer(task.getFormat());
String newExtension = determineExtension(rint);
// actioncount = # of fofiles actually processed through FOP
int actioncount = 0;
// skippedcount = # of fofiles which haven't changed (force = "false")
int skippedcount = 0;
// deal with single source file
if (task.getFofile() != null) {
if (task.getFofile().exists()) {
File outf = task.getOutfile();
if (outf == null) {
throw new BuildException("outfile is required when fofile is used");
}
if (task.getOutdir() != null) {
outf = new File(task.getOutdir(), outf.getName());
}
// Render if "force" flag is set OR
// OR output file doesn't exist OR
// output file is older than input file
if (task.getForce() || !outf.exists()
|| (task.getFofile().lastModified() > outf.lastModified() )) {
render(task.getFofile(), outf, rint);
actioncount++;
} else if (outf.exists()
&& (task.getFofile().lastModified() <= outf.lastModified() )) {
skippedcount++;
}
}
}
GlobPatternMapper mapper = new GlobPatternMapper();
mapper.setFrom("*.fo");
mapper.setTo("*" + newExtension);
// deal with the filesets
for (int i = 0; i < task.getFilesets().size(); i++) {
FileSet fs = (FileSet) task.getFilesets().get(i);
DirectoryScanner ds = fs.getDirectoryScanner(task.getProject());
String[] files = ds.getIncludedFiles();
for (int j = 0; j < files.length; j++) {
File f = new File(fs.getDir(task.getProject()), files[j]);
File outf = null;
if (task.getOutdir() != null && files[j].endsWith(".fo")) {
String[] sa = mapper.mapFileName(files[j]);
outf = new File(task.getOutdir(), sa[0]);
} else {
outf = replaceExtension(f, ".fo", newExtension);
if (task.getOutdir() != null) {
outf = new File(task.getOutdir(), outf.getName());
}
}
try {
if (task.getRelativebase()) {
this.baseURL = f.getParentFile().toURL().
toExternalForm();
}
if (this.baseURL == null) {
this.baseURL = fs.getDir(task.getProject()).toURL().
toExternalForm();
}
} catch (Exception e) {
task.log("Error setting base URL", Project.MSG_DEBUG);
}
// Render if "force" flag is set OR
// OR output file doesn't exist OR
// output file is older than input file
if (task.getForce() || !outf.exists()
|| (f.lastModified() > outf.lastModified() )) {
render(f, outf, rint);
actioncount++;
} else if (outf.exists() && (f.lastModified() <= outf.lastModified() )) {
skippedcount++;
}
}
}
if (actioncount + skippedcount == 0) {
task.log("No files processed. No files were selected by the filesets "
+ "and no fofile was set." , Project.MSG_WARN);
} else if (skippedcount > 0) {
task.log(skippedcount + " xslfo file(s) skipped (no change found"
+ " since last generation; set force=\"true\" to override)."
, Project.MSG_INFO);
}
}
private void render(File foFile, File outFile,
int renderer) throws FOPException {
InputHandler inputHandler = new FOFileHandler(foFile);
OutputStream out = null;
try {
out = new java.io.FileOutputStream(outFile);
} catch (Exception ex) {
throw new BuildException("Failed to open " + outFile, ex);
}
if (task.getLogFiles()) {
task.log(foFile + " -> " + outFile, Project.MSG_INFO);
}
try {
Driver driver = new Driver();
setupLogger(driver);
FOUserAgent userAgent = new FOUserAgent();
userAgent.setBaseURL(this.baseURL);
userAgent.enableLogging(getLogger());
driver.setUserAgent(userAgent);
driver.setRenderer(renderer);
driver.setOutputStream(out);
driver.render(inputHandler);
} catch (Exception ex) {
throw new BuildException(ex);
} finally {
try {
out.close();
} catch (IOException ioe) {
getLogger().error("Error closing output file", ioe);
}
}
}
}