package net.sf.jabref.export;
import net.sf.jabref.BibtexDatabase;
import net.sf.jabref.BibtexEntry;
import net.sf.jabref.Globals;
import net.sf.jabref.MetaData;
import net.sf.jabref.export.layout.Layout;
import net.sf.jabref.export.layout.LayoutHelper;
import javax.swing.filechooser.FileFilter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
/**
* Base class for export formats based on templates.
*
*/
public class ExportFormat implements IExportFormat {
String displayName;
String consoleName;
String lfFileName;
String directory;
String extension;
String encoding = null; // If this value is set, it will be used to override
// the default encoding for the basePanel.
FileFilter fileFilter;
boolean customExport = false;
/**
* Initialize another export format based on templates stored in dir with
* layoutFile lfFilename.
*
* @param displayName
* Name to display to the user.
* @param consoleName
* Name to call this format in the console.
* @param lfFileName
* Name of the main layout file.
* @param directory
* Directory in which to find the layout file.
* @param extension
* Should contain the . (for instance .txt).
*/
public ExportFormat(String displayName, String consoleName,
String lfFileName, String directory, String extension) {
this.displayName = displayName;
this.consoleName = consoleName;
this.lfFileName = lfFileName;
this.directory = directory;
this.extension = extension;
}
/** Empty default constructor for subclasses */
protected ExportFormat() {
// intentionally empty
}
/**
* Indicate whether this is a custom export. A custom export looks for its
* layout files using a normal file path, while a built-in export looks in
* the classpath.
*
* @param custom
* true to indicate a custom export format.
*/
public void setCustomExport(boolean custom) {
this.customExport = custom;
}
/**
* @see IExportFormat#getConsoleName()
*/
public String getConsoleName() {
return consoleName;
}
/**
* @see IExportFormat#getDisplayName()
*/
public String getDisplayName() {
return displayName;
}
/**
* Set an encoding which will be used in preference to the default value
* obtained from the basepanel.
* @param encoding The name of the encoding to use.
*/
protected void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* This method should return a reader from which the given layout file can
* be read.
*
* This standard implementation of this method will use the
* {@link FileActions#getReader(String)} method.
*
* Subclasses of ExportFormat are free to override and provide their own
* implementation.
*
* @param filename
* the file name
* @throws IOException
* if the reader could not be created
*
* @return a newly created reader
*/
protected Reader getReader(String filename) throws IOException {
// If this is a custom export, just use the given file name:
String dir;
if (customExport) {
dir = "";
} else {
dir = Globals.LAYOUT_PREFIX
+ (directory == null ? "" : directory + "/");
}
return FileActions.getReader(dir + filename);
}
/**
* Perform the export of {@code database}.
*
* @param database
* The database to export from.
* @param metaData
* The database's meta data.
* @param file
* the file to write the resulting export to
* @param encoding
* The encoding of the database
* @param entryIds
* Contains the IDs of all entries that should be exported. If
* <code>null</code>, all entries will be exported.
*
* @throws IOException
* if a problem occurred while trying to write to {@code writer}
* or read from required resources.
* @throws Exception
* if any other error occurred during export.
*
* @see net.sf.jabref.export.IExportFormat#performExport(net.sf.jabref.BibtexDatabase,
* net.sf.jabref.MetaData, java.lang.String, java.lang.String, java.util.Set)
*/
public void performExport(final BibtexDatabase database,
final MetaData metaData, final String file,
final String encoding, Set<String> entryIds) throws Exception {
File outFile = new File(file);
SaveSession ss = null;
if (this.encoding != null) {
try {
ss = getSaveSession(this.encoding, outFile);
} catch (IOException ex) {
// Perhaps the overriding encoding doesn't work?
// We will fall back on the default encoding.
ex.printStackTrace();
}
}
if (ss == null)
ss = getSaveSession(encoding, outFile);
VerifyingWriter ps = ss.getWriter();
Layout beginLayout = null;
Reader reader = null;
// Check if this export filter has bundled name formatters:
HashMap<String,String> customNameFormatters = readFormatterFile(lfFileName);
// Set a global field, so all layouts have access to the custom name formatters:
Globals.prefs.customExportNameFormatters = customNameFormatters;
ArrayList<String> missingFormatters = new ArrayList<String>(1);
// Print header
try {
reader = getReader(lfFileName + ".begin.layout");
LayoutHelper layoutHelper = new LayoutHelper(reader);
beginLayout = layoutHelper
.getLayoutFromText(Globals.FORMATTER_PACKAGE);
reader.close();
} catch (IOException ex) {
// If an exception was cast, export filter doesn't have a begin
// file.
}
// Write the header
if (beginLayout != null) {
ps.write(beginLayout.doLayout(database, encoding));
missingFormatters.addAll(beginLayout.getMissingFormatters());
}
/*
* Write database entries; entries will be sorted as they appear on the
* screen, or sorted by author, depending on Preferences. We also supply
* the Set entries - if we are to export only certain entries, it will
* be non-null, and be used to choose entries. Otherwise, it will be
* null, and be ignored.
*/
List<BibtexEntry> sorted = FileActions.getSortedEntries(database,
entryIds, false);
// Load default layout
reader = getReader(lfFileName + ".layout");
LayoutHelper layoutHelper = new LayoutHelper(reader);
Layout defLayout = layoutHelper
.getLayoutFromText(Globals.FORMATTER_PACKAGE);
reader.close();
if (defLayout != null) {
missingFormatters.addAll(defLayout.getMissingFormatters());
System.out.println(defLayout.getMissingFormatters());
}
HashMap<String, Layout> layouts = new HashMap<String, Layout>();
Layout layout;
ExportFormats.entryNumber = 0;
for (BibtexEntry entry : sorted) {
ExportFormats.entryNumber++; // Increment entry counter.
// Get the layout
String type = entry.getType().getName().toLowerCase();
if (layouts.containsKey(type))
layout = layouts.get(type);
else {
try {
// We try to get a type-specific layout for this entry.
reader = getReader(lfFileName + "." + type + ".layout");
layoutHelper = new LayoutHelper(reader);
layout = layoutHelper
.getLayoutFromText(Globals.FORMATTER_PACKAGE);
layouts.put(type, layout);
reader.close();
if (layout != null)
missingFormatters.addAll(layout.getMissingFormatters());
} catch (IOException ex) {
// The exception indicates that no type-specific layout
// exists, so we
// go with the default one.
layout = defLayout;
}
}
// Write the entry
ps.write(layout.doLayout(entry, database));
}
// Print footer
// changed section - begin (arudert)
Layout endLayout = null;
try {
reader = getReader(lfFileName + ".end.layout");
layoutHelper = new LayoutHelper(reader);
endLayout = layoutHelper
.getLayoutFromText(Globals.FORMATTER_PACKAGE);
reader.close();
} catch (IOException ex) {
// If an exception was thrown, export filter doesn't have an end
// file.
}
// Write footer
if (endLayout != null) {
ps.write(endLayout.doLayout(database, encoding));
missingFormatters.addAll(endLayout.getMissingFormatters());
}
// Clear custom name formatters:
Globals.prefs.customExportNameFormatters = null;
if (missingFormatters.size() > 0) {
StringBuilder sb = new StringBuilder("The following formatters could not be found").
append(": ");
for (Iterator<String> i = missingFormatters.iterator(); i.hasNext();) {
sb.append(i.next());
if (i.hasNext())
sb.append(", ");
}
System.err.println(sb.toString());
}
finalizeSaveSession(ss);
}
/**
* See if there is a name formatter file bundled with this export format. If so, read
* all the name formatters so they can be used by the filter layouts.
* @param lfFileName The layout file name.
*/
private HashMap<String, String> readFormatterFile(String lfFileName) {
HashMap<String,String> formatters = new HashMap<String, String>();
File formatterFile = new File(lfFileName + ".formatters");
if (formatterFile.exists()) {
Reader in = null;
try {
in = new FileReader(formatterFile);
if (in != null) {
// Ok, we found and opened the file. Read all contents:
StringBuilder sb = new StringBuilder();
int c;
while ((c = in.read()) != -1) {
sb.append((char)c);
}
String[] lines = sb.toString().split("\n");
// Go through each line:
for (int i=0; i<lines.length; i++) {
String line = lines[i].trim();
// Do not deal with empty lines:
if (line.length() == 0)
continue;
int index = line.indexOf(":"); // TODO: any need to accept escaped colons here?
if ((index > 0) && (index+1 < line.length())) {
String formatterName = line.substring(0, index);
String contents = line.substring(index+1);
//System.out.println("Name: '"+formatterName+"'");
//System.out.println("Contents: '"+contents+"'");
formatters.put(formatterName, contents);
}
}
}
} catch (IOException ex) {
// TODO: show error message here?
ex.printStackTrace();
} finally {
if (in != null)
try {
in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return formatters;
}
protected SaveSession getSaveSession(final String encoding,
final File outFile) throws IOException {
return new SaveSession(outFile, encoding, false);
}
/**
* @see net.sf.jabref.export.IExportFormat#getFileFilter()
*/
public FileFilter getFileFilter() {
if (fileFilter == null)
fileFilter = new ExportFileFilter(this, extension);
return fileFilter;
}
public void finalizeSaveSession(final SaveSession ss) throws Exception {
ss.getWriter().flush();
ss.getWriter().close();
if (!ss.getWriter().couldEncodeAll()) {
System.err.println("Could not encode...");
}
ss.commit();
}
}