Skip to content

Commit a73bb07

Browse files
Fix PDF export (#10361)
* Create PDF WIP * Create PDF WIP * Create PDF WIP * Add @test to XmpPdfExporterTest WIP * Add Importer to XmpPdfExporterTest * WIP * Fix testRoundtripExportImport * Finish testRoundtripExportImport * Add @AfterEach to XmpPdfExporterTest.java * Change @test to @ParameterizedTest * Delete IllegalArgumentException in XmpPdfExporter.java * add changelog and change message --------- Co-authored-by: Siedlerchr <[email protected]>
1 parent 303281e commit a73bb07

File tree

3 files changed

+97
-4
lines changed

3 files changed

+97
-4
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
2626
- The export formats `listrefs`, `tablerefs`, `tablerefsabsbib`, now use the ISO date format in the footer [#10383](https://github.com/JabRef/jabref/pull/10383).
2727
- When searching for an identifier in the "Web search", the title of the search window is now "Identifier-based Web Search". [#10391](https://github.com/JabRef/jabref/pull/10391)
2828
- The ampersand checker now skips verbatim fields (`file`, `url`, ...). [#10419](https://github.com/JabRef/jabref/pull/10419)
29+
- If no existing document is selected for exporting "XMP annotated pdf" JabRef will now create a new PDF file with a sample text and the metadata. [#10102](https://github.com/JabRef/jabref/issues/10102)
2930

3031
### Fixed
3132

@@ -38,6 +39,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
3839
- We fixed an issue where it was possible to create a group with no name or with a group separator inside the name [#9776](https://github.com/JabRef/jabref/issues/9776)
3940
- Biblatex's `journaltitle` is now also respected for showing the journal information. [#10397](https://github.com/JabRef/jabref/issues/10397)
4041
- JabRef does not hang anymore when exporting via CLI. [#10380](https://github.com/JabRef/jabref/issues/10380)
42+
- We fixed an issue where exporting "XMP annotated pdf" without selecting an existing document would produce an exception. [#10102](https://github.com/JabRef/jabref/issues/10102)
4143

4244
### Removed
4345

src/main/java/org/jabref/logic/exporter/XmpPdfExporter.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.jabref.logic.exporter;
22

3+
import java.io.IOException;
4+
import java.nio.file.Files;
35
import java.nio.file.Path;
46
import java.util.List;
57
import java.util.Objects;
@@ -11,6 +13,12 @@
1113
import org.jabref.model.database.BibDatabaseContext;
1214
import org.jabref.model.entry.BibEntry;
1315

16+
import org.apache.pdfbox.pdmodel.PDDocument;
17+
import org.apache.pdfbox.pdmodel.PDPage;
18+
import org.apache.pdfbox.pdmodel.PDPageContentStream;
19+
import org.apache.pdfbox.pdmodel.font.PDType1Font;
20+
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
21+
1422
public class XmpPdfExporter extends Exporter {
1523

1624
private final XmpPreferences xmpPreferences;
@@ -26,7 +34,24 @@ public void export(BibDatabaseContext databaseContext, Path pdfFile, List<BibEnt
2634
Objects.requireNonNull(pdfFile);
2735
Objects.requireNonNull(entries);
2836

29-
if (pdfFile.toString().endsWith(".pdf")) {
37+
Path filePath = pdfFile.toAbsolutePath();
38+
39+
if (!Files.exists(filePath)) {
40+
try (PDDocument document = new PDDocument()) {
41+
PDPage page = new PDPage();
42+
document.addPage(page);
43+
44+
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
45+
contentStream.beginText();
46+
contentStream.newLineAtOffset(25, 500);
47+
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
48+
contentStream.showText("This PDF was created by JabRef. It demonstrates the embedding of XMP data in PDF files. Please open the file metadata view of your PDF viewer to see the attached files. Note that the normal usage is to embed the BibTeX data in an existing PDF.");
49+
contentStream.endText();
50+
}
51+
document.save(filePath.toString());
52+
} catch (IOException e) {
53+
throw new Exception("Error creating PDF", e);
54+
}
3055
new XmpUtilWriter(xmpPreferences).writeXmp(pdfFile, entries, databaseContext.getDatabase());
3156
}
3257
}

src/test/java/org/jabref/logic/exporter/XmpPdfExporterTest.java

+69-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
import java.util.stream.Stream;
88

99
import javafx.beans.property.SimpleObjectProperty;
10+
import javafx.collections.FXCollections;
1011

12+
import org.jabref.logic.cleanup.FieldFormatterCleanup;
13+
import org.jabref.logic.formatter.bibtexfields.NormalizeNamesFormatter;
14+
import org.jabref.logic.importer.ImportFormatPreferences;
15+
import org.jabref.logic.importer.fileformat.PdfXmpImporter;
1116
import org.jabref.logic.journals.JournalAbbreviationRepository;
1217
import org.jabref.logic.xmp.XmpPreferences;
18+
import org.jabref.logic.xmp.XmpUtilWriter;
1319
import org.jabref.model.database.BibDatabase;
1420
import org.jabref.model.database.BibDatabaseContext;
1521
import org.jabref.model.entry.BibEntry;
@@ -21,12 +27,18 @@
2127

2228
import org.apache.pdfbox.pdmodel.PDDocument;
2329
import org.apache.pdfbox.pdmodel.PDPage;
30+
import org.apache.pdfbox.pdmodel.PDPageContentStream;
31+
import org.apache.pdfbox.pdmodel.font.PDType1Font;
32+
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
33+
import org.junit.jupiter.api.AfterEach;
2434
import org.junit.jupiter.api.BeforeEach;
2535
import org.junit.jupiter.api.io.TempDir;
2636
import org.junit.jupiter.params.ParameterizedTest;
2737
import org.junit.jupiter.params.provider.Arguments;
2838
import org.junit.jupiter.params.provider.MethodSource;
39+
import org.mockito.Answers;
2940

41+
import static org.junit.jupiter.api.Assertions.assertEquals;
3042
import static org.junit.jupiter.api.Assertions.assertFalse;
3143
import static org.junit.jupiter.api.Assertions.assertTrue;
3244
import static org.mockito.Mockito.mock;
@@ -41,6 +53,8 @@ class XmpPdfExporterTest {
4153
private static BibEntry vapnik2000 = new BibEntry(StandardEntryType.Article);
4254

4355
private XmpPdfExporter exporter;
56+
private PdfXmpImporter importer;
57+
private XmpPreferences xmpPreferences;
4458

4559
private BibDatabaseContext databaseContext;
4660
private JournalAbbreviationRepository abbreviationRepository;
@@ -84,7 +98,7 @@ private static void initBibEntries() throws IOException {
8498
vapnik2000.setCitationKey("vapnik2000");
8599
vapnik2000.setField(StandardField.TITLE, "The Nature of Statistical Learning Theory");
86100
vapnik2000.setField(StandardField.PUBLISHER, "Springer Science + Business Media");
87-
vapnik2000.setField(StandardField.AUTHOR, "Vladimir N. Vapnik");
101+
vapnik2000.setField(StandardField.AUTHOR, "Vapnik, Vladimir N.");
88102
vapnik2000.setField(StandardField.DOI, "10.1007/978-1-4757-3264-1");
89103
vapnik2000.setField(StandardField.OWNER, "Ich");
90104
}
@@ -99,9 +113,13 @@ void setUp() throws IOException {
99113
when(filePreferences.getUserAndHost()).thenReturn(tempDir.toAbsolutePath().toString());
100114
when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(false);
101115

102-
XmpPreferences xmpPreferences = new XmpPreferences(false, Collections.emptySet(), new SimpleObjectProperty<>(','));
116+
xmpPreferences = new XmpPreferences(false, Collections.emptySet(), new SimpleObjectProperty<>(','));
103117
exporter = new XmpPdfExporter(xmpPreferences);
104118

119+
ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS);
120+
when(importFormatPreferences.fieldPreferences().getNonWrappableFields()).thenReturn(FXCollections.emptyObservableList());
121+
importer = new PdfXmpImporter(xmpPreferences);
122+
105123
databaseContext = new BibDatabaseContext();
106124
BibDatabase dataBase = databaseContext.getDatabase();
107125

@@ -111,6 +129,17 @@ void setUp() throws IOException {
111129
dataBase.insertEntry(vapnik2000);
112130
}
113131

132+
@AfterEach
133+
void reset() throws IOException {
134+
List<BibEntry> expectedEntries = databaseContext.getEntries();
135+
for (BibEntry entry : expectedEntries) {
136+
entry.clearField(StandardField.FILE);
137+
}
138+
LinkedFile linkedFile = createDefaultLinkedFile("existing.pdf", tempDir);
139+
olly2018.setFiles(List.of(linkedFile));
140+
toral2006.setFiles(List.of(new LinkedFile("non-existing", "path/to/nowhere.pdf", "PDF")));
141+
}
142+
114143
@ParameterizedTest
115144
@MethodSource("provideBibEntriesWithValidPdfFileLinks")
116145
void successfulExportToAllFilesOfEntry(BibEntry bibEntryWithValidPdfFileLink) throws Exception {
@@ -143,6 +172,39 @@ void unsuccessfulExportToFileByPath(Path path) throws Exception {
143172
assertFalse(exporter.exportToFileByPath(databaseContext, filePreferences, path, abbreviationRepository));
144173
}
145174

175+
@ParameterizedTest
176+
@MethodSource("providePathToNewPDFs")
177+
public void testRoundtripExportImport(Path path) throws Exception {
178+
try (PDDocument document = new PDDocument()) {
179+
PDPage page = new PDPage();
180+
document.addPage(page);
181+
182+
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
183+
contentStream.beginText();
184+
contentStream.newLineAtOffset(25, 500);
185+
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
186+
contentStream.showText("This PDF was created by JabRef. It demonstrates the embedding of XMP data in PDF files. Please open the file metadata view of your PDF viewer to see the attached files. Note that the normal usage is to embed the BibTeX data in an existing PDF.");
187+
contentStream.endText();
188+
}
189+
document.save(path.toString());
190+
}
191+
new XmpUtilWriter(xmpPreferences).writeXmp(path, databaseContext.getEntries(), databaseContext.getDatabase());
192+
193+
List<BibEntry> importedEntries = importer.importDatabase(path).getDatabase().getEntries();
194+
importedEntries.forEach(bibEntry -> new FieldFormatterCleanup(StandardField.AUTHOR, new NormalizeNamesFormatter()).cleanup(bibEntry));
195+
196+
List<BibEntry> expectedEntries = databaseContext.getEntries();
197+
for (BibEntry entry : expectedEntries) {
198+
entry.clearField(StandardField.FILE);
199+
entry.addFile(createDefaultLinkedFile("original.pdf", tempDir));
200+
}
201+
assertEquals(expectedEntries, importedEntries);
202+
}
203+
204+
public static Stream<Arguments> providePathToNewPDFs() {
205+
return Stream.of(Arguments.of(tempDir.resolve("original.pdf").toAbsolutePath()));
206+
}
207+
146208
public static Stream<Arguments> providePathsToValidPDFs() {
147209
return Stream.of(Arguments.of(tempDir.resolve("existing.pdf").toAbsolutePath()));
148210
}
@@ -156,12 +218,16 @@ public static Stream<Arguments> providePathsToInvalidPDFs() throws IOException {
156218
}
157219

158220
private static LinkedFile createDefaultLinkedFile(String fileName, Path tempDir) throws IOException {
221+
return createDefaultLinkedFile("", fileName, tempDir);
222+
}
223+
224+
private static LinkedFile createDefaultLinkedFile(String description, String fileName, Path tempDir) throws IOException {
159225
Path pdfFile = tempDir.resolve(fileName);
160226
try (PDDocument pdf = new PDDocument()) {
161227
pdf.addPage(new PDPage());
162228
pdf.save(pdfFile.toAbsolutePath().toString());
163229
}
164230

165-
return new LinkedFile("A linked pdf", pdfFile, "PDF");
231+
return new LinkedFile("", pdfFile, "PDF");
166232
}
167233
}

0 commit comments

Comments
 (0)