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 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /*
  2. * Copyright 1999-2006 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.tools.anttasks;
  18. // Ant
  19. import org.apache.tools.ant.BuildException;
  20. import org.apache.tools.ant.DirectoryScanner;
  21. import org.apache.tools.ant.Project;
  22. import org.apache.tools.ant.Task;
  23. import org.apache.tools.ant.types.FileSet;
  24. import org.apache.tools.ant.util.GlobPatternMapper;
  25. // Java
  26. import java.io.BufferedOutputStream;
  27. import java.io.File;
  28. import java.io.IOException;
  29. import java.io.OutputStream;
  30. import java.net.MalformedURLException;
  31. import java.util.List;
  32. // FOP
  33. import org.apache.fop.apps.FOPException;
  34. import org.apache.fop.apps.FOUserAgent;
  35. import org.apache.fop.apps.FopFactory;
  36. import org.apache.fop.apps.MimeConstants;
  37. import org.apache.fop.cli.InputHandler;
  38. import org.apache.avalon.framework.configuration.Configuration;
  39. import org.apache.avalon.framework.configuration.ConfigurationException;
  40. import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
  41. import org.apache.commons.logging.impl.SimpleLog;
  42. import org.apache.commons.logging.Log;
  43. import org.xml.sax.SAXException;
  44. /**
  45. * Wrapper for FOP which allows it to be accessed from within an Ant task.
  46. * Accepts the inputs:
  47. * <ul>
  48. * <li>fofile -> formatting objects file to be transformed</li>
  49. * <li>format -> MIME type of the format to generate ex. "application/pdf"</li>
  50. * <li>outfile -> output filename</li>
  51. * <li>baseDir -> directory to work from</li>
  52. * <li>relativebase -> (true | false) control whether to use each FO's
  53. * directory as base directory. false uses the baseDir parameter.</li>
  54. * <li>userconfig -> file with user configuration (same as the "-c" command
  55. * line option)</li>
  56. * <li>messagelevel -> (error | warn | info | verbose | debug) level to output
  57. * non-error messages</li>
  58. * <li>logFiles -> Controls whether the names of the files that are processed
  59. * are logged or not</li>
  60. * </ul>
  61. */
  62. public class Fop extends Task {
  63. private File foFile;
  64. private List filesets = new java.util.ArrayList();
  65. private File outFile;
  66. private File outDir;
  67. private String format; //MIME type
  68. private File baseDir;
  69. private File userConfig;
  70. private int messageType = Project.MSG_VERBOSE;
  71. private boolean logFiles = true;
  72. private boolean force = false;
  73. private boolean relativebase = false;
  74. /**
  75. * Sets the filename for the userconfig.xml.
  76. * @param userConfig Configuration to use
  77. */
  78. public void setUserconfig(File userConfig) {
  79. this.userConfig = userConfig;
  80. }
  81. /**
  82. * Returns the file for the userconfig.xml.
  83. * @return the userconfig.xml file
  84. */
  85. public File getUserconfig() {
  86. return this.userConfig;
  87. }
  88. /**
  89. * Sets the input XSL-FO file.
  90. * @param foFile input XSL-FO file
  91. */
  92. public void setFofile(File foFile) {
  93. this.foFile = foFile;
  94. }
  95. /**
  96. * Gets the input XSL-FO file.
  97. * @return input XSL-FO file
  98. */
  99. public File getFofile() {
  100. return foFile;
  101. }
  102. /**
  103. * Adds a set of XSL-FO files (nested fileset attribute).
  104. * @param set a fileset
  105. */
  106. public void addFileset(FileSet set) {
  107. filesets.add(set);
  108. }
  109. /**
  110. * Returns the current list of filesets.
  111. * @return the filesets
  112. */
  113. public List getFilesets() {
  114. return this.filesets;
  115. }
  116. /**
  117. * Set whether to include files (external-graphics, instream-foreign-object)
  118. * from a path relative to the .fo file (true) or the working directory (false, default)
  119. * only useful for filesets
  120. *
  121. * @param relbase true if paths are relative to file.
  122. */
  123. public void setRelativebase(boolean relbase) {
  124. this.relativebase = relbase;
  125. }
  126. /**
  127. * Gets the relative base attribute
  128. * @return the relative base attribute
  129. */
  130. public boolean getRelativebase() {
  131. return relativebase;
  132. }
  133. /**
  134. * Set whether to check dependencies, or to always generate;
  135. * optional, default is false.
  136. *
  137. * @param force true if always generate.
  138. */
  139. public void setForce(boolean force) {
  140. this.force = force;
  141. }
  142. /**
  143. * Gets the force attribute
  144. * @return the force attribute
  145. */
  146. public boolean getForce() {
  147. return force;
  148. }
  149. /**
  150. * Sets the output file.
  151. * @param outFile File to output to
  152. */
  153. public void setOutfile(File outFile) {
  154. this.outFile = outFile;
  155. }
  156. /**
  157. * Gets the output file.
  158. * @return the output file
  159. */
  160. public File getOutfile() {
  161. return this.outFile;
  162. }
  163. /**
  164. * Sets the output directory.
  165. * @param outDir Directory to output to
  166. */
  167. public void setOutdir(File outDir) {
  168. this.outDir = outDir;
  169. }
  170. /**
  171. * Gets the output directory.
  172. * @return the output directory
  173. */
  174. public File getOutdir() {
  175. return this.outDir;
  176. }
  177. /**
  178. * Sets output format (MIME type).
  179. * @param format the output format
  180. */
  181. public void setFormat(String format) {
  182. this.format = format;
  183. }
  184. /**
  185. * Gets the output format (MIME type).
  186. * @return the output format
  187. */
  188. public String getFormat() {
  189. return this.format;
  190. }
  191. /**
  192. * Sets the message level to be used while processing.
  193. * @param messageLevel (error | warn| info | verbose | debug)
  194. */
  195. public void setMessagelevel(String messageLevel) {
  196. if (messageLevel.equalsIgnoreCase("info")) {
  197. messageType = Project.MSG_INFO;
  198. } else if (messageLevel.equalsIgnoreCase("verbose")) {
  199. messageType = Project.MSG_VERBOSE;
  200. } else if (messageLevel.equalsIgnoreCase("debug")) {
  201. messageType = Project.MSG_DEBUG;
  202. } else if (messageLevel.equalsIgnoreCase("err")
  203. || messageLevel.equalsIgnoreCase("error")) {
  204. messageType = Project.MSG_ERR;
  205. } else if (messageLevel.equalsIgnoreCase("warn")) {
  206. messageType = Project.MSG_WARN;
  207. } else {
  208. log("messagelevel set to unknown value \"" + messageLevel
  209. + "\"", Project.MSG_ERR);
  210. throw new BuildException("unknown messagelevel");
  211. }
  212. }
  213. /**
  214. * Returns the message type corresponding to Project.MSG_*
  215. * representing the current message level.
  216. * @see org.apache.tools.ant.Project
  217. */
  218. public int getMessageType() {
  219. return messageType;
  220. }
  221. /**
  222. * Sets the base directory for single FO file (non-fileset) usage
  223. * @param baseDir File to use as a working directory
  224. */
  225. public void setBasedir(File baseDir) {
  226. this.baseDir = baseDir;
  227. }
  228. /**
  229. * Gets the base directory.
  230. * @return the base directory
  231. */
  232. public File getBasedir() {
  233. return (baseDir != null) ? baseDir : getProject().resolveFile(".");
  234. }
  235. /**
  236. * Controls whether the filenames of the files that are processed are logged
  237. * or not.
  238. * @param logFiles True if the feature should be enabled
  239. */
  240. public void setLogFiles(boolean logFiles) {
  241. this.logFiles = logFiles;
  242. }
  243. /**
  244. * Returns True if the filename of each file processed should be logged.
  245. * @return True if the filenames should be logged.
  246. */
  247. public boolean getLogFiles() {
  248. return this.logFiles;
  249. }
  250. /**
  251. * @see org.apache.tools.ant.Task#execute()
  252. */
  253. public void execute() throws BuildException {
  254. int logLevel = SimpleLog.LOG_LEVEL_INFO;
  255. switch (getMessageType()) {
  256. case Project.MSG_DEBUG : logLevel = SimpleLog.LOG_LEVEL_DEBUG; break;
  257. case Project.MSG_INFO : logLevel = SimpleLog.LOG_LEVEL_INFO; break;
  258. case Project.MSG_WARN : logLevel = SimpleLog.LOG_LEVEL_WARN; break;
  259. case Project.MSG_ERR : logLevel = SimpleLog.LOG_LEVEL_ERROR; break;
  260. case Project.MSG_VERBOSE: logLevel = SimpleLog.LOG_LEVEL_DEBUG; break;
  261. default: logLevel = SimpleLog.LOG_LEVEL_INFO;
  262. }
  263. SimpleLog logger = new SimpleLog("FOP/Anttask");
  264. logger.setLevel(logLevel);
  265. try {
  266. FOPTaskStarter starter = new FOPTaskStarter(this);
  267. starter.setLogger(logger);
  268. starter.run();
  269. } catch (FOPException ex) {
  270. throw new BuildException(ex);
  271. }
  272. }
  273. }
  274. class FOPTaskStarter {
  275. // configure fopFactory as desired
  276. private FopFactory fopFactory = FopFactory.newInstance();
  277. private Fop task;
  278. private String baseURL = null;
  279. /**
  280. * logging instance
  281. */
  282. protected Log logger = null;
  283. /**
  284. * Sets the Commons-Logging instance for this class
  285. * @param logger The Commons-Logging instance
  286. */
  287. public void setLogger(Log logger) {
  288. this.logger = logger;
  289. }
  290. /**
  291. * Returns the Commons-Logging instance for this class
  292. * @return The Commons-Logging instance
  293. */
  294. protected Log getLogger() {
  295. return logger;
  296. }
  297. FOPTaskStarter(Fop task) throws FOPException {
  298. this.task = task;
  299. }
  300. private static final String[][] SHORT_NAMES = {
  301. {"pdf", MimeConstants.MIME_PDF},
  302. {"ps", MimeConstants.MIME_POSTSCRIPT},
  303. {"mif", MimeConstants.MIME_MIF},
  304. {"rtf", MimeConstants.MIME_RTF},
  305. {"pcl", MimeConstants.MIME_PCL},
  306. {"txt", MimeConstants.MIME_PLAIN_TEXT},
  307. {"at", MimeConstants.MIME_FOP_AREA_TREE},
  308. {"xml", MimeConstants.MIME_FOP_AREA_TREE},
  309. {"tiff", MimeConstants.MIME_TIFF},
  310. {"tif", MimeConstants.MIME_TIFF},
  311. {"png", MimeConstants.MIME_PNG}
  312. };
  313. private String normalizeOutputFormat(String format) {
  314. for (int i = 0; i < SHORT_NAMES.length; i++) {
  315. if (SHORT_NAMES[i][0].equals(format)) {
  316. return SHORT_NAMES[i][1];
  317. }
  318. }
  319. return format; //no change
  320. }
  321. private static final String[][] EXTENSIONS = {
  322. {MimeConstants.MIME_FOP_AREA_TREE, ".at.xml"},
  323. {MimeConstants.MIME_FOP_AWT_PREVIEW, null},
  324. {MimeConstants.MIME_FOP_PRINT, null},
  325. {MimeConstants.MIME_PDF, ".pdf"},
  326. {MimeConstants.MIME_POSTSCRIPT, ".ps"},
  327. {MimeConstants.MIME_PCL, ".pcl"},
  328. {MimeConstants.MIME_PCL_ALT, ".pcl"},
  329. {MimeConstants.MIME_PLAIN_TEXT, ".txt"},
  330. {MimeConstants.MIME_RTF, ".rtf"},
  331. {MimeConstants.MIME_RTF_ALT1, ".rtf"},
  332. {MimeConstants.MIME_RTF_ALT2, ".rtf"},
  333. {MimeConstants.MIME_MIF, ".mif"},
  334. {MimeConstants.MIME_SVG, ".svg"},
  335. {MimeConstants.MIME_PNG, ".png"},
  336. {MimeConstants.MIME_JPEG, ".jpg"},
  337. {MimeConstants.MIME_TIFF, ".tif"},
  338. {MimeConstants.MIME_XSL_FO, ".fo"}
  339. };
  340. private String determineExtension(String outputFormat) {
  341. for (int i = 0; i < EXTENSIONS.length; i++) {
  342. if (EXTENSIONS[i][0].equals(outputFormat)) {
  343. String ext = EXTENSIONS[i][1];
  344. if (ext == null) {
  345. throw new RuntimeException("Output format '"
  346. + outputFormat + "' does not produce a file.");
  347. } else {
  348. return ext;
  349. }
  350. }
  351. }
  352. return ".unk"; //unknown
  353. }
  354. private File replaceExtension(File file, String expectedExt,
  355. String newExt) {
  356. String name = file.getName();
  357. if (name.toLowerCase().endsWith(expectedExt)) {
  358. name = name.substring(0, name.length() - expectedExt.length());
  359. }
  360. name = name.concat(newExt);
  361. return new File(file.getParentFile(), name);
  362. }
  363. /**
  364. * @see org.apache.fop.apps.Starter#run()
  365. */
  366. public void run() throws FOPException {
  367. //Setup configuration
  368. Configuration userConfig = null;
  369. if (task.getUserconfig() != null) {
  370. if (task.getUserconfig() != null) {
  371. DefaultConfigurationBuilder configBuilder = new DefaultConfigurationBuilder();
  372. try {
  373. userConfig = configBuilder.buildFromFile(task.getUserconfig());
  374. } catch (SAXException e) {
  375. throw new FOPException(e);
  376. } catch (ConfigurationException e) {
  377. throw new FOPException(e);
  378. } catch (IOException e) {
  379. throw new FOPException(e);
  380. }
  381. }
  382. }
  383. //Set base directory
  384. if (task.getBasedir() != null) {
  385. try {
  386. this.baseURL = task.getBasedir().toURL().toExternalForm();
  387. } catch (MalformedURLException mfue) {
  388. logger.error("Error creating base URL from base directory", mfue);
  389. }
  390. } else {
  391. try {
  392. if (task.getFofile() != null) {
  393. this.baseURL = task.getFofile().getParentFile().toURL().
  394. toExternalForm();
  395. }
  396. } catch (MalformedURLException mfue) {
  397. logger.error("Error creating base URL from XSL-FO input file", mfue);
  398. }
  399. }
  400. task.log("Using base URL: " + baseURL, Project.MSG_DEBUG);
  401. String outputFormat = normalizeOutputFormat(task.getFormat());
  402. String newExtension = determineExtension(outputFormat);
  403. // actioncount = # of fofiles actually processed through FOP
  404. int actioncount = 0;
  405. // skippedcount = # of fofiles which haven't changed (force = "false")
  406. int skippedcount = 0;
  407. // deal with single source file
  408. if (task.getFofile() != null) {
  409. if (task.getFofile().exists()) {
  410. File outf = task.getOutfile();
  411. if (outf == null) {
  412. throw new BuildException("outfile is required when fofile is used");
  413. }
  414. if (task.getOutdir() != null) {
  415. outf = new File(task.getOutdir(), outf.getName());
  416. }
  417. // Render if "force" flag is set OR
  418. // OR output file doesn't exist OR
  419. // output file is older than input file
  420. if (task.getForce() || !outf.exists()
  421. || (task.getFofile().lastModified() > outf.lastModified() )) {
  422. render(task.getFofile(), outf, outputFormat, userConfig);
  423. actioncount++;
  424. } else if (outf.exists()
  425. && (task.getFofile().lastModified() <= outf.lastModified() )) {
  426. skippedcount++;
  427. }
  428. }
  429. }
  430. GlobPatternMapper mapper = new GlobPatternMapper();
  431. mapper.setFrom("*.fo");
  432. mapper.setTo("*" + newExtension);
  433. // deal with the filesets
  434. for (int i = 0; i < task.getFilesets().size(); i++) {
  435. FileSet fs = (FileSet) task.getFilesets().get(i);
  436. DirectoryScanner ds = fs.getDirectoryScanner(task.getProject());
  437. String[] files = ds.getIncludedFiles();
  438. for (int j = 0; j < files.length; j++) {
  439. File f = new File(fs.getDir(task.getProject()), files[j]);
  440. File outf = null;
  441. if (task.getOutdir() != null && files[j].endsWith(".fo")) {
  442. String[] sa = mapper.mapFileName(files[j]);
  443. outf = new File(task.getOutdir(), sa[0]);
  444. } else {
  445. outf = replaceExtension(f, ".fo", newExtension);
  446. if (task.getOutdir() != null) {
  447. outf = new File(task.getOutdir(), outf.getName());
  448. }
  449. }
  450. try {
  451. if (task.getRelativebase()) {
  452. this.baseURL = f.getParentFile().toURL().
  453. toExternalForm();
  454. }
  455. if (this.baseURL == null) {
  456. this.baseURL = fs.getDir(task.getProject()).toURL().
  457. toExternalForm();
  458. }
  459. } catch (Exception e) {
  460. task.log("Error setting base URL", Project.MSG_DEBUG);
  461. }
  462. // Render if "force" flag is set OR
  463. // OR output file doesn't exist OR
  464. // output file is older than input file
  465. if (task.getForce() || !outf.exists()
  466. || (f.lastModified() > outf.lastModified() )) {
  467. render(f, outf, outputFormat, userConfig);
  468. actioncount++;
  469. } else if (outf.exists() && (f.lastModified() <= outf.lastModified() )) {
  470. skippedcount++;
  471. }
  472. }
  473. }
  474. if (actioncount + skippedcount == 0) {
  475. task.log("No files processed. No files were selected by the filesets "
  476. + "and no fofile was set." , Project.MSG_WARN);
  477. } else if (skippedcount > 0) {
  478. task.log(skippedcount + " xslfo file(s) skipped (no change found"
  479. + " since last generation; set force=\"true\" to override)."
  480. , Project.MSG_INFO);
  481. }
  482. }
  483. private void render(File foFile, File outFile,
  484. String outputFormat, Configuration userConfig) throws FOPException {
  485. InputHandler inputHandler = new InputHandler(foFile);
  486. OutputStream out = null;
  487. try {
  488. out = new java.io.FileOutputStream(outFile);
  489. out = new BufferedOutputStream(out);
  490. } catch (Exception ex) {
  491. throw new BuildException("Failed to open " + outFile, ex);
  492. }
  493. if (task.getLogFiles()) {
  494. task.log(foFile + " -> " + outFile, Project.MSG_INFO);
  495. }
  496. boolean success = false;
  497. try {
  498. FOUserAgent userAgent = fopFactory.newFOUserAgent();
  499. userAgent.setBaseURL(this.baseURL);
  500. inputHandler.renderTo(userAgent, outputFormat, out);
  501. success = true;
  502. } catch (Exception ex) {
  503. throw new BuildException(ex);
  504. } finally {
  505. try {
  506. out.close();
  507. } catch (IOException ioe) {
  508. logger.error("Error closing output file", ioe);
  509. }
  510. if (!success) {
  511. outFile.delete();
  512. }
  513. }
  514. }
  515. }