Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<String, EntryType> OPENALEX_TYPE_TO_ENTRY_TYPE = Map.ofEntries(
Map.entry("article", StandardEntryType.Article),
Map.entry("other", StandardEntryType.Misc),
Map.entry("dataset", StandardEntryType.Dataset),
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Map.entry("book-chapter", StandardEntryType.InBook),
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),
Comment on lines +75 to +78

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Biblatex-only types mapped 🐞 Bug ≡ Correctness

OpenAlex maps some OpenAlex work types to BibLaTeX-exclusive EntryTypes (e.g.,
BiblatexNonStandardEntryType.Review, StandardEntryType.Report/Thesis/Online/Dataset/Software).
In BibTeX-mode libraries this triggers an integrity warning (BibTeXEntryTypeChecker) and exports
the non-BibTeX entry type header verbatim, which many BibTeX toolchains may not recognize.
Agent Prompt
## Issue description
OpenAlex’s type mapping includes BibLaTeX-only entry types (notably `BiblatexNonStandardEntryType.Review` and several BibLaTeX-only `StandardEntryType`s). When a user is working in a BibTeX-mode library, JabRef will flag these as “only defined for BibLaTeX” and exported `.bib` files will contain those entry-type headers.

## Issue Context
- `BibTeXEntryTypeChecker` warns in BibTeX mode when `EntryTypeFactory.isExclusiveBiblatex(entry.getType())` is true.
- `StandardEntryType` clearly separates BibTeX vs BibLaTeX-only types in the enum.
- `BibEntryWriter` writes `@` + `entry.getType().getDisplayName()` regardless of database mode.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/importer/fetcher/OpenAlex.java[62-89]

### Suggested remediation
Adjust the `OPENALEX_TYPE_TO_ENTRY_TYPE` mapping to avoid emitting BibLaTeX-exclusive types when there is a reasonable BibTeX-compatible alternative:
- Map `report` to `StandardEntryType.TechReport` (supported in both definitions).
- Map `dissertation` to `StandardEntryType.PhdThesis` or `StandardEntryType.MastersThesis` (BibTeX types), or fall back to `Misc`.
- Map `book-review` / `peer-review` to a BibTeX-safe type (e.g., `Misc` or `Article`) instead of `BiblatexNonStandardEntryType.Review`.
- Consider a design where the mapping is chosen based on the target library mode (BibTeX vs BibLaTeX) if that information is available at the call site; otherwise prefer BibTeX-safe defaults to avoid generating BibLaTeX-only types unexpectedly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Map.entry("editorial", StandardEntryType.Article),
Map.entry("review", StandardEntryType.Article),
Map.entry("software", StandardEntryType.Software),
Map.entry("supplementary-materials", StandardEntryType.Misc),
Map.entry("letter", StandardEntryType.Article),
Map.entry("erratum", StandardEntryType.Article),
Map.entry("standard", StandardEntryType.Misc),
Map.entry("retraction", StandardEntryType.Misc),
Map.entry("data-paper", StandardEntryType.Article),
Map.entry("software-paper", StandardEntryType.Article)
);

private final ImporterPreferences importerPreferences;

Expand All @@ -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<String> extractOpenAlexId(String url) {
if (StringUtil.isBlank(url)) {
Expand Down Expand Up @@ -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));
}
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

entry.setField(StandardField.TITLE, item.optString("title"));

Expand Down Expand Up @@ -289,7 +336,7 @@ public Optional<URL> 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();
Expand All @@ -314,7 +361,7 @@ public TrustLevel getTrustLevel() {

private List<BibEntry> 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())
Expand Down Expand Up @@ -351,7 +398,7 @@ private List<BibEntry> 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();
}

Expand Down Expand Up @@ -421,7 +468,7 @@ public Optional<URI> 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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -164,11 +157,11 @@ 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.DATE, "2022-06-01")
.withField(StandardField.URL, "https://openalex.org/W4312280420");
List<BibEntry> fetchedEntries = fetcher.performSearch("Block-NeRF: Scalable Large Scene Neural View Synthesis");
fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT));
Expand All @@ -184,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<BibEntry> 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());
}
}
Loading