diff --git a/CHANGELOG.md b/CHANGELOG.md index d33d741c353..39e94d1044e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We moved the search box in preview preferences closer to the available citation styles list. [#8370](https://github.com/JabRef/jabref/pull/8370) - Changing the preference to show the preview panel as a separate tab now has effect without restarting JabRef. [#8370](https://github.com/JabRef/jabref/pull/8370) - We enabled switching themes in JabRef without the need to restart JabRef. [#7335](https://github.com/JabRef/jabref/pull/7335) +- We added support for the field `day`, `rights`, `coverage` and `language` when reading XMP data in Dublin Core format. [#8491](https://github.com/JabRef/jabref/issues/8491) ### Fixed diff --git a/src/main/java/org/jabref/logic/xmp/DublinCoreExtractor.java b/src/main/java/org/jabref/logic/xmp/DublinCoreExtractor.java index 78ed8b6a64a..59af0cb3869 100644 --- a/src/main/java/org/jabref/logic/xmp/DublinCoreExtractor.java +++ b/src/main/java/org/jabref/logic/xmp/DublinCoreExtractor.java @@ -1,7 +1,6 @@ package org.jabref.logic.xmp; -import java.io.IOException; -import java.util.Calendar; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map.Entry; @@ -16,6 +15,7 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Date; import org.jabref.model.entry.Month; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; @@ -24,7 +24,6 @@ import org.jabref.model.entry.types.EntryTypeFactory; import org.jabref.model.strings.StringUtil; -import org.apache.xmpbox.DateConverter; import org.apache.xmpbox.schema.DublinCoreSchema; import org.apache.xmpbox.type.BadFieldValueException; import org.slf4j.Logger; @@ -32,6 +31,10 @@ public class DublinCoreExtractor { + public static final String DC_COVERAGE = "coverage"; + public static final String DC_RIGHTS = "rights"; + public static final String DC_SOURCE = "source"; + private static final Logger LOGGER = LoggerFactory.getLogger(DublinCoreExtractor.class); private final DublinCoreSchema dcSchema; @@ -71,32 +74,19 @@ private void extractAuthor() { } /** - * Year in BibTex - Date in DublinCore is only the year information, because dc interprets empty months as January. - * Tries to extract the month as well. In JabRef the bibtex/month/value is prioritized.
The problem is the - * default value of the calendar, which is always January, also if there is no month information in the xmp metdata. - * The idea is, to reject all information with YYYY-01-01. In cases, where xmp is written with JabRef the month - * property filled with jan will override this behavior and no data is lost. In the cases, where xmp is written by - * another service, the assumption is, that the 1st January is not a publication date at all. + * Bibtex-Fields : year, [month], [day] - 'dc:date' in DublinCore */ - private void extractYearAndMonth() { + private void extractDate() { List dates = dcSchema.getUnqualifiedSequenceValueList("date"); if ((dates != null) && !dates.isEmpty()) { + String date = dates.get(0).trim(); - Calendar calender = null; - try { - calender = DateConverter.toCalendar(date); - } catch (IOException ignored) { - // Ignored - } - if (calender != null) { - bibEntry.setField(StandardField.YEAR, String.valueOf(calender.get(Calendar.YEAR))); - int monthNumber = calender.get(Calendar.MONTH) + 1; - // not the 1st of January - if (!((monthNumber == 1) && (calender.get(Calendar.DAY_OF_MONTH) == 1))) { - Month.getMonthByNumber(monthNumber) - .ifPresent(month -> bibEntry.setMonth(month)); - } - } + Date.parse(date) + .ifPresent(dateValue -> { + dateValue.getDay().ifPresent(day -> bibEntry.setField(StandardField.DAY, Integer.toString(day))); + dateValue.getMonth().ifPresent(bibEntry::setMonth); + dateValue.getYear().ifPresent(year -> bibEntry.setField(StandardField.YEAR, Integer.toString(year))); + }); } } @@ -182,7 +172,7 @@ private void extractRights() { LOGGER.warn("Could not extract rights", e); } if (!StringUtil.isNullOrEmpty(rights)) { - bibEntry.setField(new UnknownField("rights"), rights); + bibEntry.setField(new UnknownField(DC_RIGHTS), rights); } } @@ -192,7 +182,7 @@ private void extractRights() { private void extractSource() { String source = dcSchema.getSource(); if (!StringUtil.isNullOrEmpty(source)) { - bibEntry.setField(new UnknownField("source"), source); + bibEntry.setField(new UnknownField(DC_SOURCE), source); } } @@ -234,6 +224,29 @@ private void extractType() { } } + /** + * No Equivalent in BibTex. Will create an Unknown "Coverage" Field + */ + private void extractCoverage() { + String coverage = dcSchema.getCoverage(); + if (!StringUtil.isNullOrEmpty(coverage)) { + bibEntry.setField(FieldFactory.parseField(DC_COVERAGE), coverage); + } + } + + /** + * Language is equivalent in both formats (BibTex and DublinCore) + */ + private void extractLanguages() { + StringBuilder builder = new StringBuilder(); + + List languages = dcSchema.getLanguages(); + if (languages != null && !languages.isEmpty()) { + languages.forEach(language -> builder.append(",").append(language)); + bibEntry.setField(StandardField.LANGUAGE, builder.substring(1)); + } + } + /** * Helper function for retrieving a BibEntry from the DublinCore metadata in a PDF file. *

@@ -252,7 +265,7 @@ public Optional extractBibtexEntry() { // then extract all "standard" dublin core entries this.extractEditor(); this.extractAuthor(); - this.extractYearAndMonth(); + this.extractDate(); this.extractAbstract(); this.extractDOI(); this.extractPublisher(); @@ -261,6 +274,8 @@ public Optional extractBibtexEntry() { this.extractSubject(); this.extractTitle(); this.extractType(); + this.extractCoverage(); + this.extractLanguages(); // we pass a new BibEntry in the constructor which is never empty as it already consists of "@misc" if (bibEntry.getFieldMap().isEmpty()) { @@ -350,6 +365,37 @@ private void fillTitle(String title) { dcSchema.setTitle(title); } + /** + * BibTex : Coverage (Custom Field); DC Field : Coverage + * + * @param coverage + */ + private void fillCoverage(String coverage) { + dcSchema.setCoverage(coverage); + } + + /** + * BibTex Field : language ; DC Field : dc:language + */ + private void fillLanguages(String languages) { + Arrays.stream(languages.split(",")) + .forEach(dcSchema::addLanguage); + } + + /** + * BibTex : Rights (Custom Field); DC Field : dc:rights + */ + private void fillRights(String rights) { + dcSchema.addRights(null, rights.split(",")[0]); + } + + /** + * BibTex : Source (Custom Field); DC Field : Source + */ + private void fillSource(String source) { + dcSchema.setSource(source); + } + /** * All others (+ citation key) get packaged in the relation attribute * @@ -366,29 +412,60 @@ public void fillDublinCoreSchema() { Set> fieldValues = new TreeSet<>(Comparator.comparing(fieldStringEntry -> fieldStringEntry.getKey().getName())); fieldValues.addAll(bibEntry.getFieldMap().entrySet()); + boolean hasStandardYearField = fieldValues.stream().anyMatch(field -> StandardField.YEAR.equals(field.getKey())); for (Entry field : fieldValues) { if (useXmpPrivacyFilter && xmpPreferences.getXmpPrivacyFilter().contains(field.getKey())) { continue; } - if (StandardField.EDITOR.equals(field.getKey())) { - this.fillContributor(field.getValue()); - } else if (StandardField.AUTHOR.equals(field.getKey())) { - this.fillCreator(field.getValue()); - } else if (StandardField.YEAR.equals(field.getKey())) { - this.fillDate(); - } else if (StandardField.ABSTRACT.equals(field.getKey())) { - this.fillDescription(field.getValue()); - } else if (StandardField.DOI.equals(field.getKey())) { - this.fillIdentifier(field.getValue()); - } else if (StandardField.PUBLISHER.equals(field.getKey())) { - this.fillPublisher(field.getValue()); - } else if (StandardField.KEYWORDS.equals(field.getKey())) { - this.fillKeywords(field.getValue()); - } else if (StandardField.TITLE.equals(field.getKey())) { - this.fillTitle(field.getValue()); + Field fieldEntry = field.getKey(); + if (fieldEntry instanceof StandardField) { + switch ((StandardField) fieldEntry) { + case EDITOR: + this.fillContributor(field.getValue()); + break; + case AUTHOR: + this.fillCreator(field.getValue()); + break; + case YEAR: + this.fillDate(); + break; + case ABSTRACT: + this.fillDescription(field.getValue()); + break; + case DOI: + this.fillIdentifier(field.getValue()); + break; + case PUBLISHER: + this.fillPublisher(field.getValue()); + break; + case KEYWORDS: + this.fillKeywords(field.getValue()); + break; + case TITLE: + this.fillTitle(field.getValue()); + break; + case LANGUAGE: + this.fillLanguages(field.getValue()); + break; + case DAY: + case MONTH: + if (hasStandardYearField) { + break; + } + default: + this.fillCustomField(field.getKey(), field.getValue()); + } } else { - this.fillCustomField(field.getKey(), field.getValue()); + if (DC_COVERAGE.equals(fieldEntry.getName())) { + this.fillCoverage(field.getValue()); + } else if (DC_RIGHTS.equals(fieldEntry.getName())) { + this.fillRights(field.getValue()); + } else if (DC_SOURCE.equals(fieldEntry.getName())) { + this.fillSource(field.getValue()); + } else { + this.fillCustomField(field.getKey(), field.getValue()); + } } } diff --git a/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java b/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java index 21be8d96000..51248543d94 100644 --- a/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java +++ b/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java @@ -10,6 +10,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import org.jabref.model.schema.DublinCoreSchemaCustom; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; @@ -77,8 +78,8 @@ public static List readXmp(Path path, XmpPreferences xmpPreferences) if (!xmpMetaList.isEmpty()) { // Only support Dublin Core since JabRef 4.2 for (XMPMetadata xmpMeta : xmpMetaList) { - DublinCoreSchema dcSchema = xmpMeta.getDublinCoreSchema(); + DublinCoreSchema dcSchema = DublinCoreSchemaCustom.copyDublinCoreSchema(xmpMeta.getDublinCoreSchema()); if (dcSchema != null) { DublinCoreExtractor dcExtractor = new DublinCoreExtractor(dcSchema, xmpPreferences, new BibEntry()); Optional entry = dcExtractor.extractBibtexEntry(); diff --git a/src/main/java/org/jabref/logic/xmp/XmpUtilWriter.java b/src/main/java/org/jabref/logic/xmp/XmpUtilWriter.java index 7133d3773b6..60fbc825b5b 100644 --- a/src/main/java/org/jabref/logic/xmp/XmpUtilWriter.java +++ b/src/main/java/org/jabref/logic/xmp/XmpUtilWriter.java @@ -19,6 +19,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.schema.DublinCoreSchemaCustom; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; @@ -160,7 +161,7 @@ private static void writeDublinCore(PDDocument document, meta.removeSchema(meta.getDublinCoreSchema()); for (BibEntry entry : resolvedEntries) { - DublinCoreSchema dcSchema = meta.createAndAddDublinCoreSchema(); + DublinCoreSchema dcSchema = DublinCoreSchemaCustom.copyDublinCoreSchema(meta.createAndAddDublinCoreSchema()); XmpUtilWriter.writeToDCSchema(dcSchema, entry, null, xmpPreferences); } diff --git a/src/main/java/org/jabref/model/entry/Date.java b/src/main/java/org/jabref/model/entry/Date.java index b63b78c7781..a5266eb7521 100644 --- a/src/main/java/org/jabref/model/entry/Date.java +++ b/src/main/java/org/jabref/model/entry/Date.java @@ -20,18 +20,19 @@ public class Date { private static final DateTimeFormatter SIMPLE_DATE_FORMATS; static { List formatStrings = Arrays.asList( - "uuuu-M-d", // covers 2009-1-15 - "uuuu-M", // covers 2009-11 - "d-M-uuuu", // covers 15-1-2012 - "M-uuuu", // covers 1-2012 - "M/uuuu", // covers 9/2015 and 09/2015 - "M/uu", // covers 9/15 - "MMMM d, uuuu", // covers September 1, 2015 - "MMMM, uuuu", // covers September, 2015 - "d.M.uuuu", // covers 15.1.2015 - "uuuu.M.d", // covers 2015.1.15 - "uuuu", // covers 2015 - "MMM, uuuu"); // covers Jan, 2020 + "uuuu-MM-dd'T'HH:mm:ss[xxx][xx][X]", // covers 2018-10-03T07:24:14+03:00 + "uuuu-M-d", // covers 2009-1-15 + "uuuu-M", // covers 2009-11 + "d-M-uuuu", // covers 15-1-2012 + "M-uuuu", // covers 1-2012 + "M/uuuu", // covers 9/2015 and 09/2015 + "M/uu", // covers 9/15 + "MMMM d, uuuu", // covers September 1, 2015 + "MMMM, uuuu", // covers September, 2015 + "d.M.uuuu", // covers 15.1.2015 + "uuuu.M.d", // covers 2015.1.15 + "uuuu", // covers 2015 + "MMM, uuuu"); // covers Jan, 2020 SIMPLE_DATE_FORMATS = formatStrings.stream() .map(DateTimeFormatter::ofPattern) diff --git a/src/main/java/org/jabref/model/schema/DublinCoreSchemaCustom.java b/src/main/java/org/jabref/model/schema/DublinCoreSchemaCustom.java new file mode 100644 index 00000000000..e4d4cfe3215 --- /dev/null +++ b/src/main/java/org/jabref/model/schema/DublinCoreSchemaCustom.java @@ -0,0 +1,66 @@ +package org.jabref.model.schema; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.xmpbox.XMPMetadata; +import org.apache.xmpbox.schema.DublinCoreSchema; +import org.apache.xmpbox.type.AbstractField; +import org.apache.xmpbox.type.ArrayProperty; +import org.apache.xmpbox.type.DateType; +import org.apache.xmpbox.type.StructuredType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A DublinCoreSchema extension Class. + * In case anyone intends to alter standard behaviour. + */ +@StructuredType(preferedPrefix = "dc", namespace = "http://purl.org/dc/elements/1.1/") +public class DublinCoreSchemaCustom extends DublinCoreSchema { + + private static final Logger LOGGER = LoggerFactory.getLogger(DublinCoreSchemaCustom.class); + + public DublinCoreSchemaCustom(XMPMetadata metadata) { + super(metadata); + } + + public static DublinCoreSchema copyDublinCoreSchema(DublinCoreSchema dcSchema) { + if (Objects.isNull(dcSchema)) { + return null; + } + + try { + DublinCoreSchemaCustom dublinCoreSchemaCustom = new DublinCoreSchemaCustom(dcSchema.getMetadata()); + FieldUtils.writeField(dublinCoreSchemaCustom, "container", dcSchema.getContainer(), true); + FieldUtils.writeField(dublinCoreSchemaCustom, "attributes", + FieldUtils.readField(dcSchema, "attributes", true), true); + return dublinCoreSchemaCustom; + } catch (Exception e) { + LOGGER.error("Error making custom DC Schema. Using the default", e); + return dcSchema; + } + } + + /** + * Overloaded XMP Schema method + * Behaviour is same except when seqName is "Date". Will return raw value instead + */ + @Override + public List getUnqualifiedSequenceValueList(String seqName) { + AbstractField abstractProperty = getAbstractProperty(seqName); + if (abstractProperty instanceof ArrayProperty) { + if ("date".equals(seqName)) { + return ((ArrayProperty) abstractProperty).getContainer() + .getAllProperties() + .stream() + .map(field -> (String) ((DateType) field).getRawValue()) + .collect(Collectors.toList()); + } + return ((ArrayProperty) abstractProperty).getElementsAsString(); + } + return null; + } +} diff --git a/src/test/java/org/jabref/logic/xmp/XmpUtilReaderTest.java b/src/test/java/org/jabref/logic/xmp/XmpUtilReaderTest.java index 8f012187d37..fe93d9f0504 100644 --- a/src/test/java/org/jabref/logic/xmp/XmpUtilReaderTest.java +++ b/src/test/java/org/jabref/logic/xmp/XmpUtilReaderTest.java @@ -10,11 +10,11 @@ import java.util.Optional; import org.jabref.logic.importer.ImportFormatPreferences; -import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.fileformat.BibtexImporter; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import org.jabref.model.schema.DublinCoreSchemaCustom; import org.jabref.model.util.DummyFileUpdateMonitor; import org.apache.xmpbox.XMPMetadata; @@ -53,15 +53,15 @@ void setUp() { * Tests reading of dublinCore metadata. */ @Test - void testReadArticleDublinCoreReadRawXmp() throws IOException, URISyntaxException, ParseException { - Path path = Path.of(XmpUtilShared.class.getResource("article_dublinCore.pdf").toURI()); + void testReadArticleDublinCoreReadRawXmp() throws IOException, URISyntaxException { + Path path = Path.of(XmpUtilShared.class.getResource("article_dublinCore_without_day.pdf").toURI()); List meta = XmpUtilReader.readRawXmp(path); - DublinCoreSchema dcSchema = meta.get(0).getDublinCoreSchema(); + DublinCoreSchema dcSchema = DublinCoreSchemaCustom.copyDublinCoreSchema(meta.get(0).getDublinCoreSchema()); DublinCoreExtractor dcExtractor = new DublinCoreExtractor(dcSchema, xmpPreferences, new BibEntry()); Optional entry = dcExtractor.extractBibtexEntry(); - Path bibFile = Path.of(XmpUtilShared.class.getResource("article_dublinCore.bib").toURI()); + Path bibFile = Path.of(XmpUtilShared.class.getResource("article_dublinCore_without_day.bib").toURI()); List expected = testImporter.importDatabase(bibFile, StandardCharsets.UTF_8).getDatabase().getEntries(); assertEquals(expected, Collections.singletonList(entry.get())); @@ -85,6 +85,20 @@ void testReadArticleDublinCoreReadXmp() throws IOException, URISyntaxException { assertEquals(expected, entries); } + @Test + void testReadArticleDublinCoreReadXmpPartialDate() throws IOException, URISyntaxException { + Path pathPdf = Path.of(XmpUtilShared.class.getResource("article_dublinCore_partial_date.pdf").toURI()); + List entries = XmpUtilReader.readXmp(pathPdf, xmpPreferences); + Path bibFile = Path.of(XmpUtilShared.class.getResource("article_dublinCore_partial_date.bib").toURI()); + List expected = testImporter.importDatabase(bibFile, StandardCharsets.UTF_8).getDatabase().getEntries(); + + expected.forEach(bibEntry -> bibEntry.setFiles(Arrays.asList( + new LinkedFile("", pathPdf.toAbsolutePath(), "PDF")) + )); + + assertEquals(expected, entries); + } + /** * Tests an pdf file with an empty metadata section. */ diff --git a/src/test/java/org/jabref/logic/xmp/XmpUtilWriterTest.java b/src/test/java/org/jabref/logic/xmp/XmpUtilWriterTest.java index 6013264bea4..537e0d08a42 100644 --- a/src/test/java/org/jabref/logic/xmp/XmpUtilWriterTest.java +++ b/src/test/java/org/jabref/logic/xmp/XmpUtilWriterTest.java @@ -8,8 +8,10 @@ import javax.xml.transform.TransformerException; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Date; import org.jabref.model.entry.Month; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; import org.jabref.model.entry.types.StandardEntryType; import org.apache.pdfbox.pdmodel.PDDocument; @@ -18,6 +20,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import static org.jabref.logic.xmp.DublinCoreExtractor.DC_COVERAGE; +import static org.jabref.logic.xmp.DublinCoreExtractor.DC_RIGHTS; +import static org.jabref.logic.xmp.DublinCoreExtractor.DC_SOURCE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -68,6 +73,12 @@ private void initBibEntries() { vapnik2000.setField(StandardField.AUTHOR, "Vladimir N. Vapnik"); vapnik2000.setField(StandardField.DOI, "10.1007/978-1-4757-3264-1"); vapnik2000.setField(StandardField.OWNER, "Ich"); + vapnik2000.setField(StandardField.LANGUAGE, "English, Japanese"); + vapnik2000.setDate(new Date(2000, 5)); + + vapnik2000.setField(new UnknownField(DC_COVERAGE), "coverageField"); + vapnik2000.setField(new UnknownField((DC_SOURCE)), "JabRef"); + vapnik2000.setField(new UnknownField(DC_RIGHTS), "Right To X"); } /** diff --git a/src/test/resources/org/jabref/logic/xmp/article_dublinCore.bib b/src/test/resources/org/jabref/logic/xmp/article_dublinCore.bib index 82c5604dd38..f959a333921 100644 --- a/src/test/resources/org/jabref/logic/xmp/article_dublinCore.bib +++ b/src/test/resources/org/jabref/logic/xmp/article_dublinCore.bib @@ -7,6 +7,7 @@ @Article{Olly2018 number = {1}, pages = {1-2}, month = mar, + day = {1}, issn = {978-123-123}, note = {That's a note}, abstract = {That's an abstract}, diff --git a/src/test/resources/org/jabref/logic/xmp/article_dublinCore_partial_date.bib b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_partial_date.bib new file mode 100644 index 00000000000..39c4e5d137e --- /dev/null +++ b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_partial_date.bib @@ -0,0 +1,8 @@ +@Article{, + coverage = {coverageField}, + language = {English,Japanese}, + month = jan, + rights = {Right To X}, + source = {JabRef}, + year = {2005}, +} diff --git a/src/test/resources/org/jabref/logic/xmp/article_dublinCore_partial_date.pdf b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_partial_date.pdf new file mode 100644 index 00000000000..b9343488134 Binary files /dev/null and b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_partial_date.pdf differ diff --git a/src/test/resources/org/jabref/logic/xmp/article_dublinCore_without_day.bib b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_without_day.bib new file mode 100644 index 00000000000..82c5604dd38 --- /dev/null +++ b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_without_day.bib @@ -0,0 +1,23 @@ +@Article{Olly2018, + author = {Olly and Johannes}, + title = {Stefan's palace}, + journal = {Test Journal}, + year = {2018}, + volume = {1}, + number = {1}, + pages = {1-2}, + month = mar, + issn = {978-123-123}, + note = {That's a note}, + abstract = {That's an abstract}, + comment = {That's a comment}, + doi = {10/3212.3123}, + file = {:paper.pdf:PDF}, + groups = {NO}, + howpublished = {Online}, + keywords = {Keyword1, Keyword2}, + owner = {Me}, + review = {Here are the reviews}, + timestamp = {2018-02-15}, + url = {https://www.olly2018.edu}, +} diff --git a/src/test/resources/org/jabref/logic/xmp/article_dublinCore_without_day.pdf b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_without_day.pdf new file mode 100644 index 00000000000..ae2d982b0a3 Binary files /dev/null and b/src/test/resources/org/jabref/logic/xmp/article_dublinCore_without_day.pdf differ