From 577f51c9c2270f50c4e025a2038c8a75782b0499 Mon Sep 17 00:00:00 2001 From: Benedikt Tutzer Date: Tue, 31 Aug 2021 14:52:33 +0200 Subject: [PATCH] Write embedded bib in addition to XMP metadata (#8037) * EmbeddedBibFilePdfExporter * Standard Action * Gui triggers * Fix empty optional * CLI triggers * Localization * CHANGELOG * use System streams instead of Logger for user out * Typo in CHANGELOG.md Co-authored-by: Christoph Co-authored-by: Benedikt Tutzer Co-authored-by: Christoph --- CHANGELOG.md | 1 + .../org/jabref/cli/ArgumentProcessor.java | 78 +++++--- src/main/java/org/jabref/cli/JabRefCLI.java | 32 ++- src/main/java/org/jabref/gui/JabRefFrame.java | 4 +- src/main/java/org/jabref/gui/JabRefMain.java | 4 +- .../jabref/gui/actions/StandardActions.java | 2 +- .../jabref/gui/exporter/ExportCommand.java | 3 +- ...ion.java => WriteMetadataToPdfAction.java} | 63 ++++-- .../gui/fieldeditors/LinkedFileViewModel.java | 10 +- .../gui/fieldeditors/LinkedFilesEditor.java | 12 +- .../PreferencesDialogViewModel.java | 3 +- .../exporter/EmbeddedBibFilePdfExporter.java | 112 +++++++++++ .../logic/exporter/ExporterFactory.java | 9 +- src/main/resources/l10n/JabRef_en.properties | 16 +- .../exporter/ExportToClipboardActionTest.java | 5 +- .../logic/exporter/CsvExportFormatTest.java | 5 +- .../logic/exporter/DocBook5ExporterTest.java | 5 +- .../logic/exporter/DocbookExporterTest.java | 5 +- .../EmbeddedBibFilePdfExporterTest.java | 186 ++++++++++++++++++ .../jabref/logic/exporter/ExporterTest.java | 11 +- .../logic/exporter/HtmlExportFormatTest.java | 5 +- .../logic/exporter/YamlExporterTest.java | 5 +- 22 files changed, 499 insertions(+), 77 deletions(-) rename src/main/java/org/jabref/gui/exporter/{WriteXMPAction.java => WriteMetadataToPdfAction.java} (74%) create mode 100644 src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java create mode 100644 src/test/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporterTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 591970d3715..9e415fe2708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We merged the barely used ImportSettingsTab and the CustomizationTab in the preferences into one single tab and moved the option to allow Integers in Edition Fields in Bibtex-Mode to the EntryEditor tab. [#7849](https://github.com/JabRef/jabref/pull/7849) - We moved the export order in the preferences from `File` to `Import and Export`. [#7935](https://github.com/JabRef/jabref/pull/7935) - We reworked the export order in the preferences and the save order in the library preferences. You can now set more than three sort criteria in your library preferences. [#7935](https://github.com/JabRef/jabref/pull/7935) +- The metadata-to-pdf actions now also embeds the bibfile to the PDF. [#8037](https://github.com/JabRef/jabref/pull/8037) ### Fixed diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 5943d4ee960..622208295e2 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -18,10 +18,12 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.JabRefException; +import org.jabref.logic.bibtex.FieldWriterPreferences; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibDatabaseWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.exporter.SavePreferences; @@ -51,6 +53,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileHelper; @@ -224,13 +227,22 @@ private List processArguments() { automaticallySetFileLinks(loaded); } - if (cli.isWriteXMPtoPdf()) { + if (cli.isWriteXMPtoPdf() && cli.isEmbeddBibfileInPdf() || cli.isWriteMetadatatoPdf() && (cli.isWriteXMPtoPdf() || cli.isEmbeddBibfileInPdf())) { + System.err.println("Give only one of [writeXMPtoPdf, embeddBibfileInPdf, writeMetadatatoPdf]"); + } + + if (cli.isWriteMetadatatoPdf() || cli.isWriteXMPtoPdf() || cli.isEmbeddBibfileInPdf()) { if (!loaded.isEmpty()) { - writeXMPtoPdf(loaded, - cli.getWriteXMPtoPdf(), + writeMetadatatoPdf(loaded, + cli.getWriteMetadatatoPdf(), preferencesService.getDefaultEncoding(), preferencesService.getXmpPreferences(), - preferencesService.getFilePreferences()); + preferencesService.getFilePreferences(), + preferencesService.getDefaultBibDatabaseMode(), + Globals.entryTypesManager, + preferencesService.getFieldWriterPreferences(), + cli.isWriteXMPtoPdf() || cli.isWriteMetadatatoPdf(), + cli.isEmbeddBibfileInPdf() || cli.isWriteMetadatatoPdf()); } } @@ -258,7 +270,7 @@ private List processArguments() { return loaded; } - private void writeXMPtoPdf(List loaded, String filesAndCitekeys, Charset encoding, XmpPreferences xmpPreferences, FilePreferences filePreferences) { + private void writeMetadatatoPdf(List loaded, String filesAndCitekeys, Charset encoding, XmpPreferences xmpPreferences, FilePreferences filePreferences, BibDatabaseMode databaseMode, BibEntryTypesManager entryTypesManager, FieldWriterPreferences fieldWriterPreferences, boolean writeXMP, boolean embeddBibfile) { if (loaded.isEmpty()) { LOGGER.error("The write xmp option depends on a valid import option."); return; @@ -268,10 +280,11 @@ private void writeXMPtoPdf(List loaded, String filesAndCitekeys, C BibDatabase dataBase = pr.getDatabase(); XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences); + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldWriterPreferences); if ("all".equals(filesAndCitekeys)) { for (BibEntry entry : dataBase.getEntries()) { - writeXMPtoPDFsOfEntry(databaseContext, entry.getCitationKey().orElse(""), entry, encoding, filePreferences, xmpPdfExporter); + writeMetadatatoPDFsOfEntry(databaseContext, entry.getCitationKey().orElse(""), entry, encoding, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); } return; } @@ -286,37 +299,46 @@ private void writeXMPtoPdf(List loaded, String filesAndCitekeys, C } } - writeXMPtoPdfByCitekey(databaseContext, dataBase, citeKeys, encoding, filePreferences, xmpPdfExporter); - writeXMPtoPdfByFileNames(databaseContext, dataBase, pdfs, encoding, filePreferences, xmpPdfExporter); + writeMetadatatoPdfByCitekey(databaseContext, dataBase, citeKeys, encoding, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); + writeMetadatatoPdfByFileNames(databaseContext, dataBase, pdfs, encoding, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); } - private void writeXMPtoPDFsOfEntry(BibDatabaseContext databaseContext, String citeKey, BibEntry entry, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter) { + private void writeMetadatatoPDFsOfEntry(BibDatabaseContext databaseContext, String citeKey, BibEntry entry, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, boolean writeXMP, boolean embeddBibfile) { try { - if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, encoding, filePreferences, entry, List.of(entry))) { - LOGGER.info(String.format("Successfully written XMP metadata on at least one linked file of %s", citeKey)); - } else { - LOGGER.error(String.format("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.", citeKey)); + if (writeXMP) { + if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, encoding, filePreferences, entry, List.of(entry))) { + System.out.println(String.format("Successfully written XMP metadata on at least one linked file of %s", citeKey)); + } else { + System.err.println(String.format("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.", citeKey)); + } + } + if (embeddBibfile) { + if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, encoding, filePreferences, entry, List.of(entry))) { + System.out.println(String.format("Successfully embedded metadata on at least one linked file of %s", citeKey)); + } else { + System.out.println(String.format("Cannot embedd metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.", citeKey)); + } } } catch (Exception e) { - LOGGER.error(String.format("Failed writing XMP metadata on a linked file of %s.", citeKey)); + LOGGER.error(String.format("Failed writing metadata on a linked file of %s.", citeKey)); } } - private void writeXMPtoPdfByCitekey(BibDatabaseContext databaseContext, BibDatabase dataBase, Vector citeKeys, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter) { + private void writeMetadatatoPdfByCitekey(BibDatabaseContext databaseContext, BibDatabase dataBase, Vector citeKeys, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, boolean writeXMP, boolean embeddBibfile) { for (String citeKey : citeKeys) { List bibEntryList = dataBase.getEntriesByCitationKey(citeKey); if (bibEntryList.isEmpty()) { - LOGGER.error(String.format("Skipped - Cannot find %s in library.", citeKey)); + System.err.println(String.format("Skipped - Cannot find %s in library.", citeKey)); continue; } for (BibEntry entry : bibEntryList) { - writeXMPtoPDFsOfEntry(databaseContext, citeKey, entry, encoding, filePreferences, xmpPdfExporter); + writeMetadatatoPDFsOfEntry(databaseContext, citeKey, entry, encoding, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, writeXMP, embeddBibfile); } } } - private void writeXMPtoPdfByFileNames(BibDatabaseContext databaseContext, BibDatabase dataBase, Vector fileNames, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter) { + private void writeMetadatatoPdfByFileNames(BibDatabaseContext databaseContext, BibDatabase dataBase, Vector fileNames, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter, EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, boolean writeXMP, boolean embeddBibfile) { for (String fileName : fileNames) { Path filePath = Path.of(fileName); if (!filePath.isAbsolute()) { @@ -324,10 +346,19 @@ private void writeXMPtoPdfByFileNames(BibDatabaseContext databaseContext, BibDat } if (Files.exists(filePath)) { try { - if (xmpPdfExporter.exportToFileByPath(databaseContext, dataBase, encoding, filePreferences, filePath)) { - LOGGER.info(String.format("Successfully written XMP metadata of at least one entry to %s", fileName)); - } else { - LOGGER.error(String.format("File %s is not linked to any entry in database.", fileName)); + if (writeXMP) { + if (xmpPdfExporter.exportToFileByPath(databaseContext, dataBase, encoding, filePreferences, filePath)) { + System.out.println(String.format("Successfully written XMP metadata of at least one entry to %s", fileName)); + } else { + System.out.println(String.format("File %s is not linked to any entry in database.", fileName)); + } + } + if (embeddBibfile) { + if (embeddedBibFilePdfExporter.exportToFileByPath(databaseContext, dataBase, encoding, filePreferences, filePath)) { + System.out.println(String.format("Successfully embedded XMP metadata of at least one entry to %s", fileName)); + } else { + System.out.println(String.format("File %s is not linked to any entry in database.", fileName)); + } } } catch (IOException e) { LOGGER.error("Error accessing file '{}'.", fileName); @@ -567,7 +598,8 @@ private void importPreferences() { preferencesService.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository); SavePreferences savePreferences = preferencesService.getSavePreferencesForExport(); XmpPreferences xmpPreferences = preferencesService.getXmpPreferences(); - Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, xmpPreferences); + BibDatabaseMode bibDatabaseMode = preferencesService.getDefaultBibDatabaseMode(); + Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, xmpPreferences, bibDatabaseMode, Globals.entryTypesManager); } catch (JabRefException ex) { LOGGER.error("Cannot import preferences", ex); } diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index 6f6e1b75e2d..1de6d544c00 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -151,8 +151,18 @@ public boolean isWriteXMPtoPdf() { return cl.hasOption("writeXMPtoPdf"); } - public String getWriteXMPtoPdf() { - return cl.getOptionValue("writeXMPtoPdf"); + public boolean isEmbeddBibfileInPdf() { + return cl.hasOption("embeddBibfileInPdf"); + } + + public boolean isWriteMetadatatoPdf() { + return cl.hasOption("writeMetadatatoPdf"); + } + + public String getWriteMetadatatoPdf() { + return cl.hasOption("writeMetadatatoPdf") ? cl.getOptionValue("writeMetadatatoPdf") : + cl.hasOption("writeXMPtoPdf") ? cl.getOptionValue("writeXMPtoPdf") : + cl.hasOption("embeddBibfileInPdf") ? cl.getOptionValue("embeddBibfileInPdf") : null; } private static Options getOptions() { @@ -251,13 +261,29 @@ private static Options getOptions() { .build()); options.addOption(Option - .builder("w") + .builder() .longOpt("writeXMPtoPdf") .desc(String.format("%s: '%s'", Localization.lang("Write BibTeXEntry as XMP metadata to PDF."), "-w pathToMyOwnPaper.pdf")) .hasArg() .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") .build()); + options.addOption(Option + .builder() + .longOpt("embeddBibfileInPdf") + .desc(String.format("%s: '%s'", Localization.lang("Embedd BibTeXEntry in PDF."), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder("w") + .longOpt("writeMetadatatoPdf") + .desc(String.format("%s: '%s'", Localization.lang("Write BibTeXEntry as metadata to PDF."), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + return options; } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index fc2abd11554..ca566313ffe 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -77,7 +77,7 @@ import org.jabref.gui.exporter.SaveAction; import org.jabref.gui.exporter.SaveAllAction; import org.jabref.gui.exporter.SaveDatabaseAction; -import org.jabref.gui.exporter.WriteXMPAction; +import org.jabref.gui.exporter.WriteMetadataToPdfAction; import org.jabref.gui.externalfiles.AutoLinkFilesAction; import org.jabref.gui.externalfiles.DownloadFullTextAction; import org.jabref.gui.externalfiles.FindUnlinkedFilesAction; @@ -810,7 +810,7 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.WRITE_XMP, new WriteXMPAction(stateManager, dialogService, prefs)), + factory.createMenuItem(StandardActions.WRITE_METADATA_TO_PDF, new WriteMetadataToPdfAction(stateManager, prefs.getDefaultBibDatabaseMode(), Globals.entryTypesManager, prefs.getFieldWriterPreferences(), dialogService, taskExecutor, prefs.getFilePreferences(), prefs.getXmpPreferences(), prefs.getDefaultEncoding())), factory.createMenuItem(StandardActions.COPY_LINKED_FILES, new CopyFilesAction(dialogService, prefs, stateManager)), new SeparatorMenuItem(), diff --git a/src/main/java/org/jabref/gui/JabRefMain.java b/src/main/java/org/jabref/gui/JabRefMain.java index 37f0183e236..ca1074ead05 100644 --- a/src/main/java/org/jabref/gui/JabRefMain.java +++ b/src/main/java/org/jabref/gui/JabRefMain.java @@ -132,7 +132,9 @@ private static void applyPreferences(PreferencesService preferences) { preferences.getCustomExportFormats(Globals.journalAbbreviationRepository), preferences.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository), preferences.getSavePreferencesForExport(), - preferences.getXmpPreferences()); + preferences.getXmpPreferences(), + preferences.getDefaultBibDatabaseMode(), + Globals.entryTypesManager); // Initialize protected terms loader Globals.protectedTermsLoader = new ProtectedTermsLoader(preferences.getProtectedTermsPreferences()); diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 2aca942782f..d8748ea19c2 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -88,7 +88,7 @@ public enum StandardActions implements Action { PARSE_LATEX(Localization.lang("Search for citations in LaTeX files..."), IconTheme.JabRefIcons.LATEX_CITATIONS), NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), - WRITE_XMP(Localization.lang("Write XMP metadata to PDFs"), Localization.lang("Will write XMP metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_XMP), + WRITE_METADATA_TO_PDF(Localization.lang("Write XMP metadata to PDFs"), Localization.lang("Will write XMP metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_XMP), START_NEW_STUDY(Localization.lang("Start new systematic literature review")), SEARCH_FOR_EXISTING_STUDY(Localization.lang("Perform search for existing systematic literature review")), OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")), diff --git a/src/main/java/org/jabref/gui/exporter/ExportCommand.java b/src/main/java/org/jabref/gui/exporter/ExportCommand.java index 5c427d9e02c..e3adc87c201 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportCommand.java +++ b/src/main/java/org/jabref/gui/exporter/ExportCommand.java @@ -61,7 +61,8 @@ public void execute() { .sorted(Comparator.comparing(Exporter::getName)) .collect(Collectors.toList()); - Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, xmpPreferences); + Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, + xmpPreferences, preferences.getDefaultBibDatabaseMode(), Globals.entryTypesManager); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() .addExtensionFilter(FileFilterConverter.exporterToExtensionFilter(exporters)) .withDefaultExtension(preferences.getImportExportPreferences().getLastExportExtension()) diff --git a/src/main/java/org/jabref/gui/exporter/WriteXMPAction.java b/src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java similarity index 74% rename from src/main/java/org/jabref/gui/exporter/WriteXMPAction.java rename to src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java index fd76757ec84..0f6dd42b7d4 100644 --- a/src/main/java/org/jabref/gui/exporter/WriteXMPAction.java +++ b/src/main/java/org/jabref/gui/exporter/WriteMetadataToPdfAction.java @@ -1,5 +1,6 @@ package org.jabref.gui.exporter; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; @@ -22,23 +23,35 @@ import org.jabref.gui.DialogService; import org.jabref.gui.FXDialog; -import org.jabref.gui.Globals; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.StandardFileType; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.logic.xmp.XmpPreferences; import org.jabref.logic.xmp.XmpUtilWriter; import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.PreferencesService; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.preferences.FilePreferences; import static org.jabref.gui.actions.ActionHelper.needsDatabase; -public class WriteXMPAction extends SimpleCommand { +public class WriteMetadataToPdfAction extends SimpleCommand { private final StateManager stateManager; private final DialogService dialogService; - private final PreferencesService preferencesService; + private final TaskExecutor taskExecutor; + private final FilePreferences filePreferences; + private final XmpPreferences xmpPreferences; + private final EmbeddedBibFilePdfExporter embeddedBibExporter; + private final Charset encoding; private OptionsDialog optionsDialog; @@ -50,10 +63,14 @@ public class WriteXMPAction extends SimpleCommand { private int entriesChanged; private int errors; - public WriteXMPAction(StateManager stateManager, DialogService dialogService, PreferencesService preferencesService) { + public WriteMetadataToPdfAction(StateManager stateManager, BibDatabaseMode databaseMode, BibEntryTypesManager entryTypesManager, FieldWriterPreferences fieldWriterPreferences, DialogService dialogService, TaskExecutor taskExecutor, FilePreferences filePreferences, XmpPreferences xmpPreferences, Charset encoding) { this.stateManager = stateManager; this.dialogService = dialogService; - this.preferencesService = preferencesService; + this.taskExecutor = taskExecutor; + this.filePreferences = filePreferences; + this.xmpPreferences = xmpPreferences; + this.encoding = encoding; + this.embeddedBibExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldWriterPreferences); this.executable.bind(needsDatabase(stateManager)); } @@ -61,8 +78,8 @@ public WriteXMPAction(StateManager stateManager, DialogService dialogService, Pr @Override public void execute() { init(); - BackgroundTask.wrap(this::writeXMP) - .executeWith(Globals.TASK_EXECUTOR); + BackgroundTask.wrap(this::writeMetadata) + .executeWith(taskExecutor); } public void init() { @@ -80,14 +97,14 @@ public void init() { if (entries.isEmpty()) { dialogService.showErrorDialogAndWait( - Localization.lang("Write XMP metadata"), + Localization.lang("Write metadata to PDF files"), Localization.lang("This operation requires one or more entries to be selected.")); shouldContinue = false; return; } else { boolean confirm = dialogService.showConfirmationDialogAndWait( - Localization.lang("Write XMP metadata"), - Localization.lang("Write XMP metadata for all PDFs in current library?")); + Localization.lang("Write metadata to PDF files"), + Localization.lang("Write metadata for all PDFs in current library?")); if (confirm) { shouldContinue = false; return; @@ -102,10 +119,10 @@ public void init() { } optionsDialog.open(); - dialogService.notify(Localization.lang("Writing XMP metadata...")); + dialogService.notify(Localization.lang("Writing metadata...")); } - private void writeXMP() { + private void writeMetadata() { if (!shouldContinue || stateManager.getActiveDatabase().isEmpty()) { return; } @@ -113,10 +130,10 @@ private void writeXMP() { for (BibEntry entry : entries) { // Make a list of all PDFs linked from this entry: List files = entry.getFiles().stream() - .filter(file -> file.getFileType().equalsIgnoreCase("pdf")) - .map(file -> file.findIn(stateManager.getActiveDatabase().get(), preferencesService.getFilePreferences())) + .map(file -> file.findIn(stateManager.getActiveDatabase().get(), filePreferences)) .filter(Optional::isPresent) .map(Optional::get) + .filter(path -> StandardFileType.PDF.getExtensions().contains(FileUtil.getFileExtension(path))) .collect(Collectors.toList()); Platform.runLater(() -> optionsDialog.getProgressArea() @@ -130,7 +147,7 @@ private void writeXMP() { for (Path file : files) { if (Files.exists(file)) { try { - XmpUtilWriter.writeXmp(file, entry, database, preferencesService.getXmpPreferences()); + writeMetadataToFile(file, entry, stateManager.getActiveDatabase().get(), database); Platform.runLater( () -> optionsDialog.getProgressArea().appendText(" " + Localization.lang("OK") + ".\n")); entriesChanged++; @@ -162,7 +179,7 @@ private void writeXMP() { Platform.runLater(() -> { optionsDialog.getProgressArea() .appendText("\n" - + Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", String + + Localization.lang("Finished writing metadata for %0 file (%1 skipped, %2 errors).", String .valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); optionsDialog.done(); }); @@ -171,10 +188,18 @@ private void writeXMP() { return; } - dialogService.notify(Localization.lang("Finished writing XMP for %0 file (%1 skipped, %2 errors).", + dialogService.notify(Localization.lang("Finished writing metadata for %0 file (%1 skipped, %2 errors).", String.valueOf(entriesChanged), String.valueOf(skipped), String.valueOf(errors))); } + private void writeMetadataToFile(Path file, BibEntry entry, BibDatabaseContext databaseContext, BibDatabase database) throws Exception { + // XMP + XmpUtilWriter.writeXmp(file, entry, database, xmpPreferences); + + // Embedded Bib File + embeddedBibExporter.exportToFileByPath(databaseContext, database, encoding, filePreferences, file); + } + class OptionsDialog extends FXDialog { private final Button okButton = new Button(Localization.lang("OK")); @@ -185,7 +210,7 @@ class OptionsDialog extends FXDialog { private final TextArea progressArea; public OptionsDialog() { - super(AlertType.NONE, Localization.lang("Writing XMP metadata for selected entries..."), false); + super(AlertType.NONE, Localization.lang("Writing metadata for selected entries..."), false); okButton.setDisable(true); okButton.setOnAction(e -> dispose()); okButton.setPrefSize(100, 30); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index 35bacdaaac2..8e275a61921 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -28,6 +28,7 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; +import org.jabref.gui.Globals; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.externalfiles.FileDownloadTask; import org.jabref.gui.externalfiletype.ExternalFileType; @@ -40,6 +41,7 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; import org.jabref.logic.externalfiles.LinkedFileHandler; import org.jabref.logic.importer.Importer; import org.jabref.logic.importer.ParserResult; @@ -397,8 +399,7 @@ public void edit() { }); } - public void writeXMPMetadata() { - // Localization.lang("Writing XMP metadata...") + public void writeMetadataToPdf() { BackgroundTask writeTask = BackgroundTask.wrap(() -> { Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); if (file.isEmpty()) { @@ -407,6 +408,9 @@ public void writeXMPMetadata() { } else { try { XmpUtilWriter.writeXmp(file.get(), entry, databaseContext.getDatabase(), preferences.getXmpPreferences()); + + EmbeddedBibFilePdfExporter embeddedBibExporter = new EmbeddedBibFilePdfExporter(preferences.getDefaultBibDatabaseMode(), Globals.entryTypesManager, preferences.getFieldWriterPreferences()); + embeddedBibExporter.exportToFileByPath(databaseContext, databaseContext.getDatabase(), preferences.getDefaultEncoding(), preferences.getFilePreferences(), file.get()); } catch (IOException | TransformerException ex) { // TODO: Print error message // Localization.lang("Error while writing") + " '" + file.toString() + "': " + ex; @@ -415,8 +419,6 @@ public void writeXMPMetadata() { return null; }); - // Localization.lang("Finished writing XMP metadata.") - // TODO: Show progress taskExecutor.execute(writeTask); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index 6809b5d058f..138e4ecec13 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -175,11 +175,11 @@ private Node createFileDisplay(LinkedFileViewModel linkedFile) { acceptAutoLinkedFile.setOnAction(event -> linkedFile.acceptAsLinked()); acceptAutoLinkedFile.getStyleClass().setAll("icon-button"); - Button writeXMPMetadata = IconTheme.JabRefIcons.IMPORT.asButton(); - writeXMPMetadata.setTooltip(new Tooltip(Localization.lang("Write BibTeXEntry as XMP metadata to PDF."))); - writeXMPMetadata.visibleProperty().bind(linkedFile.isOfflinePdfProperty()); - writeXMPMetadata.setOnAction(event -> linkedFile.writeXMPMetadata()); - writeXMPMetadata.getStyleClass().setAll("icon-button"); + Button writeMetadataToPdf = IconTheme.JabRefIcons.IMPORT.asButton(); + writeMetadataToPdf.setTooltip(new Tooltip(Localization.lang("Write BibTeXEntry metadata to PDF."))); + writeMetadataToPdf.visibleProperty().bind(linkedFile.isOfflinePdfProperty()); + writeMetadataToPdf.setOnAction(event -> linkedFile.writeMetadataToPdf()); + writeMetadataToPdf.getStyleClass().setAll("icon-button"); Button parsePdfMetadata = IconTheme.JabRefIcons.FILE_SEARCH.asButton(); parsePdfMetadata.setTooltip(new Tooltip(Localization.lang("Parse Metadata from PDF."))); @@ -193,7 +193,7 @@ private Node createFileDisplay(LinkedFileViewModel linkedFile) { HBox container = new HBox(10); container.setPrefHeight(Double.NEGATIVE_INFINITY); - container.getChildren().addAll(acceptAutoLinkedFile, info, writeXMPMetadata, parsePdfMetadata); + container.getChildren().addAll(acceptAutoLinkedFile, info, writeMetadataToPdf, parsePdfMetadata); return container; } diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java index c7453014b58..4c116e7fd15 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java @@ -173,7 +173,8 @@ private void updateAfterPreferenceChanges() { LayoutFormatterPreferences layoutPreferences = preferences.getLayoutFormatterPreferences(Globals.journalAbbreviationRepository); SavePreferences savePreferences = preferences.getSavePreferencesForExport(); XmpPreferences xmpPreferences = preferences.getXmpPreferences(); - Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, xmpPreferences); + Globals.exportFactory = ExporterFactory.create(customExporters, layoutPreferences, savePreferences, + xmpPreferences, preferences.getDefaultBibDatabaseMode(), Globals.entryTypesManager); ExternalApplicationsPreferences externalApplicationsPreferences = preferences.getExternalApplicationsPreferences(); PushToApplicationsManager manager = frame.getPushToApplicationsManager(); diff --git a/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java b/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java new file mode 100644 index 00000000000..480d7cde05a --- /dev/null +++ b/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java @@ -0,0 +1,112 @@ +package org.jabref.logic.exporter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.util.StandardFileType; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; + +/** + * A custom exporter to write bib entries to an embedded bib file. + */ +public class EmbeddedBibFilePdfExporter extends Exporter { + + public static String EMBEDDED_FILE_NAME = "main.bib"; + + private final BibDatabaseMode bibDatabaseMode; + private final BibEntryTypesManager bibEntryTypesManager; + private final FieldWriterPreferences fieldWriterPreferences; + + public EmbeddedBibFilePdfExporter(BibDatabaseMode bibDatabaseMode, BibEntryTypesManager bibEntryTypesManager, FieldWriterPreferences fieldWriterPreferences) { + super("bib", "Embedded BibTeX", StandardFileType.PDF); + this.bibDatabaseMode = bibDatabaseMode; + this.bibEntryTypesManager = bibEntryTypesManager; + this.fieldWriterPreferences = fieldWriterPreferences; + } + + /** + * @param databaseContext the database to export from + * @param file the file to write to. If it contains "split", then the output is split into different files + * @param encoding the encoding to use + * @param entries a list containing all entries that should be exported + */ + @Override + public void export(BibDatabaseContext databaseContext, Path file, Charset encoding, List entries) throws Exception { + Objects.requireNonNull(databaseContext); + Objects.requireNonNull(file); + Objects.requireNonNull(entries); + + String bibString = getBibString(entries); + embedBibTex(bibString, file, encoding); + } + + private void embedBibTex(String bibTeX, Path file, Charset encoding) throws IOException { + if (!Files.exists(file) || !StandardFileType.PDF.getExtensions().contains(FileUtil.getFileExtension(file).orElse(""))) { + return; + } + try (PDDocument document = PDDocument.load(file.toFile())) { + PDDocumentNameDictionary nameDictionary = document.getDocumentCatalog().getNames(); + PDEmbeddedFilesNameTreeNode efTree; + Map names; + + if (nameDictionary == null) { + efTree = new PDEmbeddedFilesNameTreeNode(); + names = new HashMap(); + nameDictionary = new PDDocumentNameDictionary(document.getDocumentCatalog()); + nameDictionary.setEmbeddedFiles(efTree); + document.getDocumentCatalog().setNames(nameDictionary); + } else { + efTree = nameDictionary.getEmbeddedFiles(); + names = efTree.getNames(); + } + + if (efTree != null) { + PDComplexFileSpecification fileSpecification = new PDComplexFileSpecification(); + fileSpecification.setFile(EMBEDDED_FILE_NAME); + InputStream inputStream = new ByteArrayInputStream(bibTeX.getBytes(encoding)); + PDEmbeddedFile embeddedFile = new PDEmbeddedFile(document, inputStream); + embeddedFile.setSubtype("text/x-bibtex"); + embeddedFile.setSize(bibTeX.length()); + fileSpecification.setEmbeddedFile(embeddedFile); + + names.put(EMBEDDED_FILE_NAME, fileSpecification); + efTree.setNames(names); + nameDictionary.setEmbeddedFiles(efTree); + document.getDocumentCatalog().setNames(nameDictionary); + } + document.save(file.toFile()); + } + } + + private String getBibString(List entries) throws IOException { + StringWriter stringWriter = new StringWriter(200); + FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); + BibEntryWriter bibEntryWriter = new BibEntryWriter(fieldWriter, bibEntryTypesManager); + for (BibEntry entry : entries) { + bibEntryWriter.writeWithoutPrependedNewlines(entry, stringWriter, bibDatabaseMode); + } + return stringWriter.getBuffer().toString(); + } +} diff --git a/src/main/java/org/jabref/logic/exporter/ExporterFactory.java b/src/main/java/org/jabref/logic/exporter/ExporterFactory.java index 281a032ed24..ccc32055ddc 100644 --- a/src/main/java/org/jabref/logic/exporter/ExporterFactory.java +++ b/src/main/java/org/jabref/logic/exporter/ExporterFactory.java @@ -11,6 +11,8 @@ import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.util.StandardFileType; import org.jabref.logic.xmp.XmpPreferences; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntryTypesManager; public class ExporterFactory { @@ -28,7 +30,11 @@ private ExporterFactory(List exporters) { } public static ExporterFactory create(List customFormats, - LayoutFormatterPreferences layoutPreferences, SavePreferences savePreferences, XmpPreferences xmpPreferences) { + LayoutFormatterPreferences layoutPreferences, + SavePreferences savePreferences, + XmpPreferences xmpPreferences, + BibDatabaseMode bibDatabaseMode, + BibEntryTypesManager entryTypesManager) { List exporters = new ArrayList<>(); @@ -59,6 +65,7 @@ public static ExporterFactory create(List customFormats, exporters.add(new ModsExporter()); exporters.add(new XmpExporter(xmpPreferences)); exporters.add(new XmpPdfExporter(xmpPreferences)); + exporters.add(new EmbeddedBibFilePdfExporter(bibDatabaseMode, entryTypesManager, savePreferences.getFieldWriterPreferences())); // Now add custom export formats exporters.addAll(customFormats); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index dfc6db0cfc8..e5ab08d6ffa 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -346,7 +346,7 @@ Filter=Filter Filter\ groups=Filter groups -Finished\ writing\ XMP\ for\ %0\ file\ (%1\ skipped,\ %2\ errors).=Finished writing XMP for %0 file (%1 skipped, %2 errors). +Finished\ writing\ metadata\ for\ %0\ file\ (%1\ skipped,\ %2\ errors).=Finished writing metadata for %0 file (%1 skipped, %2 errors). First\ select\ the\ entries\ you\ want\ keys\ to\ be\ generated\ for.=First select the entries you want keys to be generated for. @@ -898,13 +898,16 @@ Whatever\ option\ you\ choose,\ Mr.\ DLib\ may\ share\ its\ data\ with\ research Will\ write\ XMP\ metadata\ to\ the\ PDFs\ linked\ from\ selected\ entries.=Will write XMP metadata to the PDFs linked from selected entries. -Write\ BibTeXEntry\ as\ XMP\ metadata\ to\ PDF.=Write BibTeXEntry as XMP metadata to PDF. +Write\ BibTeXEntry\ as\ metadata\ to\ PDF.=Write BibTeXEntry as metadata to PDF. +Write\ metadata\ for\ all\ PDFs\ in\ current\ library?=Write metadata for all PDFs in current library? +Writing\ metadata\ for\ selected\ entries...=Writing metadata for selected entries... +Writing\ metadata...=Writing metadata... Write\ XMP=Write XMP -Write\ XMP\ metadata=Write XMP metadata -Write\ XMP\ metadata\ for\ all\ PDFs\ in\ current\ library?=Write XMP metadata for all PDFs in current library? -Writing\ XMP\ metadata...=Writing XMP metadata... -Writing\ XMP\ metadata\ for\ selected\ entries...=Writing XMP metadata for selected entries... +Embedd\ BibTeXEntry\ in\ PDF.=Embedd BibTeXEntry in PDF. +Write\ BibTeXEntry\ as\ XMP\ metadata\ to\ PDF.=Write BibTeXEntry as XMP metadata to PDF. +Write\ BibTeXEntry\ metadata\ to\ PDF.=Write BibTeXEntry metadata to PDF. +Write\ metadata\ to\ PDF\ files=Write metadata to PDF files XMP-annotated\ PDF=XMP-annotated PDF XMP\ export\ privacy\ settings=XMP export privacy settings @@ -1784,7 +1787,6 @@ Could\ not\ retrieve\ entry\ data\ from\ '%0'.=Could not retrieve entry data fro Entry\ from\ %0\ could\ not\ be\ parsed.=Entry from %0 could not be parsed. Invalid\ identifier\:\ '%0'.=Invalid identifier: '%0'. This\ paper\ has\ been\ withdrawn.=This paper has been withdrawn. -Finished\ writing\ XMP\ metadata.=Finished writing XMP metadata. empty\ citation\ key=empty citation key Aux\ file=Aux file Group\ containing\ entries\ cited\ in\ a\ given\ TeX\ file=Group containing entries cited in a given TeX file diff --git a/src/test/java/org/jabref/gui/exporter/ExportToClipboardActionTest.java b/src/test/java/org/jabref/gui/exporter/ExportToClipboardActionTest.java index de4d0b7d8e2..d26f8d15693 100644 --- a/src/test/java/org/jabref/gui/exporter/ExportToClipboardActionTest.java +++ b/src/test/java/org/jabref/gui/exporter/ExportToClipboardActionTest.java @@ -24,7 +24,9 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.metadata.MetaData; @@ -66,7 +68,8 @@ public void setUp() { LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class, Answers.RETURNS_DEEP_STUBS); SavePreferences savePreferences = mock(SavePreferences.class); XmpPreferences xmpPreferences = mock(XmpPreferences.class); - exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); exportToClipboardAction = new ExportToClipboardAction(libraryTab, dialogService, exporterFactory, clipBoardManager, taskExecutor, preferences); BibEntry entry = new BibEntry(StandardEntryType.Misc) diff --git a/src/test/java/org/jabref/logic/exporter/CsvExportFormatTest.java b/src/test/java/org/jabref/logic/exporter/CsvExportFormatTest.java index 5698e78f1b7..d641b1a7e59 100644 --- a/src/test/java/org/jabref/logic/exporter/CsvExportFormatTest.java +++ b/src/test/java/org/jabref/logic/exporter/CsvExportFormatTest.java @@ -12,7 +12,9 @@ import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.junit.jupiter.api.AfterEach; @@ -36,7 +38,8 @@ public void setUp() { 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); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); exportFormat = exporterFactory.getExporterByName("oocsv").get(); diff --git a/src/test/java/org/jabref/logic/exporter/DocBook5ExporterTest.java b/src/test/java/org/jabref/logic/exporter/DocBook5ExporterTest.java index 651acdc49b3..e6baf19f1cb 100644 --- a/src/test/java/org/jabref/logic/exporter/DocBook5ExporterTest.java +++ b/src/test/java/org/jabref/logic/exporter/DocBook5ExporterTest.java @@ -13,7 +13,9 @@ import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -47,7 +49,8 @@ void setUp() throws URISyntaxException { 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); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); exporter = exporterFactory.getExporterByName("docbook5").get(); diff --git a/src/test/java/org/jabref/logic/exporter/DocbookExporterTest.java b/src/test/java/org/jabref/logic/exporter/DocbookExporterTest.java index 8fcff33ea5b..39246058eeb 100644 --- a/src/test/java/org/jabref/logic/exporter/DocbookExporterTest.java +++ b/src/test/java/org/jabref/logic/exporter/DocbookExporterTest.java @@ -11,7 +11,9 @@ import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.junit.jupiter.api.BeforeEach; @@ -35,7 +37,8 @@ public void setUp() { 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); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); exportFormat = exporterFactory.getExporterByName("docbook4").get(); } diff --git a/src/test/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporterTest.java b/src/test/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporterTest.java new file mode 100644 index 00000000000..d676aea61bf --- /dev/null +++ b/src/test/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporterTest.java @@ -0,0 +1,186 @@ +package org.jabref.logic.exporter; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.fileformat.PdfEmbeddedBibFileImporter; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.preferences.FilePreferences; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class EmbeddedBibFilePdfExporterTest { + + @TempDir static Path tempDir; + + private static BibEntry olly2018 = new BibEntry(StandardEntryType.Article); + private static BibEntry toral2006 = new BibEntry(StandardEntryType.Article); + private static BibEntry vapnik2000 = new BibEntry(StandardEntryType.Article); + + private PdfEmbeddedBibFileImporter importer; + private EmbeddedBibFilePdfExporter exporter; + private ImportFormatPreferences importFormatPreferences; + private BibDatabaseMode bibDatabaseMode; + private BibEntryTypesManager bibEntryTypesManager; + private FieldWriterPreferences fieldWriterPreferences; + private Charset encoding; + + private BibDatabaseContext databaseContext; + private BibDatabase dataBase; + private FilePreferences filePreferences; + + private static void initBibEntries() throws IOException { + olly2018.setCitationKey("Olly2018"); + olly2018.setField(StandardField.AUTHOR, "Olly and Johannes"); + olly2018.setField(StandardField.TITLE, "Stefan's palace"); + olly2018.setField(StandardField.JOURNAL, "Test Journal"); + olly2018.setField(StandardField.VOLUME, "1"); + olly2018.setField(StandardField.NUMBER, "1"); + olly2018.setField(StandardField.PAGES, "1-2"); + olly2018.setMonth(Month.MARCH); + olly2018.setField(StandardField.ISSN, "978-123-123"); + olly2018.setField(StandardField.NOTE, "NOTE"); + olly2018.setField(StandardField.ABSTRACT, "ABSTRACT"); + olly2018.setField(StandardField.COMMENT, "COMMENT"); + olly2018.setField(StandardField.DOI, "10/3212.3123"); + olly2018.setField(StandardField.FILE, ":article_dublinCore.pdf:PDF"); + olly2018.setField(StandardField.GROUPS, "NO"); + olly2018.setField(StandardField.HOWPUBLISHED, "online"); + olly2018.setField(StandardField.KEYWORDS, "k1, k2"); + olly2018.setField(StandardField.OWNER, "me"); + olly2018.setField(StandardField.REVIEW, "review"); + olly2018.setField(StandardField.URL, "https://www.olly2018.edu"); + + LinkedFile linkedFile = createDefaultLinkedFile("existing.pdf", tempDir); + olly2018.setFiles(Arrays.asList(linkedFile)); + + toral2006.setField(StandardField.AUTHOR, "Toral, Antonio and Munoz, Rafael"); + toral2006.setField(StandardField.TITLE, "A proposal to automatically build and maintain gazetteers for Named Entity Recognition by using Wikipedia"); + toral2006.setField(StandardField.BOOKTITLE, "Proceedings of EACL"); + toral2006.setField(StandardField.PAGES, "56--61"); + toral2006.setField(StandardField.EPRINTTYPE, "asdf"); + toral2006.setField(StandardField.OWNER, "Ich"); + toral2006.setField(StandardField.URL, "www.url.de"); + + toral2006.setFiles(Arrays.asList(new LinkedFile("non-existing", "path/to/nowhere.pdf", "PDF"))); + + vapnik2000.setCitationKey("vapnik2000"); + vapnik2000.setField(StandardField.TITLE, "The Nature of Statistical Learning Theory"); + vapnik2000.setField(StandardField.PUBLISHER, "Springer Science + Business Media"); + vapnik2000.setField(StandardField.AUTHOR, "Vladimir N. Vapnik"); + vapnik2000.setField(StandardField.DOI, "10.1007/978-1-4757-3264-1"); + vapnik2000.setField(StandardField.OWNER, "Ich"); + } + + /** + * Create a temporary PDF-file with a single empty page. + */ + @BeforeEach + void setUp() throws IOException { + encoding = Charset.defaultCharset(); + + filePreferences = mock(FilePreferences.class); + when(filePreferences.getUser()).thenReturn(tempDir.toAbsolutePath().toString()); + when(filePreferences.shouldStoreFilesRelativeToBib()).thenReturn(false); + + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + when(importFormatPreferences.getFieldContentFormatterPreferences().getNonWrappableFields()).thenReturn(List.of()); + + bibDatabaseMode = BibDatabaseMode.BIBTEX; + bibEntryTypesManager = new BibEntryTypesManager(); + fieldWriterPreferences = new FieldWriterPreferences(); + + importer = new PdfEmbeddedBibFileImporter(importFormatPreferences); + exporter = new EmbeddedBibFilePdfExporter(bibDatabaseMode, bibEntryTypesManager, fieldWriterPreferences); + + databaseContext = new BibDatabaseContext(); + dataBase = databaseContext.getDatabase(); + + initBibEntries(); + dataBase.insertEntry(olly2018); + dataBase.insertEntry(toral2006); + dataBase.insertEntry(vapnik2000); + } + + @ParameterizedTest + @MethodSource("provideBibEntriesWithValidPdfFileLinks") + void successfulExportToAllFilesOfEntry(BibEntry bibEntryWithValidPdfFileLink) throws Exception { + assertTrue(exporter.exportToAllFilesOfEntry(databaseContext, encoding, filePreferences, bibEntryWithValidPdfFileLink, Arrays.asList(olly2018))); + } + + @ParameterizedTest + @MethodSource("provideBibEntriesWithInvalidPdfFileLinks") + void unsuccessfulExportToAllFilesOfEntry(BibEntry bibEntryWithValidPdfFileLink) throws Exception { + assertFalse(exporter.exportToAllFilesOfEntry(databaseContext, encoding, filePreferences, bibEntryWithValidPdfFileLink, Arrays.asList(olly2018))); + } + + public static Stream provideBibEntriesWithValidPdfFileLinks() { + return Stream.of(Arguments.of(olly2018)); + } + + public static Stream provideBibEntriesWithInvalidPdfFileLinks() { + return Stream.of(Arguments.of(vapnik2000), Arguments.of(toral2006)); + } + + @ParameterizedTest + @MethodSource("providePathsToValidPDFs") + void successfulExportToFileByPath(Path path) throws Exception { + assertTrue(exporter.exportToFileByPath(databaseContext, dataBase, encoding, filePreferences, path)); + } + + @ParameterizedTest + @MethodSource("providePathsToInvalidPDFs") + void unsuccessfulExportToFileByPath(Path path) throws Exception { + assertFalse(exporter.exportToFileByPath(databaseContext, dataBase, encoding, filePreferences, path)); + } + + public static Stream providePathsToValidPDFs() { + return Stream.of(Arguments.of(tempDir.resolve("existing.pdf").toAbsolutePath())); + } + + public static Stream providePathsToInvalidPDFs() throws IOException { + LinkedFile existingFileThatIsNotLinked = createDefaultLinkedFile("notlinked.pdf", tempDir); + return Stream.of( + Arguments.of(Path.of("")), + Arguments.of(tempDir.resolve("path/to/nowhere.pdf").toAbsolutePath()), + Arguments.of(Path.of(existingFileThatIsNotLinked.getLink()))); + } + + private static LinkedFile createDefaultLinkedFile(String fileName, Path tempDir) throws IOException { + Path pdfFile = tempDir.resolve(fileName); + try (PDDocument pdf = new PDDocument()) { + pdf.addPage(new PDPage()); + pdf.save(pdfFile.toAbsolutePath().toString()); + } + + LinkedFile linkedFile = new LinkedFile("A linked pdf", pdfFile, "PDF"); + + return linkedFile; + } +} diff --git a/src/test/java/org/jabref/logic/exporter/ExporterTest.java b/src/test/java/org/jabref/logic/exporter/ExporterTest.java index ef33a47b167..810f32800d8 100644 --- a/src/test/java/org/jabref/logic/exporter/ExporterTest.java +++ b/src/test/java/org/jabref/logic/exporter/ExporterTest.java @@ -10,19 +10,24 @@ import java.util.List; import java.util.stream.Stream; +import org.jabref.logic.bibtex.FieldWriterPreferences; import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Answers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ExporterTest { @@ -42,9 +47,11 @@ private static Stream exportFormats() { List customFormats = new ArrayList<>(); LayoutFormatterPreferences layoutPreferences = mock(LayoutFormatterPreferences.class); - SavePreferences savePreferences = mock(SavePreferences.class); + SavePreferences savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + when(savePreferences.getFieldWriterPreferences()).thenReturn(new FieldWriterPreferences()); XmpPreferences xmpPreferences = mock(XmpPreferences.class); - ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); for (Exporter format : exporterFactory.getExporters()) { result.add(new Object[]{format, format.getName()}); diff --git a/src/test/java/org/jabref/logic/exporter/HtmlExportFormatTest.java b/src/test/java/org/jabref/logic/exporter/HtmlExportFormatTest.java index 6b1b05a5f00..821b9f4933b 100644 --- a/src/test/java/org/jabref/logic/exporter/HtmlExportFormatTest.java +++ b/src/test/java/org/jabref/logic/exporter/HtmlExportFormatTest.java @@ -11,7 +11,9 @@ import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.junit.jupiter.api.AfterEach; @@ -35,7 +37,8 @@ public void setUp() { 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); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); exportFormat = exporterFactory.getExporterByName("html").get(); diff --git a/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java b/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java index 84b0d9349f2..dcef2c1e12e 100644 --- a/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java +++ b/src/test/java/org/jabref/logic/exporter/YamlExporterTest.java @@ -11,7 +11,9 @@ import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.xmp.XmpPreferences; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -35,7 +37,8 @@ static void setUp() { 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); + BibEntryTypesManager entryTypesManager = mock(BibEntryTypesManager.class); + ExporterFactory exporterFactory = ExporterFactory.create(customFormats, layoutPreferences, savePreferences, xmpPreferences, BibDatabaseMode.BIBTEX, entryTypesManager); databaseContext = new BibDatabaseContext(); charset = StandardCharsets.UTF_8;