Skip to content

Commit

Permalink
Add XMP Exporter (#3895)
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-manner authored and koppor committed Apr 12, 2018
1 parent be91964 commit 73fb95f
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 8 deletions.
4 changes: 3 additions & 1 deletion src/main/java/org/jabref/cli/ArgumentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.jabref.logic.search.SearchQuery;
import org.jabref.logic.shared.prefs.SharedDatabasePreferences;
import org.jabref.logic.util.OS;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.model.Defaults;
import org.jabref.model.EntryTypes;
import org.jabref.model.database.BibDatabase;
Expand Down Expand Up @@ -471,7 +472,8 @@ private void importPreferences() {
LayoutFormatterPreferences layoutPreferences = Globals.prefs
.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader);
SavePreferences savePreferences = SavePreferences.loadForExportFromPreferences(Globals.prefs);
Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences);
XmpPreferences xmpPreferences = Globals.prefs.getXMPPreferences();
Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, xmpPreferences);
} catch (JabRefException ex) {
LOGGER.error("Cannot import preferences", ex);
}
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/org/jabref/logic/exporter/ExporterFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.jabref.logic.journals.JournalAbbreviationLoader;
import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.util.FileType;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.preferences.JabRefPreferences;

public class ExporterFactory {
Expand All @@ -29,7 +30,7 @@ private ExporterFactory(List<Exporter> exporters) {
}

public static ExporterFactory create(Map<String, TemplateExporter> customFormats,
LayoutFormatterPreferences layoutPreferences, SavePreferences savePreferences) {
LayoutFormatterPreferences layoutPreferences, SavePreferences savePreferences, XmpPreferences xmpPreferences) {

List<Exporter> exporters = new ArrayList<>();

Expand All @@ -54,6 +55,7 @@ public static ExporterFactory create(Map<String, TemplateExporter> customFormats
exporters.add(new OpenDocumentSpreadsheetCreator());
exporters.add(new MSBibExporter());
exporters.add(new ModsExporter());
exporters.add(new XmpExporter(xmpPreferences));

// Now add custom export formats
exporters.addAll(customFormats.values());
Expand All @@ -65,7 +67,8 @@ public static ExporterFactory create(JabRefPreferences preferences, JournalAbbre
Map<String, TemplateExporter> customFormats = preferences.customExports.getCustomExportFormats(preferences, abbreviationLoader);
LayoutFormatterPreferences layoutPreferences = preferences.getLayoutFormatterPreferences(abbreviationLoader);
SavePreferences savePreferences = SavePreferences.loadForExportFromPreferences(preferences);
return create(customFormats, layoutPreferences, savePreferences);
XmpPreferences xmpPreferences = preferences.getXMPPreferences();
return create(customFormats, layoutPreferences, savePreferences, xmpPreferences);
}

/**
Expand Down
72 changes: 72 additions & 0 deletions src/main/java/org/jabref/logic/exporter/XmpExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.jabref.logic.exporter;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import org.jabref.logic.util.FileType;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.logic.xmp.XmpUtilWriter;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

/**
* A custom exporter to write bib entries to a .xmp file for further processing
* in other scenarios and applications. The xmp metadata are written in dublin
* core format.
*/
public class XmpExporter extends Exporter {

private static final String XMP_SPLIT_PATTERN = "split";

private final XmpPreferences xmpPreferences;

public XmpExporter(XmpPreferences xmpPreferences) {
super("xmp", FileType.PLAIN_XMP.getDescription(), FileType.PLAIN_XMP);
this.xmpPreferences = xmpPreferences;
}

@Override
public void export(BibDatabaseContext databaseContext, Path file, Charset encoding, List<BibEntry> entries) throws Exception {
Objects.requireNonNull(databaseContext);
Objects.requireNonNull(file);
Objects.requireNonNull(entries);

if (entries.isEmpty()) {
return;
}

// This is a distinction between writing all entries from the supplied list to a single .xmp file,
// or write every entry to a separate file.
if (file.getFileName().toString().trim().equals(XMP_SPLIT_PATTERN)) {

for (BibEntry entry : entries) {
// Avoid situations, where two cite keys are null
Path entryFile;
String suffix = entry.getId() + "_" + entry.getCiteKey() + ".xmp";
if (file.getParent() == null) {
entryFile = Paths.get(suffix);
} else {
entryFile = Paths.get(file.getParent().toString() + "/" + suffix);
}

this.writeBibToXmp(entryFile, Arrays.asList(entry), encoding);
}
} else {
this.writeBibToXmp(file, entries, encoding);
}
}

private void writeBibToXmp(Path file, List<BibEntry> entries, Charset encoding) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(file, encoding)) {
writer.write(XmpUtilWriter.generateXmpString(entries, this.xmpPreferences));
writer.flush();
}
}
}
1 change: 1 addition & 0 deletions src/main/java/org/jabref/logic/util/FileType.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public enum FileType {
SILVER_PLATTER(Localization.lang("%0 file", "SilverPlatter"), "dat", "txt"),
SIMPLE_HTML(Localization.lang("%0 file", Localization.lang("Simple HTML")), "html"),
XMP(Localization.lang("XMP-annotated PDF"), "pdf"),
PLAIN_XMP(Localization.lang("%0 file", "XMP"), "xmp"),

AUX(Localization.lang("%0 file", "AUX"), "aux"),
JSTYLE(Localization.lang("Style file"), "jstyle"),
Expand Down
61 changes: 59 additions & 2 deletions src/main/java/org/jabref/logic/xmp/XmpUtilWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
Expand Down Expand Up @@ -82,15 +84,38 @@ public static void writeXmp(Path file, BibEntry entry,
XmpUtilWriter.writeXmp(file, bibEntryList, database, xmpPreferences);
}

/**
* Writes the information of the bib entry to the dublin core schema using
* a custom extractor.
*
* @param dcSchema Dublin core schema, which is filled with the bib entry.
* @param entry The entry, which is added to the dublin core metadata.
* @param database maybenull An optional database which the given bibtex entries belong to, which will be used to
* resolve strings. If the database is null the strings will not be resolved.
* @param xmpPreferences The user's xmp preferences.
*/
private static void writeToDCSchema(DublinCoreSchema dcSchema, BibEntry entry, BibDatabase database,
XmpPreferences xmpPreferences) {

BibEntry resolvedEntry = XmpUtilWriter.getDefaultOrDatabaseEntry(entry, database);

DublinCoreExtractor dcExtractor = new DublinCoreExtractor(dcSchema, xmpPreferences, resolvedEntry);
dcExtractor.fillDublinCoreSchema();
writeToDCSchema(dcSchema, resolvedEntry, xmpPreferences);
}

/**
* Writes the information of the bib entry to the dublin core schema using
* a custom extractor.
*
* @param dcSchema Dublin core schema, which is filled with the bib entry.
* @param entry The entry, which is added to the dublin core metadata.
* @param xmpPreferences The user's xmp preferences.
*/
private static void writeToDCSchema(DublinCoreSchema dcSchema, BibEntry entry,
XmpPreferences xmpPreferences) {

DublinCoreExtractor dcExtractor = new DublinCoreExtractor(dcSchema, xmpPreferences, entry);
dcExtractor.fillDublinCoreSchema();
}

/**
* Try to write the given BibTexEntry as a DublinCore XMP Schema
Expand Down Expand Up @@ -159,6 +184,38 @@ private static void writeDublinCore(PDDocument document,
catalog.setMetadata(metadataStream);
}

/**
* This method generates an xmp metadata string in dublin core format.
* <br/>
*
* @param entries A list of entries, which are added to the dublin core metadata.
* @param xmpPreferences The user's xmp preferences.
*
* @return If something goes wrong (e.g. an exception is thrown), the method returns an empty string,
* otherwise it returns the xmp metadata as a string in dublin core format.
*/
public static String generateXmpString(List<BibEntry> entries, XmpPreferences xmpPreferences) {
XMPMetadata meta = XMPMetadata.createXMPMetadata();
for (BibEntry entry : entries) {
DublinCoreSchema dcSchema = meta.createAndAddDublinCoreSchema();
XmpUtilWriter.writeToDCSchema(dcSchema, entry, xmpPreferences);
}
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
XmpSerializer serializer = new XmpSerializer();
serializer.serialize(meta, os, true);
return os.toString(StandardCharsets.UTF_8.name());
} catch (TransformerException e) {
LOGGER.warn("Tranformation into xmp not possible: " + e.getMessage(), e);
return "";
} catch (UnsupportedEncodingException e) {
LOGGER.warn("Unsupported encoding to UTF-8 of bib entries in xmp metadata.", e);
return "";
} catch (IOException e) {
LOGGER.warn("IO Exception thrown by closing the output stream.", e);
return "";
}
}

/**
* Try to write the given BibTexEntry in the Document Information (the
* properties of the pdf).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Map;

import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

Expand All @@ -36,7 +37,8 @@ public void setUp() {
Map<String, TemplateExporter> customFormats = new HashMap<>();
LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS);
SavePreferences savePreferences = mock(SavePreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences);
XmpPreferences xmpPreferences = mock(XmpPreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences);

exportFormat = exporterFactory.getExporterByName("oocsv").get();

Expand Down
4 changes: 3 additions & 1 deletion src/test/java/org/jabref/logic/exporter/ExporterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Map;

import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

Expand Down Expand Up @@ -57,7 +58,8 @@ public static Collection<Object[]> exportFormats() {
Map<String, TemplateExporter> customFormats = new HashMap<>();
LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class);
SavePreferences savePreferences = mock(SavePreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences);
XmpPreferences xmpPreferences = mock(XmpPreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences);

for (Exporter format : exporterFactory.getExporters()) {
result.add(new Object[]{format, format.getDisplayName()});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Map;

import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

Expand Down Expand Up @@ -37,7 +38,8 @@ public void setUp() {
Map<String, TemplateExporter> customFormats = new HashMap<>();
LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS);
SavePreferences savePreferences = mock(SavePreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences);
XmpPreferences xmpPreferences = mock(XmpPreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences);

exportFormat = exporterFactory.getExporterByName("html").get();

Expand Down
112 changes: 112 additions & 0 deletions src/test/java/org/jabref/logic/exporter/XmpExporterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.jabref.logic.exporter;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.xmp.XmpPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Answers;

import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;

public class XmpExporterTest {

private Exporter exporter;
private BibDatabaseContext databaseContext;
private Charset encoding;

@Rule public TemporaryFolder testFolder = new TemporaryFolder();

@Before
public void setUp() {
Map<String, TemplateExporter> customFormats = new HashMap<>();
LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS);
SavePreferences savePreferences = mock(SavePreferences.class);
XmpPreferences xmpPreferences = mock(XmpPreferences.class);
ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences);

exporter = exporterFactory.getExporterByName("xmp").get();

databaseContext = new BibDatabaseContext();
encoding = StandardCharsets.UTF_8;
}

@Test
public void exportSingleEntry() throws Exception {

Path file = testFolder.newFile().toPath();

BibEntry entry = new BibEntry();
entry.setField("author", "Alan Turing");

exporter.export(databaseContext, file, encoding, Arrays.asList(entry));

List<String> lines = Files.readAllLines(file);
assertTrue(lines.size() == 21);
assertEquals("<rdf:li>Alan Turing</rdf:li>", lines.get(7).trim());
}

@Test
public void writeMutlipleEntriesInASingleFile() throws Exception {

Path file = testFolder.newFile().toPath();

BibEntry entryTuring = new BibEntry();
entryTuring.setField("author", "Alan Turing");

BibEntry entryArmbrust = new BibEntry();
entryArmbrust.setField("author", "Michael Armbrust");
entryArmbrust.setCiteKey("Armbrust2010");

exporter.export(databaseContext, file, encoding, Arrays.asList(entryTuring, entryArmbrust));

List<String> lines = Files.readAllLines(file);
assertTrue(lines.size() == 39);
assertEquals("<rdf:li>Alan Turing</rdf:li>", lines.get(7).trim());
assertEquals("<rdf:li>Michael Armbrust</rdf:li>", lines.get(20).trim());
}

@Test
public void writeMultipleEntriesInDifferentFiles() throws Exception {

Path file = testFolder.newFile("split").toPath();

BibEntry entryTuring = new BibEntry();
entryTuring.setField("author", "Alan Turing");

BibEntry entryArmbrust = new BibEntry();
entryArmbrust.setField("author", "Michael Armbrust");
entryArmbrust.setCiteKey("Armbrust2010");

exporter.export(databaseContext, file, encoding, Arrays.asList(entryTuring, entryArmbrust));

List<String> lines = Files.readAllLines(file);
assertTrue(lines.size() == 0);

Path fileTuring = Paths.get(file.getParent().toString() + "/" + entryTuring.getId() + "_null.xmp");
List<String> linesTuring = Files.readAllLines(fileTuring);
assertTrue(linesTuring.size() == 21);
assertEquals("<rdf:li>Alan Turing</rdf:li>", linesTuring.get(7).trim());

Path fileArmbrust = Paths.get(file.getParent().toString() + "/" + entryArmbrust.getId() + "_Armbrust2010.xmp");
List<String> linesArmbrust = Files.readAllLines(fileArmbrust);
assertTrue(linesArmbrust.size() == 26);
assertEquals("<rdf:li>Michael Armbrust</rdf:li>", linesArmbrust.get(7).trim());
}
}

0 comments on commit 73fb95f

Please sign in to comment.