You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Fop.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /*
  2. * $Id: Fop.java,v 1.24 2003/03/07 10:09:30 jeremias Exp $
  3. * ============================================================================
  4. * The Apache Software License, Version 1.1
  5. * ============================================================================
  6. *
  7. * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  8. *
  9. * Redistribution and use in source and binary forms, with or without modifica-
  10. * tion, are permitted provided that the following conditions are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright notice,
  13. * this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright notice,
  16. * this list of conditions and the following disclaimer in the documentation
  17. * and/or other materials provided with the distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if any, must
  20. * include the following acknowledgment: "This product includes software
  21. * developed by the Apache Software Foundation (http://www.apache.org/)."
  22. * Alternately, this acknowledgment may appear in the software itself, if
  23. * and wherever such third-party acknowledgments normally appear.
  24. *
  25. * 4. The names "FOP" and "Apache Software Foundation" must not be used to
  26. * endorse or promote products derived from this software without prior
  27. * written permission. For written permission, please contact
  28. * apache@apache.org.
  29. *
  30. * 5. Products derived from this software may not be called "Apache", nor may
  31. * "Apache" appear in their name, without prior written permission of the
  32. * Apache Software Foundation.
  33. *
  34. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
  35. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  36. * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  37. * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  38. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
  39. * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  40. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  41. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  42. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  43. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. * ============================================================================
  45. *
  46. * This software consists of voluntary contributions made by many individuals
  47. * on behalf of the Apache Software Foundation and was originally created by
  48. * James Tauber <jtauber@jtauber.com>. For more information on the Apache
  49. * Software Foundation, please see <http://www.apache.org/>.
  50. */
  51. package org.apache.fop.tools.anttasks;
  52. // Ant
  53. import org.apache.tools.ant.BuildException;
  54. import org.apache.tools.ant.DirectoryScanner;
  55. import org.apache.tools.ant.Project;
  56. import org.apache.tools.ant.Task;
  57. import org.apache.tools.ant.types.FileSet;
  58. import org.apache.tools.ant.util.GlobPatternMapper;
  59. // Java
  60. import java.io.File;
  61. import java.io.IOException;
  62. import java.io.OutputStream;
  63. import java.net.MalformedURLException;
  64. import java.util.List;
  65. // FOP
  66. import org.apache.fop.apps.Starter;
  67. import org.apache.fop.apps.InputHandler;
  68. import org.apache.fop.apps.FOFileHandler;
  69. import org.apache.fop.apps.Driver;
  70. import org.apache.fop.apps.FOPException;
  71. import org.apache.fop.fo.FOUserAgent;
  72. // Avalon
  73. import org.apache.avalon.framework.logger.ConsoleLogger;
  74. import org.apache.avalon.framework.logger.Logger;
  75. /**
  76. * Wrapper for FOP which allows it to be accessed from within an Ant task.
  77. * Accepts the inputs:
  78. * <ul>
  79. * <li>fofile -> formatting objects file to be transformed</li>
  80. * <li>format -> MIME type of the format to generate ex. "application/pdf"</li>
  81. * <li>outfile -> output filename</li>
  82. * <li>baseDir -> directory to work from</li>
  83. * <li>userconfig -> file with user configuration (same as the "-c" command
  84. * line option)</li>
  85. * <li>messagelevel -> (error | warn | info | verbose | debug) level to output
  86. * non-error messages</li>
  87. * <li>logFiles -> Controls whether the names of the files that are processed
  88. * are logged or not</li>
  89. * </ul>
  90. */
  91. public class Fop extends Task {
  92. private File foFile;
  93. private List filesets = new java.util.ArrayList();
  94. private File outFile;
  95. private File outDir;
  96. private String format; //MIME type
  97. private File baseDir;
  98. private File userConfig;
  99. private int messageType = Project.MSG_VERBOSE;
  100. private boolean logFiles = true;
  101. private boolean force = false;
  102. /**
  103. * Sets the filename for the userconfig.xml.
  104. * @param userConfig Configuration to use
  105. */
  106. public void setUserconfig(File userConfig) {
  107. this.userConfig = userConfig;
  108. }
  109. /**
  110. * Returns the file for the userconfig.xml.
  111. * @return the userconfig.xml file
  112. */
  113. public File getUserconfig() {
  114. return this.userConfig;
  115. }
  116. /**
  117. * Sets the input XSL-FO file.
  118. * @param foFile input XSL-FO file
  119. */
  120. public void setFofile(File foFile) {
  121. this.foFile = foFile;
  122. }
  123. /**
  124. * Gets the input XSL-FO file.
  125. * @return input XSL-FO file
  126. */
  127. public File getFofile() {
  128. return foFile;
  129. }
  130. /**
  131. * Adds a set of XSL-FO files (nested fileset attribute).
  132. * @param set a fileset
  133. */
  134. public void addFileset(FileSet set) {
  135. filesets.add(set);
  136. }
  137. /**
  138. * Returns the current list of filesets.
  139. * @return the filesets
  140. */
  141. public List getFilesets() {
  142. return this.filesets;
  143. }
  144. /**
  145. * Set whether to check dependencies, or to always generate;
  146. * optional, default is false.
  147. *
  148. * @param force true if always generate.
  149. */
  150. public void setForce(boolean force) {
  151. this.force = force;
  152. }
  153. /**
  154. * Gets the force attribute
  155. * @return the force attribute
  156. */
  157. public boolean getForce() {
  158. return force;
  159. }
  160. /**
  161. * Sets the output file.
  162. * @param outFile File to output to
  163. */
  164. public void setOutfile(File outFile) {
  165. this.outFile = outFile;
  166. }
  167. /**
  168. * Gets the output file.
  169. * @return the output file
  170. */
  171. public File getOutfile() {
  172. return this.outFile;
  173. }
  174. /**
  175. * Sets the output directory.
  176. * @param outDir Directory to output to
  177. */
  178. public void setOutdir(File outDir) {
  179. this.outDir = outDir;
  180. }
  181. /**
  182. * Gets the output directory.
  183. * @return the output directory
  184. */
  185. public File getOutdir() {
  186. return this.outDir;
  187. }
  188. /**
  189. * Sets output format (MIME type).
  190. * @param format the output format
  191. */
  192. public void setFormat(String format) {
  193. this.format = format;
  194. }
  195. /**
  196. * Gets the output format (MIME type).
  197. * @return the output format
  198. */
  199. public String getFormat() {
  200. return this.format;
  201. }
  202. /**
  203. * Sets the message level to be used while processing.
  204. * @param messageLevel (error | warn| info | verbose | debug)
  205. */
  206. public void setMessagelevel(String messageLevel) {
  207. if (messageLevel.equalsIgnoreCase("info")) {
  208. messageType = Project.MSG_INFO;
  209. } else if (messageLevel.equalsIgnoreCase("verbose")) {
  210. messageType = Project.MSG_VERBOSE;
  211. } else if (messageLevel.equalsIgnoreCase("debug")) {
  212. messageType = Project.MSG_DEBUG;
  213. } else if (messageLevel.equalsIgnoreCase("err")
  214. || messageLevel.equalsIgnoreCase("error")) {
  215. messageType = Project.MSG_ERR;
  216. } else if (messageLevel.equalsIgnoreCase("warn")) {
  217. messageType = Project.MSG_WARN;
  218. } else {
  219. log("messagelevel set to unknown value \"" + messageLevel
  220. + "\"", Project.MSG_ERR);
  221. throw new BuildException("unknown messagelevel");
  222. }
  223. }
  224. /**
  225. * Returns the message type corresponding to Project.MSG_*
  226. * representing the current message level.
  227. * @see org.apache.tools.ant.Project
  228. */
  229. public int getMessageType() {
  230. return messageType;
  231. }
  232. /**
  233. * Sets the base directory; currently ignored.
  234. * @param baseDir File to use as a working directory
  235. */
  236. public void setBasedir(File baseDir) {
  237. this.baseDir = baseDir;
  238. }
  239. /**
  240. * Gets the base directory.
  241. * @return the base directory
  242. */
  243. public File getBasedir() {
  244. return (baseDir != null) ? baseDir : getProject().resolveFile(".");
  245. }
  246. /**
  247. * Controls whether the filenames of the files that are processed are logged
  248. * or not.
  249. * @param logFiles True if the feature should be enabled
  250. */
  251. public void setLogFiles(boolean logFiles) {
  252. this.logFiles = logFiles;
  253. }
  254. /**
  255. * Returns True if the filename of each file processed should be logged.
  256. * @return True if the filenames should be logged.
  257. */
  258. public boolean getLogFiles() {
  259. return this.logFiles;
  260. }
  261. /**
  262. * @see org.apache.tools.ant.Task#execute()
  263. */
  264. public void execute() throws BuildException {
  265. int logLevel = ConsoleLogger.LEVEL_INFO;
  266. switch (getMessageType()) {
  267. case Project.MSG_DEBUG : logLevel = ConsoleLogger.LEVEL_DEBUG; break;
  268. case Project.MSG_INFO : logLevel = ConsoleLogger.LEVEL_INFO; break;
  269. case Project.MSG_WARN : logLevel = ConsoleLogger.LEVEL_WARN; break;
  270. case Project.MSG_ERR : logLevel = ConsoleLogger.LEVEL_ERROR; break;
  271. case Project.MSG_VERBOSE: logLevel = ConsoleLogger.LEVEL_DEBUG; break;
  272. }
  273. Logger log = new ConsoleLogger(logLevel);
  274. try {
  275. Starter starter = new FOPTaskStarter(this);
  276. starter.enableLogging(log);
  277. starter.run();
  278. } catch (FOPException ex) {
  279. throw new BuildException(ex);
  280. }
  281. }
  282. }
  283. class FOPTaskStarter extends Starter {
  284. private Fop task;
  285. private String baseURL = null;
  286. FOPTaskStarter(Fop task) throws FOPException {
  287. this.task = task;
  288. }
  289. private int determineRenderer(String format) {
  290. if ((format == null)
  291. || format.equalsIgnoreCase("application/pdf")
  292. || format.equalsIgnoreCase("pdf")) {
  293. return Driver.RENDER_PDF;
  294. } else if (format.equalsIgnoreCase("application/postscript")
  295. || format.equalsIgnoreCase("ps")) {
  296. return Driver.RENDER_PS;
  297. } else if (format.equalsIgnoreCase("application/vnd.mif")
  298. || format.equalsIgnoreCase("mif")) {
  299. return Driver.RENDER_MIF;
  300. } else if (format.equalsIgnoreCase("application/msword")
  301. || format.equalsIgnoreCase("application/rtf")
  302. || format.equalsIgnoreCase("rtf")) {
  303. return Driver.RENDER_RTF;
  304. } else if (format.equalsIgnoreCase("application/vnd.hp-PCL")
  305. || format.equalsIgnoreCase("pcl")) {
  306. return Driver.RENDER_PCL;
  307. } else if (format.equalsIgnoreCase("text/plain")
  308. || format.equalsIgnoreCase("txt")) {
  309. return Driver.RENDER_TXT;
  310. } else if (format.equalsIgnoreCase("text/xml")
  311. || format.equalsIgnoreCase("at")
  312. || format.equalsIgnoreCase("xml")) {
  313. return Driver.RENDER_XML;
  314. } else {
  315. String err = "Couldn't determine renderer to use: " + format;
  316. throw new BuildException(err);
  317. }
  318. }
  319. private String determineExtension(int renderer) {
  320. switch (renderer) {
  321. case Driver.RENDER_PDF:
  322. return ".pdf";
  323. case Driver.RENDER_PS:
  324. return ".ps";
  325. case Driver.RENDER_MIF:
  326. return ".mif";
  327. case Driver.RENDER_RTF:
  328. return ".rtf";
  329. case Driver.RENDER_PCL:
  330. return ".pcl";
  331. case Driver.RENDER_TXT:
  332. return ".txt";
  333. case Driver.RENDER_XML:
  334. return ".xml";
  335. default:
  336. String err = "Unknown renderer: " + renderer;
  337. throw new BuildException(err);
  338. }
  339. }
  340. private File replaceExtension(File file, String expectedExt,
  341. String newExt) {
  342. String name = file.getName();
  343. if (name.toLowerCase().endsWith(expectedExt)) {
  344. name = name.substring(0, name.length() - expectedExt.length());
  345. }
  346. name = name.concat(newExt);
  347. return new File(file.getParentFile(), name);
  348. }
  349. /**
  350. * @see org.apache.fop.apps.Starter#run()
  351. */
  352. public void run() throws FOPException {
  353. //Setup configuration
  354. if (task.getUserconfig() != null) {
  355. /**@todo implement me */
  356. }
  357. //Set base directory
  358. if (task.getBasedir() != null) {
  359. try {
  360. this.baseURL = task.getBasedir().toURL().toExternalForm();
  361. } catch (MalformedURLException mfue) {
  362. getLogger().error("Error creating base URL from base directory", mfue);
  363. }
  364. } else {
  365. try {
  366. if (task.getFofile() != null) {
  367. this.baseURL = task.getFofile().getParentFile().toURL().
  368. toExternalForm();
  369. }
  370. } catch (MalformedURLException mfue) {
  371. getLogger().error("Error creating base URL from XSL-FO input file", mfue);
  372. }
  373. }
  374. task.log("Using base URL: " + baseURL, Project.MSG_DEBUG);
  375. int rint = determineRenderer(task.getFormat());
  376. String newExtension = determineExtension(rint);
  377. // actioncount = # of fofiles actually processed through FOP
  378. int actioncount = 0;
  379. // skippedcount = # of fofiles which haven't changed (force = "false")
  380. int skippedcount = 0;
  381. // deal with single source file
  382. if (task.getFofile() != null) {
  383. if (task.getFofile().exists()) {
  384. File outf = task.getOutfile();
  385. if (outf == null) {
  386. throw new BuildException("outfile is required when fofile is used");
  387. }
  388. if (task.getOutdir() != null) {
  389. outf = new File(task.getOutdir(), outf.getName());
  390. }
  391. // Render if "force" flag is set OR
  392. // OR output file doesn't exist OR
  393. // output file is older than input file
  394. if (task.getForce() || !outf.exists()
  395. || (task.getFofile().lastModified() > outf.lastModified() )) {
  396. render(task.getFofile(), outf, rint);
  397. actioncount++;
  398. } else if (outf.exists() && (task.getFofile().lastModified() <= outf.lastModified() )) {
  399. skippedcount++;
  400. }
  401. }
  402. }
  403. GlobPatternMapper mapper = new GlobPatternMapper();
  404. mapper.setFrom("*.fo");
  405. mapper.setTo("*" + newExtension);
  406. // deal with the filesets
  407. for (int i = 0; i < task.getFilesets().size(); i++) {
  408. FileSet fs = (FileSet) task.getFilesets().get(i);
  409. DirectoryScanner ds = fs.getDirectoryScanner(task.getProject());
  410. String[] files = ds.getIncludedFiles();
  411. for (int j = 0; j < files.length; j++) {
  412. File f = new File(fs.getDir(task.getProject()), files[j]);
  413. File outf = null;
  414. if (task.getOutdir() != null && files[j].endsWith(".fo")) {
  415. String[] sa = mapper.mapFileName(files[j]);
  416. outf = new File(task.getOutdir(), sa[0]);
  417. } else {
  418. outf = replaceExtension(f, ".fo", newExtension);
  419. if (task.getOutdir() != null) {
  420. outf = new File(task.getOutdir(), outf.getName());
  421. }
  422. }
  423. try {
  424. if (this.baseURL == null) {
  425. this.baseURL = fs.getDir(task.getProject()).toURL().
  426. toExternalForm();
  427. }
  428. } catch (Exception e) {
  429. task.log("Error setting base URL", Project.MSG_DEBUG);
  430. }
  431. // Render if "force" flag is set OR
  432. // OR output file doesn't exist OR
  433. // output file is older than input file
  434. if (task.getForce() || !outf.exists()
  435. || (f.lastModified() > outf.lastModified() )) {
  436. render(f, outf, rint);
  437. actioncount++;
  438. } else if (outf.exists() && (f.lastModified() <= outf.lastModified() )) {
  439. skippedcount++;
  440. }
  441. }
  442. }
  443. if (actioncount + skippedcount == 0) {
  444. task.log("No files processed. No files were selected by the filesets "
  445. + "and no fofile was set." , Project.MSG_WARN);
  446. } else if (skippedcount > 0) {
  447. task.log(skippedcount + " xslfo file(s) skipped (no change found"
  448. + " since last generation; set force=\"true\" to override)."
  449. , Project.MSG_INFO);
  450. }
  451. }
  452. private void render(File foFile, File outFile,
  453. int renderer) throws FOPException {
  454. InputHandler inputHandler = new FOFileHandler(foFile);
  455. OutputStream out = null;
  456. try {
  457. out = new java.io.FileOutputStream(outFile);
  458. } catch (Exception ex) {
  459. throw new BuildException("Failed to open " + outFile, ex);
  460. }
  461. if (task.getLogFiles()) {
  462. task.log(foFile + " -> " + outFile, Project.MSG_INFO);
  463. }
  464. try {
  465. Driver driver = new Driver();
  466. setupLogger(driver);
  467. FOUserAgent userAgent = new FOUserAgent();
  468. userAgent.setBaseURL(this.baseURL);
  469. userAgent.enableLogging(getLogger());
  470. driver.setUserAgent(userAgent);
  471. driver.setRenderer(renderer);
  472. driver.setOutputStream(out);
  473. driver.render(inputHandler);
  474. } catch (Exception ex) {
  475. throw new BuildException(ex);
  476. } finally {
  477. try {
  478. out.close();
  479. } catch (IOException ioe) {
  480. getLogger().error("Error closing output file", ioe);
  481. }
  482. }
  483. }
  484. }