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

CLI option to write XMP metadata to pdfs #7814

Merged
merged 15 commits into from
Jun 21, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We added a select all button for the library import function. [#7786](https://github.com/JabRef/jabref/issues/7786)
- We added a search feature for journal abbreviations. [#7804](https://github.com/JabRef/jabref/pull/7804)
- We added auto-key-generation progress to the background task list. [#7267](https://github.com/JabRef/jabref/issues/72)
- We added the option to write XMP metadata to pdfs from the CLI. [7814](https://github.com/JabRef/jabref/pull/7814)

### Changed

Expand Down
92 changes: 92 additions & 0 deletions src/main/java/org/jabref/cli/ArgumentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.Vector;
import java.util.prefs.BackingStoreException;

import org.jabref.gui.Globals;
Expand All @@ -24,6 +28,7 @@
import org.jabref.logic.exporter.ExporterFactory;
import org.jabref.logic.exporter.SavePreferences;
import org.jabref.logic.exporter.TemplateExporter;
import org.jabref.logic.exporter.XmpPdfExporter;
import org.jabref.logic.importer.FetcherException;
import org.jabref.logic.importer.ImportException;
import org.jabref.logic.importer.ImportFormatReader;
Expand All @@ -49,6 +54,8 @@
import org.jabref.model.entry.BibEntry;
import org.jabref.model.strings.StringUtil;
import org.jabref.model.util.DummyFileUpdateMonitor;
import org.jabref.model.util.FileHelper;
import org.jabref.preferences.FilePreferences;
import org.jabref.preferences.SearchPreferences;

import com.google.common.base.Throwables;
Expand Down Expand Up @@ -215,6 +222,12 @@ private List<ParserResult> processArguments() {
automaticallySetFileLinks(loaded);
}

if (cli.isWriteXMPtoPdf()) {
if (!loaded.isEmpty()) {
writeXMPtoPdf(loaded, cli.getWriteXMPtoPdf(), Globals.prefs.getDefaultEncoding(), Globals.prefs.getXmpPreferences(), Globals.prefs.getFilePreferences());
}
}

if (cli.isFileExport()) {
if (!loaded.isEmpty()) {
exportFile(loaded, cli.getFileExport().split(","));
Expand All @@ -239,6 +252,85 @@ private List<ParserResult> processArguments() {
return loaded;
}

private void writeXMPtoPdf(List<ParserResult> loaded, String filesAndCitekeys, Charset encoding, XmpPreferences xmpPreferences, FilePreferences filePreferences) {

ParserResult pr = loaded.get(loaded.size() - 1);
BibDatabaseContext databaseContext = pr.getDatabaseContext();
BibDatabase dataBase = pr.getDatabase();

XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences);

if (filesAndCitekeys.equals("all")) {
for (BibEntry entry : dataBase.getEntries()) {
writeXMPtoPDFsOfEntry(databaseContext, entry.getCitationKey().orElse("<no cite key defined>"), entry, encoding, filePreferences, xmpPdfExporter);
}
return;
}

Vector<String> citeKeys = new Vector<>();
Vector<String> pdfs = new Vector<>();
for (String fileOrCiteKey : filesAndCitekeys.split(",")) {
if (fileOrCiteKey.toLowerCase(Locale.ROOT).endsWith(".pdf")) {
pdfs.add(fileOrCiteKey);
} else {
citeKeys.add(fileOrCiteKey);
}
}

writeXMPtoPdfByCitekey(databaseContext, dataBase, citeKeys, encoding, filePreferences, xmpPdfExporter);
writeXMPtoPdfByFileNames(databaseContext, dataBase, pdfs, encoding, filePreferences, xmpPdfExporter);

}

private void writeXMPtoPDFsOfEntry(BibDatabaseContext databaseContext, String citeKey, BibEntry entry, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter) {
try {
if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, encoding, filePreferences, entry, Arrays.asList(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));
}
} catch (Exception e) {
LOGGER.error(String.format("Failed writing XMP metadata on a linked file of %s.", citeKey));
}
}

private void writeXMPtoPdfByCitekey(BibDatabaseContext databaseContext, BibDatabase dataBase, Vector<String> citeKeys, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter) {
for (String citeKey : citeKeys) {
List<BibEntry> bibEntryList = dataBase.getEntriesByCitationKey(citeKey);
if (bibEntryList.isEmpty()) {
LOGGER.error(String.format("Skipped - Cannot find %s in library.", citeKey));
continue;
}
for (BibEntry entry : bibEntryList) {
writeXMPtoPDFsOfEntry(databaseContext, citeKey, entry, encoding, filePreferences, xmpPdfExporter);
}
}
}

private void writeXMPtoPdfByFileNames(BibDatabaseContext databaseContext, BibDatabase dataBase, Vector<String> fileNames, Charset encoding, FilePreferences filePreferences, XmpPdfExporter xmpPdfExporter) {
for (String fileName : fileNames) {
Path filePath = Path.of(fileName);
if (!filePath.isAbsolute()) {
filePath = FileHelper.find(fileName, databaseContext.getFileDirectories(filePreferences)).orElse(FileHelper.find(fileName, Arrays.asList(Path.of("").toAbsolutePath())).orElse(filePath));
}
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));
}
} catch (IOException e) {
LOGGER.error("Error accessing files.", fileName);
} catch (Exception e) {
LOGGER.error(String.format("Error writing entry to %s.", fileName));
}
} else {
LOGGER.error(String.format("Skipped - PDF %s does not exist", fileName));
}
}
}

