diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index 8fbad5a1285..1a8ca63172f 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit 8fbad5a1285926b177803087b35b0eb6b0fd0142 +Subproject commit 1a8ca63172f96b77632810bb726dbc6a2df7ac7e diff --git a/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java index 40e52fb98da..1ca274e5e83 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -57,9 +58,23 @@ private static class CffFormat { @JsonProperty("identifiers") private List ids; + @JsonProperty("preferred-citation") + private JsonNode preferredCitation; + + @JsonProperty("type") + private String type; + + public JsonNode getPreferredCitation() { + return preferredCitation; + } + public CffFormat() { } + public void setPreferredCitation(JsonNode preferredCitation) { + this.preferredCitation = preferredCitation; + } + @JsonAnySetter private void setValues(String key, String value) { values.put(key, value); @@ -92,6 +107,7 @@ public CffIdentifier() { public ParserResult importDatabase(BufferedReader reader) throws IOException { ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); CffFormat citation = mapper.readValue(reader, CffFormat.class); + List entriesList = new ArrayList<>(); HashMap entryMap = new HashMap<>(); StandardEntryType entryType = StandardEntryType.Software; @@ -111,20 +127,20 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { // Translate CFF author format to JabRef author format String authorStr = citation.authors.stream() - .map(author -> author.values) - .map(vals -> vals.get("name") != null ? - new Author(vals.get("name"), "", "", "", "") : - new Author(vals.get("given-names"), null, vals.get("name-particle"), - vals.get("family-names"), vals.get("name-suffix"))) - .collect(AuthorList.collect()) - .getAsFirstLastNamesWithAnd(); + .map(author -> author.values) + .map(vals -> vals.get("name") != null ? + new Author(vals.get("name"), "", "", "", "") : + new Author(vals.get("given-names"), null, vals.get("name-particle"), + vals.get("family-names"), vals.get("name-suffix"))) + .collect(AuthorList.collect()) + .getAsFirstLastNamesWithAnd(); entryMap.put(StandardField.AUTHOR, authorStr); // Select DOI to keep if ((entryMap.get(StandardField.DOI) == null) && (citation.ids != null)) { List doiIds = citation.ids.stream() - .filter(id -> "doi".equals(id.type)) - .collect(Collectors.toList()); + .filter(id -> "doi".equals(id.type)) + .collect(Collectors.toList()); if (doiIds.size() == 1) { entryMap.put(StandardField.DOI, doiIds.getFirst().value); } @@ -133,9 +149,9 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { // Select SWHID to keep if (citation.ids != null) { List swhIds = citation.ids.stream() - .filter(id -> "swh".equals(id.type)) - .map(id -> id.value) - .collect(Collectors.toList()); + .filter(id -> "swh".equals(id.type)) + .map(id -> id.value) + .collect(Collectors.toList()); if (swhIds.size() == 1) { entryMap.put(BiblatexSoftwareField.SWHID, swhIds.getFirst()); @@ -149,16 +165,72 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { } } } + // Handle the main citation as a separate entry + BibEntry mainEntry = new BibEntry(entryType); + mainEntry.setField(entryMap); + + HashMap fieldMappings = getFieldMappings(); + // Now handle preferred citation as its own entry + if (citation.getPreferredCitation() != null) { + HashMap preferredEntryMap = new HashMap<>(); + processPreferredCitation(citation.getPreferredCitation(), preferredEntryMap, entriesList, fieldMappings); + } BibEntry entry = new BibEntry(entryType); entry.setField(entryMap); - List entriesList = new ArrayList<>(); entriesList.add(entry); return new ParserResult(entriesList); } + private void processPreferredCitation(JsonNode preferredCitation, HashMap entryMap, List entriesList, HashMap fieldMappings) { + if (preferredCitation.isObject()) { + BibEntry preferredEntry = new BibEntry(); + preferredCitation.fields().forEachRemaining(field -> { + String key = field.getKey(); + JsonNode value = field.getValue(); + + if (fieldMappings.containsKey(key)) { + preferredEntry.setField(fieldMappings.get(key), value.asText()); + } else if ("authors".equals(key) && value.isArray()) { + preferredEntry.setField(StandardField.AUTHOR, parseAuthors(value)); + } else if ("journal".equals(key)) { + preferredEntry.setField(StandardField.JOURNAL, value.asText()); + } else if ("doi".equals(key)) { + preferredEntry.setField(StandardField.DOI, value.asText()); + } else if ("year".equals(key)) { + preferredEntry.setField(StandardField.YEAR, value.asText()); + } else if ("volume".equals(key)) { + preferredEntry.setField(StandardField.VOLUME, value.asText()); + } else if ("issue".equals(key)) { + preferredEntry.setField(StandardField.ISSUE, value.asText()); + } else if ("pages".equals(key)) { + String pages = value.has("start") && value.has("end") + ? value.get("start").asText() + "--" + value.get("end").asText() + : value.asText(); + preferredEntry.setField(StandardField.PAGES, pages); + } + }); + if (!preferredEntry.getField(StandardField.TITLE).orElse("").isEmpty()) { + entriesList.add(preferredEntry); + } + } + } + + private String parseAuthors(JsonNode authorsNode) { + StringBuilder authors = new StringBuilder(); + for (JsonNode authorNode : authorsNode) { + String givenNames = authorNode.has("given-names") ? authorNode.get("given-names").asText() : ""; + String familyNames = authorNode.has("family-names") ? authorNode.get("family-names").asText() : ""; + authors.append(givenNames).append(" ").append(familyNames).append(" and "); + } + if (authors.lastIndexOf(" and ") == authors.length() - 5) { + authors.delete(authors.length() - 5, authors.length()); + } + return authors.toString(); + } + @Override public boolean isRecognizedFormat(BufferedReader reader) throws IOException { @@ -173,6 +245,21 @@ public boolean isRecognizedFormat(BufferedReader reader) throws IOException { } } + private StandardEntryType mapType(String cffType) { + return switch (cffType) { + case "article" -> StandardEntryType.Article; + case "book" -> StandardEntryType.Book; + case "conference" -> StandardEntryType.InProceedings; + case "proceedings" -> StandardEntryType.Proceedings; + case "misc" -> StandardEntryType.Misc; + case "manual" -> StandardEntryType.Manual; + case "software" -> StandardEntryType.Software; + case "report" -> StandardEntryType.TechReport; + case "unpublished" -> StandardEntryType.Unpublished; + default -> StandardEntryType.Dataset; + }; + } + private HashMap getFieldMappings() { HashMap fieldMappings = new HashMap<>(); fieldMappings.put("title", StandardField.TITLE); diff --git a/src/test/java/org/jabref/logic/importer/ImporterTest.java b/src/test/java/org/jabref/logic/importer/ImporterTest.java index 4db39bf1635..a37577a37e9 100644 --- a/src/test/java/org/jabref/logic/importer/ImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/ImporterTest.java @@ -6,6 +6,7 @@ import org.jabref.logic.importer.fileformat.BiblioscapeImporter; import org.jabref.logic.importer.fileformat.BibtexImporter; +import org.jabref.logic.importer.fileformat.CffImporter; import org.jabref.logic.importer.fileformat.CitaviXmlImporter; import org.jabref.logic.importer.fileformat.CopacImporter; import org.jabref.logic.importer.fileformat.EndnoteImporter; @@ -126,7 +127,8 @@ public static Stream instancesToTest() { new RepecNepImporter(importFormatPreferences), new RisImporter(), new SilverPlatterImporter(), - new CitaviXmlImporter() + new CitaviXmlImporter(), + new CffImporter() ); // @formatter:on } diff --git a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java index 54cbfee44bf..4d1b0f508ff 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java @@ -117,7 +117,7 @@ public void importEntriesDataset() throws IOException, URISyntaxException { BibEntry entry = bibEntries.getFirst(); BibEntry expected = getPopulatedEntry(); - expected.setType(StandardEntryType.Dataset); + expected.setType(StandardEntryType.Software); assertEquals(entry, expected); } @@ -144,6 +144,33 @@ public void importEntriesUnknownFields() throws IOException, URISyntaxException assertEquals(entry, expected); } + @Test + public void importCITATION() throws IOException, URISyntaxException { + Path file = Path.of(CffImporterTest.class.getResource("CITATION.cff").toURI()); + List bibEntries = importer.importDatabase(file).getDatabase().getEntries(); + BibEntry entry = bibEntries.getFirst(); + + BibEntry expected = getPopulatedEntry1(); + + assertEquals(entry, expected); + } + + public BibEntry getPopulatedEntry1() { + BibEntry entry = new BibEntry(StandardEntryType.Misc); + + String authors = "Oliver Kopp and Carl Christian Snethlage and Christoph Schwentker"; + entry.setField(StandardField.AUTHOR, authors); + entry.setField(StandardField.ISSUE, "138"); + entry.setField(StandardField.TITLE, "JabRef: BibTeX-based literature management software"); + entry.setField(StandardField.DOI, "10.47397/tb/44-3/tb138kopp-jabref"); + entry.setField(StandardField.JOURNAL, "TUGboat"); + entry.setField(StandardField.VOLUME, "44"); + entry.setField(StandardField.YEAR, "2023"); + + return entry; + } + + public BibEntry getPopulatedEntry() { BibEntry entry = new BibEntry(); entry.setType(StandardEntryType.Software); diff --git a/src/test/resources/org/jabref/logic/importer/fileformat/CITATION.cff b/src/test/resources/org/jabref/logic/importer/fileformat/CITATION.cff new file mode 100644 index 00000000000..58b0baac038 --- /dev/null +++ b/src/test/resources/org/jabref/logic/importer/fileformat/CITATION.cff @@ -0,0 +1,54 @@ +# YAML 1.2 +--- +cff-version: 1.2.0 +title: JabRef +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: Oliver + family-names: Kopp + orcid: 'https://orcid.org/0000-0001-6962-4290' + - given-names: Tobias + family-names: Diez + orcid: 'https://orcid.org/0000-0002-1407-7696' + - given-names: Christoph + family-names: Schwentker + - given-names: Carl Christian + family-names: Snethlage + - given-names: Jonatan + family-names: Asketorp + - given-names: Benedikt + family-names: Tutzer + - given-names: Thilo + family-names: Ertel + - given-names: Houssem + family-names: Nasri +repository-code: 'https://github.com/jabref/jabref/' +url: 'https://www.jabref.org' +abstract: >- + JabRef is an open-source, cross-platform citation and + reference management tool. +license: MIT +preferred-citation: + type: article + authors: + - family-names: "Kopp" + given-names: "Oliver" + orcid: "https://orcid.org/0000-0001-6962-4290" + - family-names: "Snethlage" + given-names: "Carl Christian" + - family-names: "Schwentker" + given-names: "Christoph" + doi: "10.47397/tb/44-3/tb138kopp-jabref" + journal: "TUGboat" + month: 11 + start: 441 + end: 447 + title: "JabRef: BibTeX-based literature management software" + issue: 138 + volume: 44 + number: 3 + year: 2023 +... diff --git a/src/test/resources/org/jabref/logic/importer/fileformat/CffImporterTestValid.cff b/src/test/resources/org/jabref/logic/importer/fileformat/CffImporterTestValid.cff index 472ba55169d..fd1971e7ea3 100644 --- a/src/test/resources/org/jabref/logic/importer/fileformat/CffImporterTestValid.cff +++ b/src/test/resources/org/jabref/logic/importer/fileformat/CffImporterTestValid.cff @@ -1,17 +1,18 @@ # YAML 1.2 --- -abstract: "Test abstract." authors: - family-names: Smith given-names: Joe name-particle: van cff-version: "1.1.0" +comment: "Test entry" date-released: 2000-07-02 doi: "10.0000/TEST" identifiers: license: MIT message: "Test entry." +abstract: "Test abstract." title: Test version: "1.0" url: "www.google.com"