/* * BatchExporter.java * Copyright James Dempsey, 2012 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Created on 20/01/2012 9:33:45 AM * * $Id$ */ package pcgen.system; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import pcgen.cdom.base.Constants; import pcgen.core.SettingsHandler; import pcgen.core.utils.MessageType; import pcgen.core.utils.ShowMessageDelegate; import pcgen.facade.core.CharacterFacade; import pcgen.facade.core.PartyFacade; import pcgen.facade.core.SourceSelectionFacade; import pcgen.facade.core.UIDelegate; import pcgen.gui2.UIPropertyContext; import pcgen.io.ExportException; import pcgen.io.ExportHandler; import pcgen.io.ExportUtilities; import pcgen.io.PCGFile; import pcgen.persistence.SourceFileLoader; import pcgen.util.Logging; import pcgen.util.fop.FopTask; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.output.TeeOutputStream; import org.apache.commons.lang3.StringUtils; /** * The Class {@code BatchExporter} manages character sheet output to a * file. It is capable of outputting either a single character or a party * to an output file based on a suitable export template. * <p> * BatchExporter is intended to be used for both batch export of characters * and as a library for other code to easily provide output capability. When * used in batch mode an instance should be created for the template and * one of the export methods called. When used as a library the static methods * should be used and supplied with preloaded characters. * * <br> * * @author James Dempsey <jdempsey@users.sourceforge.net> */ public class BatchExporter { private final String exportTemplateFilename; private final UIDelegate uiDelegate; private final boolean isPdf; /** * Create a new instance of BatchExporter for use with a particular export * template. * * @param exportTemplateFilename The path to the export template. * @param uiDelegate The object through which to report any issues to the user. */ BatchExporter(String exportTemplateFilename, UIDelegate uiDelegate) { this.exportTemplateFilename = exportTemplateFilename; this.uiDelegate = uiDelegate; isPdf = ExportUtilities.isPdfTemplate(exportTemplateFilename); } /** * Export a character sheet for the character to the output file using the * pre-registered template. If the output file is null then a default file * will be used based on the character file name and the type of export * template in use. If the output file exists it will be overwritten. * <p> * This method will load the required data for the character, load the * character and then export the character sheet. * * @param characterFilename The path to the character PCG file. * @param outputFile The path to the output file to be created. May be null. * @return true if the export was successful, false if it failed in some way. */ boolean exportCharacter(String characterFilename, String outputFile) { File file = new File(characterFilename); if (!PCGFile.isPCGenCharacterFile(file)) { Logging.errorPrint("Invalid character file specified: " + file.getAbsolutePath()); return false; } String outFilename = outputFile; if (outFilename == null) { outFilename = generateOutputFilename(characterFilename); } Logging.log(Logging.INFO, "Started export of " + file.getAbsolutePath() + " using " + exportTemplateFilename + " to " + outFilename); // Load data SourceSelectionFacade sourcesForCharacter = CharacterManager.getRequiredSourcesForCharacter(file, uiDelegate); Logging.log(Logging.INFO, "Loading sources " + sourcesForCharacter.getCampaigns() + " using game mode " + sourcesForCharacter.getGameMode()); SourceFileLoader loader = new SourceFileLoader(sourcesForCharacter, uiDelegate); loader.execute(); // Load character CharacterFacade character = CharacterManager.openCharacter(file, uiDelegate, loader.getDataSetFacade()); if (character == null) { return false; } // Export character File templateFile = new File(exportTemplateFilename); File outFile = new File(outFilename); if (isPdf) { return exportCharacterToPDF(character, outFile, templateFile); } else { return exportCharacterToNonPDF(character, outFile, templateFile); } } /** * Export a party sheet for the party to the output file using the * pre-registered template. If the output file is null then a default file * will be used based on the party file name and the type of export * template in use. If the output file exists it will be overwritten. * <p> * This method will load the required data for the party, load the characters * in the party and then export the party sheet. * * @param partyFilename The path to the party PCP file. * @param outputFile The path to the output file to be created. May be null. * @return true if the export was successful, false if it failed in some way. */ boolean exportParty(String partyFilename, String outputFile) { File file = new File(partyFilename); if (!PCGFile.isPCGenPartyFile(file)) { Logging.errorPrint("Invalid party file specified: " + file.getAbsolutePath()); return false; } String outFilename = outputFile; if (outFilename == null) { outFilename = generateOutputFilename(partyFilename); } Logging.log(Logging.INFO, "Started export of party " + file.getAbsolutePath() + " using " + exportTemplateFilename + " to " + outFilename); // Load data SourceSelectionFacade sourcesForCharacter = CharacterManager.getRequiredSourcesForParty(file, uiDelegate); SourceFileLoader loader = new SourceFileLoader(sourcesForCharacter, uiDelegate); loader.execute(); // Load party PartyFacade party = CharacterManager.openParty(file, uiDelegate, loader.getDataSetFacade()); // Export party File templateFile = new File(exportTemplateFilename); File outFile = new File(outFilename); if (isPdf) { return exportPartyToPDF(party, outFile, templateFile); } else { return exportPartyToNonPDF(party, outFile, templateFile); } } /** * Write a PDF character sheet for the character to the output file. The * character sheet will be built according to the template file. If the * output file exists it will be overwritten. * * @param character The already loaded character to be output. * @param outFile The file to which the character sheet is to be written. * @param templateFile The file that has the export template definition. * @return true if the export was successful, false if it failed in some way. */ public static boolean exportCharacterToPDF(CharacterFacade character, File outFile, File templateFile) { String templateExtension = FilenameUtils.getExtension(templateFile.getName()); boolean isTransformTemplate = "xslt".equalsIgnoreCase(templateExtension) || "xsl".equalsIgnoreCase(templateExtension); boolean useTempFile = PCGenSettings.OPTIONS_CONTEXT.initBoolean( PCGenSettings.OPTION_GENERATE_TEMP_FILE_WITH_PDF, false); String outFileName = FilenameUtils.removeExtension(outFile.getAbsolutePath()); File tempFile = new File(outFileName + (isTransformTemplate ? ".xml" : ".fo")); try (BufferedOutputStream fileStream = new BufferedOutputStream(new FileOutputStream(outFile)); ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); OutputStream exportOutput = useTempFile //Output to both the byte stream and to the temp file. ? new TeeOutputStream(byteOutputStream, new FileOutputStream(tempFile)) : byteOutputStream) { FopTask task; if (isTransformTemplate) { exportCharacter(character, exportOutput); ByteArrayInputStream inputStream = new ByteArrayInputStream(byteOutputStream.toByteArray()); task = FopTask.newFopTask(inputStream, templateFile, fileStream); } else { exportCharacter(character, templateFile, exportOutput); ByteArrayInputStream inputStream = new ByteArrayInputStream(byteOutputStream.toByteArray()); task = FopTask.newFopTask(inputStream, null, fileStream); } character.setDefaultOutputSheet(true, templateFile); task.run(); if (StringUtils.isNotBlank(task.getErrorMessages())) { Logging.errorPrint("BatchExporter.exportCharacterToPDF failed: " //$NON-NLS-1$ + task.getErrorMessages()); return false; } } catch (final IOException e) { Logging.errorPrint("BatchExporter.exportCharacterToPDF failed", e); //$NON-NLS-1$ return false; } catch (final ExportException e) { Logging.errorPrint("BatchExporter.exportCharacterToPDF failed", e); //$NON-NLS-1$ return false; } return true; } /** * Write a non PDF (e.g. html, text) character sheet for the character to * the output file. The character sheet will be built according to the * template file. If the output file exists it will be overwritten. * * @param character The already loaded character to be output. * @param outFile The file to which the character sheet is to be written. * @param templateFile The file that has the export template definition. * @return true if the export was successful, false if it failed in some way. */ public static boolean exportCharacterToNonPDF(CharacterFacade character, File outFile, File templateFile) { try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(outFile), "UTF-8"))) { character.export(new ExportHandler(templateFile), bw); character.setDefaultOutputSheet(false, templateFile); return true; } catch (final UnsupportedEncodingException e) { Logging.errorPrint( "Unable to create output file " + outFile.getAbsolutePath(), e); return false; } catch (final IOException e) { Logging.errorPrint( "Unable to create output file " + outFile.getAbsolutePath(), e); return false; } catch (final ExportException e) { // Error will already be reported to the log return false; } } /** * Get a temporary file name for outputting a character using a particular * output template. * @param templateFile The output template that will be used. * @return The temporary file, or null if it could not be created. */ public static File getTempOutputFilename(File templateFile) { String extension = ExportUtilities.getOutputExtension(templateFile.getName(), ExportUtilities.isPdfTemplate(templateFile)); try { // create a temporary file to view the character output return File.createTempFile(Constants.TEMPORARY_FILE_NAME, "."+extension, SettingsHandler.getTempPath()); } catch (final IOException ioe) { ShowMessageDelegate.showMessageDialog( "Could not create temporary preview file.", "PCGen", MessageType.ERROR); Logging.errorPrint("Could not create temporary preview file.", ioe); return null; } } /** * Write a PDF party sheet for the characters in the party to the output * file. The party sheet will be built according to the template file. If * the output file exists it will be overwritten. * * @param party The already loaded party of characters to be output. * @param outFile The file to which the party sheet is to be written. * @param templateFile The file that has the export template definition. * @return true if the export was successful, false if it failed in some way. */ public static boolean exportPartyToPDF(PartyFacade party, File outFile, File templateFile) { // We want the non pdf extension here for the intermediate file. String templateExtension = ExportUtilities.getOutputExtension(templateFile.getName(), false); boolean isTransformTemplate = "xslt".equalsIgnoreCase(templateExtension) || "xsl".equalsIgnoreCase(templateExtension); boolean useTempFile = PCGenSettings.OPTIONS_CONTEXT.initBoolean( PCGenSettings.OPTION_GENERATE_TEMP_FILE_WITH_PDF, false); String outFileName = FilenameUtils.removeExtension(outFile.getAbsolutePath()); File tempFile = new File(outFileName + (isTransformTemplate ? ".xml" : ".fo")); try (BufferedOutputStream fileStream = new BufferedOutputStream(new FileOutputStream(outFile)); ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); OutputStream exportOutput = useTempFile //Output to both the byte stream and to the temp file. ? new TeeOutputStream(byteOutputStream, new FileOutputStream(tempFile)) : byteOutputStream) { FopTask task; if (isTransformTemplate) { exportParty(party, exportOutput); ByteArrayInputStream inputStream = new ByteArrayInputStream(byteOutputStream.toByteArray()); task = FopTask.newFopTask(inputStream, templateFile, fileStream); } else { SettingsHandler.setSelectedPartyPDFOutputSheet(templateFile.getAbsolutePath()); exportParty(party, templateFile, exportOutput); ByteArrayInputStream inputStream = new ByteArrayInputStream(byteOutputStream.toByteArray()); task = FopTask.newFopTask(inputStream, null, fileStream); } task.run(); } catch (final IOException e) { Logging.errorPrint("BatchExporter.exportPartyToPDF failed", e); return false; } catch (final ExportException e) { Logging.errorPrint("BatchExporter.exportPartyToPDF failed", e); return false; } return true; } /** * Write a non PDF (e.g. html, text) party sheet for the characters in the * party to the output file. The party sheet will be built according to the * template file. If the output file exists it will be overwritten. * * @param party The already loaded party of characters to be output. * @param outFile The file to which the party sheet is to be written. * @param templateFile The file that has the export template definition. * @return true if the export was successful, false if it failed in some way. */ public static boolean exportPartyToNonPDF(PartyFacade party, File outFile, File templateFile) { try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(outFile), "UTF-8"))) { party.export(new ExportHandler(templateFile), bw); return true; } catch (final UnsupportedEncodingException e) { Logging.errorPrint( "Unable to create output file " + outFile.getAbsolutePath(), e); return false; } catch (final IOException e) { Logging.errorPrint( "Unable to create output file " + outFile.getAbsolutePath(), e); return false; } } /** * Write a party sheet for the characters in the party to the outputStream. The party sheet will * be selected based on the selected game mode and pcgen preferences. * * @param party the party to be output * @param outputStream the stream to output the party sheet to. * @throws IOException * @throws ExportException */ private static void exportParty(PartyFacade party, OutputStream outputStream) throws IOException, ExportException { try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"))) { for (final CharacterFacade character : party) { File templateFile = getXMLTemplate(character); character.export(new ExportHandler(templateFile), bw); } } } /** * Write a party sheet for the characters in the party to the ouputstream. The party sheet will * be built according to the template file. * * @param party the party to be output * @param templateFile The file that has the export template definition. * @param outputStream the stream to output the party sheet to. * @throws IOException * @throws ExportException */ private static void exportParty(PartyFacade party, File templateFile, OutputStream outputStream) throws IOException, ExportException { try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"))) { for (final CharacterFacade character : party) { character.export(new ExportHandler(templateFile), bw); } } } /** * Remove any temporary xml files produced while outputting characters. */ static void removeTemporaryFiles() { final boolean cleanUp = UIPropertyContext.getInstance().initBoolean( UIPropertyContext.CLEANUP_TEMP_FILES, true); if (!cleanUp) { return; } final String aDirectory = SettingsHandler.getTempPath() + File.separator; new File(aDirectory).list((aFile, aString) -> { try { if (aString.startsWith(Constants.TEMPORARY_FILE_NAME)) { final File tf = new File(aFile, aString); tf.delete(); } } catch (final Exception e) { Logging.errorPrint("removeTemporaryFiles", e); } return false; }); } /** * Exports a character to an OuputStream using the default template for the character's game * mode. This is more generic * method than writing to a file and the same effect can be achieved by passing in a * FileOutputStream. * * @param character the loaded CharacterFacade to export * @param outputStream the OutputStream that the character will be exported to * @throws IOException * @throws ExportException */ public static void exportCharacter(CharacterFacade character, OutputStream outputStream) throws IOException, ExportException { exportCharacter(character, getXMLTemplate(character), outputStream); } /** * Exports a character to an OutputStream using the given template file. The template file is * used to determine the type of character sheet that will be generated. This is more generic * method than writing to a file and the same effect can be achieved by passing in a * FileOutputStream. * * @param character the loaded CharacterFacade to export * @param templateFile the export template file for the ExportHandler to use * @param outputStream the OutputStream that the character will be exported to * @throws IOException * @throws ExportException */ private static void exportCharacter(CharacterFacade character, File templateFile, OutputStream outputStream) throws IOException, ExportException { try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"))) { character.export(new ExportHandler(templateFile), bw); } } private static File getXMLTemplate(CharacterFacade character) { File template = FileUtils.getFile(ConfigurationSettings.getSystemsDir(), "gameModes", character.getDataSet().getGameMode().getName(), "base.xml.ftl"); if (!template.exists()) { template = new File(ConfigurationSettings.getOutputSheetsDir(), "base.xml.ftl"); } return template; } /** * Create a default character sheet output file name based on the export * template type and the character file name. The output file will be * in the same folder as the character file. * * @param characterFilename The path to the character PCG file. * @return The default output file name. */ private String generateOutputFilename(String characterFilename) { File charFile = new File(characterFilename); String charname = charFile.getName(); String extension = ExportUtilities.getOutputExtension(exportTemplateFilename, isPdf); String outputName = charname.substring(0, charname.lastIndexOf('.')) + "." + extension; return new File(charFile.getParent(), outputName).getAbsolutePath(); } }