private boolean exportMatches(List<ParserResult> loaded) {
String[] data = cli.getExportMatches().split(",");
String searchTerm = data[0].replace("\\$", " "); // enables blanks within the search term:
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/jabref/cli/JabRefCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ public boolean isAutomaticallySetFileLinks() {
return cl.hasOption("automaticallySetFileLinks");
}

public boolean isWriteXMPtoPdf() {
return cl.hasOption("writeXMPtoPdf");
}

public String getWriteXMPtoPdf() {
return cl.getOptionValue("writeXMPtoPdf");
}

private static Options getOptions() {
Options options = new Options();

Expand Down Expand Up @@ -242,6 +250,14 @@ private static Options getOptions() {
.argName("KEY1[,KEY2][,KEYn] | all")
.build());

options.addOption(Option
.builder("w")
.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());

return options;
}

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

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.jabref.logic.util.FileType;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.LinkedFile;
import org.jabref.preferences.FilePreferences;

public abstract class Exporter {

Expand Down Expand Up @@ -55,4 +61,64 @@ public String toString() {
* @param entries a list containing all entries that should be exported
*/
public abstract void export(BibDatabaseContext databaseContext, Path file, Charset encoding, List<BibEntry> entries) throws Exception;

/**
* Exports to all files linked to a given entry
* @param databaseContext the database to export from
* @param encoding the encoding to use
* @param filePreferences the filePreferences to use for resolving paths
* @param entryToWriteOn the entry for which we want to write on all linked pdfs
* @param entriesToWrite the content that we want to export to the pdfs
* @return whether any file was written on
* @throws Exception if the writing fails
*/
public boolean exportToAllFilesOfEntry(BibDatabaseContext databaseContext, Charset encoding, FilePreferences filePreferences, BibEntry entryToWriteOn, List<BibEntry> entriesToWrite) throws Exception {
boolean writtenToAFile = false;

for (LinkedFile file : entryToWriteOn.getFiles()) {
if (file.getFileType().equals(fileType.getName())) {
Optional<Path> filePath = file.findIn(databaseContext, filePreferences);
if (filePath.isEmpty()) {
continue;
} else {
export(databaseContext, filePath.get(), encoding, entriesToWrite);
writtenToAFile = true;
}
}
}

return writtenToAFile;
}

/**
* Exports bib-entries a file is linked to
* Behaviour in case the file is linked to different bib-entries depends on the implementation of {@link #export}.
* If it overwrites any existing information, only the last found bib-entry will be exported (as the previous exports are overwritten).
* If it extends existing information, all found bib-entries will be exported.
* @param databaseContext the database-context to export from
* @param dataBase the database to export from
* @param encoding the encoding to use
* @param filePreferences the filePreferences to use for resolving paths
* @param filePath the path to the file we want to write on
* @return whether the file was written on at least once
* @throws Exception if the writing fails
*/
public boolean exportToFileByPath(BibDatabaseContext databaseContext, BibDatabase dataBase, Charset encoding, FilePreferences filePreferences, Path filePath) throws Exception {
if (!Files.exists(filePath)) {
return false;
}
boolean writtenABibEntry = false;
for (BibEntry entry : dataBase.getEntries()) {
for (LinkedFile linkedFile : entry.getFiles()) {
if (linkedFile.getFileType().equals(fileType.getName())) {
Optional<Path> linkedFilePath = linkedFile.findIn(databaseContext.getFileDirectories(filePreferences));
if (!linkedFilePath.isEmpty() && Files.exists(linkedFilePath.get()) && Files.isSameFile(linkedFilePath.get(), filePath)) {
export(databaseContext, filePath, encoding, Arrays.asList((entry)));
writtenABibEntry = true;
}
}
}
}
return writtenABibEntry;
}
}
Loading