From aa82e640fd11d7115180963b008eb1a478d43b85 Mon Sep 17 00:00:00 2001 From: InAnYan Date: Tue, 11 Aug 2026 17:24:43 +0200 Subject: [PATCH 1/3] fix(fetchers): fix OpenAlex entry type assignment --- .../logic/importer/fetcher/OpenAlex.java | 59 +++++++++++++++++-- .../importer/fetcher/OpenAlexFetcherTest.java | 5 +- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java b/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java index ff3122f7c31a..7923f55da3b7 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java @@ -7,6 +7,8 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; @@ -31,7 +33,10 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; -import org.jabref.model.entry.types.EntryTypeFactory; +import org.jabref.model.entry.types.BiblatexNonStandardEntryType; +import org.jabref.model.entry.types.EntryType; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.entry.types.UnknownEntryType; import org.jabref.model.search.query.BaseQueryNode; import com.google.common.annotations.VisibleForTesting; @@ -55,6 +60,33 @@ public class OpenAlex implements CustomizableKeyFetcher, SearchBasedParserFetche private static final Logger LOGGER = LoggerFactory.getLogger(OpenAlex.class); private static final String URL_PATTERN = "https://api.openalex.org/works"; + private static final Map OPENALEX_TYPE_TO_ENTRY_TYPE = Map.ofEntries( + Map.entry("article", StandardEntryType.Article), + Map.entry("other", StandardEntryType.Misc), + Map.entry("dataset", StandardEntryType.Dataset), + Map.entry("book-chapter", StandardEntryType.InCollection), + Map.entry("dissertation", StandardEntryType.Thesis), + Map.entry("conference-paper", StandardEntryType.InProceedings), + Map.entry("book", StandardEntryType.Book), + Map.entry("preprint", StandardEntryType.Online), + Map.entry("paratext", StandardEntryType.Misc), + Map.entry("conference-abstract", StandardEntryType.InProceedings), + Map.entry("report", StandardEntryType.Report), + Map.entry("reference-entry", StandardEntryType.InReference), + Map.entry("book-review", BiblatexNonStandardEntryType.Review), + Map.entry("libguides", StandardEntryType.Online), + Map.entry("peer-review", BiblatexNonStandardEntryType.Review), + Map.entry("editorial", StandardEntryType.Article), + Map.entry("review", StandardEntryType.Article), + Map.entry("software", StandardEntryType.Software), + Map.entry("supplementary-materials", StandardEntryType.Dataset), + Map.entry("letter", StandardEntryType.Article), + Map.entry("erratum", StandardEntryType.Article), + Map.entry("standard", StandardEntryType.Manual), + Map.entry("retraction", StandardEntryType.Article), + Map.entry("data-paper", StandardEntryType.Article), + Map.entry("software-paper", StandardEntryType.Article) + ); private final ImporterPreferences importerPreferences; @@ -67,6 +99,18 @@ public String getName() { return FETCHER_NAME; } + @VisibleForTesting + EntryType mapOpenAlexTypeToEntryType(String openAlexType) { + String normalizedOpenAlexType = openAlexType.toLowerCase(Locale.ENGLISH); + + EntryType entryType = OPENALEX_TYPE_TO_ENTRY_TYPE.get(normalizedOpenAlexType); + if (entryType != null) { + return entryType; + } + + return new UnknownEntryType(openAlexType); + } + @VisibleForTesting Optional extractOpenAlexId(String url) { if (StringUtil.isBlank(url)) { @@ -178,7 +222,10 @@ private BibEntry jsonItemToBibEntry(JSONObject item) throws ParseException { DoiCleanup DoiCleanup = new DoiCleanup(); BibEntry entry = new BibEntry(); - entry.setType(EntryTypeFactory.parse(item.getString("type"))); + String openAlexType = item.optString("type", null); + if (openAlexType != null) { + entry.setType(mapOpenAlexTypeToEntryType(openAlexType)); + } entry.setField(StandardField.TITLE, item.optString("title")); @@ -289,7 +336,7 @@ public Optional findFullText(BibEntry entry) throws IOException, FetcherExc .filter(Objects::nonNull) .map(primaryLocation -> primaryLocation.optString("pdf_url", "")) .filter(StringUtil::isNotBlank) - .map(Unchecked.function(pdfUrl -> URLUtil.create(pdfUrl))); + .map(Unchecked.function(URLUtil::create)); } catch (RuntimeException e) { LOGGER.warn("Malformed URL", e); throw (MalformedURLException) e.getCause(); @@ -314,7 +361,7 @@ public TrustLevel getTrustLevel() { private List workUrlsToBibEntryList(@Nullable JSONArray workUrlArray) { if (workUrlArray == null) { - List.of(); + return List.of(); } // TODO: This could be batched - see https://github.com/JabRef/jabref/pull/15023#issuecomment-3846630255 return IntStream.range(0, workUrlArray.length()) @@ -351,7 +398,7 @@ private List workArrayToBibEntryList(@Nullable JSONArray workUrlArray) } return IntStream.range(0, workUrlArray.length()) .mapToObj(workUrlArray::getJSONObject) - .map(Unchecked.function(jsonItem -> jsonItemToBibEntry(jsonItem))) + .map(Unchecked.function(this::jsonItemToBibEntry)) .toList(); } @@ -421,7 +468,7 @@ public Optional getCitationsApiUri(BibEntry entry) { // Instead, we perform a search for works that cite the given work's ID try { return getWorkObject(entry, List.of("id")) - .map(work -> work.optString("id")) + .map(work -> work.optString("id", null)) .filter(Objects::nonNull) .map(Unchecked.function(id -> getUriBuilder("", List.of()) diff --git a/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java b/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java index 4742dad5fc82..0b2fbb1319ca 100644 --- a/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java +++ b/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java @@ -164,16 +164,17 @@ void getURLForQueryWithLucene() throws MalformedURLException, URISyntaxException @Test void searchByQueryFindsEntry() throws FetcherException { - BibEntry master = new BibEntry(StandardEntryType.Article) + BibEntry master = new BibEntry(StandardEntryType.InProceedings) .withField(StandardField.AUTHOR, "Matthew Tancik and Vincent Casser and Xinchen Yan and Sabeek Pradhan and Ben Mildenhall and Pratul P. Srinivasan and Jonathan T. Barron and Henrik Kretzschmar") .withField(StandardField.TITLE, "Block-NeRF: Scalable Large Scene Neural View Synthesis") - .withField(StandardField.YEAR, "2022") .withField(StandardField.DOI, "10.1109/cvpr52688.2022.00807") .withField(StandardField.URL, "https://openalex.org/W4312280420"); List fetchedEntries = fetcher.performSearch("Block-NeRF: Scalable Large Scene Neural View Synthesis"); fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); fetchedEntries.forEach(entry -> entry.clearField(StandardField.PAGES)); fetchedEntries.forEach(entry -> entry.clearField(StandardField.KEYWORDS)); + fetchedEntries.forEach(entry -> entry.clearField(StandardField.YEAR)); + fetchedEntries.forEach(entry -> entry.clearField(StandardField.DATE)); assertEquals(master, fetchedEntries.getFirst()); } From ce757d94e28aefed69ce293754bbbd0dad23211c Mon Sep 17 00:00:00 2001 From: InAnYan Date: Tue, 11 Aug 2026 17:28:57 +0200 Subject: [PATCH 2/3] refactor(fetchers): change type mappings in OpenAlex --- .../java/org/jabref/logic/importer/fetcher/OpenAlex.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java b/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java index 7923f55da3b7..3cf51c97d8de 100644 --- a/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java +++ b/jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java @@ -64,7 +64,7 @@ public class OpenAlex implements CustomizableKeyFetcher, SearchBasedParserFetche Map.entry("article", StandardEntryType.Article), Map.entry("other", StandardEntryType.Misc), Map.entry("dataset", StandardEntryType.Dataset), - Map.entry("book-chapter", StandardEntryType.InCollection), + Map.entry("book-chapter", StandardEntryType.InBook), Map.entry("dissertation", StandardEntryType.Thesis), Map.entry("conference-paper", StandardEntryType.InProceedings), Map.entry("book", StandardEntryType.Book), @@ -79,11 +79,11 @@ public class OpenAlex implements CustomizableKeyFetcher, SearchBasedParserFetche Map.entry("editorial", StandardEntryType.Article), Map.entry("review", StandardEntryType.Article), Map.entry("software", StandardEntryType.Software), - Map.entry("supplementary-materials", StandardEntryType.Dataset), + Map.entry("supplementary-materials", StandardEntryType.Misc), Map.entry("letter", StandardEntryType.Article), Map.entry("erratum", StandardEntryType.Article), - Map.entry("standard", StandardEntryType.Manual), - Map.entry("retraction", StandardEntryType.Article), + Map.entry("standard", StandardEntryType.Misc), + Map.entry("retraction", StandardEntryType.Misc), Map.entry("data-paper", StandardEntryType.Article), Map.entry("software-paper", StandardEntryType.Article) ); From ff3514262df4d74309d52e3f7239575ae5c10691 Mon Sep 17 00:00:00 2001 From: InAnYan Date: Thu, 13 Aug 2026 11:44:39 +0200 Subject: [PATCH 3/3] fix(fetchers): fix OpenAlex tests --- .../importer/fetcher/OpenAlexFetcherTest.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java b/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java index 0b2fbb1319ca..a89a7f97f530 100644 --- a/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java +++ b/jablib/src/test/java/org/jabref/logic/importer/fetcher/OpenAlexFetcherTest.java @@ -39,13 +39,6 @@ class OpenAlexFetcherTest { private OpenAlex fetcher; - private final BibEntry NERF = new BibEntry(StandardEntryType.Article) - .withField(StandardField.AUTHOR, "Haithem Turki and Deva Ramanan and Mahadev Satyanarayanan") - .withField(StandardField.YEAR, "2022") - .withField(StandardField.DOI, "10.1109/cvpr52688.2022.01258") - .withField(StandardField.TITLE, "Mega-NeRF: Scalable Construction of Large-Scale NeRFs for Virtual Fly- Throughs") - .withField(StandardField.URL, "https://openalex.org/W4313031684"); - @BeforeEach void setUp() { ImporterPreferences importerPreferences = mock(ImporterPreferences.class); @@ -168,13 +161,12 @@ void searchByQueryFindsEntry() throws FetcherException { .withField(StandardField.AUTHOR, "Matthew Tancik and Vincent Casser and Xinchen Yan and Sabeek Pradhan and Ben Mildenhall and Pratul P. Srinivasan and Jonathan T. Barron and Henrik Kretzschmar") .withField(StandardField.TITLE, "Block-NeRF: Scalable Large Scene Neural View Synthesis") .withField(StandardField.DOI, "10.1109/cvpr52688.2022.00807") + .withField(StandardField.DATE, "2022-06-01") .withField(StandardField.URL, "https://openalex.org/W4312280420"); List fetchedEntries = fetcher.performSearch("Block-NeRF: Scalable Large Scene Neural View Synthesis"); fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); fetchedEntries.forEach(entry -> entry.clearField(StandardField.PAGES)); fetchedEntries.forEach(entry -> entry.clearField(StandardField.KEYWORDS)); - fetchedEntries.forEach(entry -> entry.clearField(StandardField.YEAR)); - fetchedEntries.forEach(entry -> entry.clearField(StandardField.DATE)); assertEquals(master, fetchedEntries.getFirst()); } @@ -185,10 +177,17 @@ void performSearchByEmptyQuery() throws FetcherException { @Test void searchByQuotedQueryFindsEntry() throws FetcherException { + BibEntry expected = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Haithem Turki and Deva Ramanan and Mahadev Satyanarayanan") + .withField(StandardField.DATE, "2022-06-01") + .withField(StandardField.DOI, "10.1109/cvpr52688.2022.01258") + .withField(StandardField.TITLE, "Mega-NeRF: Scalable Construction of Large-Scale NeRFs for Virtual Fly- Throughs") + .withField(StandardField.URL, "https://openalex.org/W4313031684"); + List fetchedEntries = fetcher.performSearch("\"Mega-NeRF: Scalable Construction of Large-Scale NeRFs for Virtual Fly- Throughs\""); fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); fetchedEntries.forEach(entry -> entry.clearField(StandardField.PAGES)); fetchedEntries.forEach(entry -> entry.clearField(StandardField.KEYWORDS)); - assertEquals(NERF, fetchedEntries.getFirst()); + assertEquals(expected, fetchedEntries.getFirst()); } }