Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add XMP Exporter #3895

Merged
merged 6 commits into from
Apr 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}
}