Skip to content

Automatic lookup of DOI at citation relations #13539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- We added automatic lookup of DOI at citation relations [#13234](https://github.com/JabRef/jabref/issues/13234)
- We added focus on the field Link in the "Add file link" dialog. [#13486](https://github.com/JabRef/jabref/issues/13486)
- We introduced a settings parameter to manage citations' relations local storage time-to-live with a default value set to 30 days. [#11189](https://github.com/JabRef/jabref/issues/11189)
- We distribute arm64 images for Linux. [#10842](https://github.com/JabRef/jabref/issues/10842)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jabref.gui.entryeditor.citationrelationtab;

import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;

import org.jabref.logic.importer.fetcher.citation.CitationFetcher;
import org.jabref.model.entry.BibEntry;

import org.controlsfx.control.CheckListView;

public record CitationComponents(
BibEntry entry,
CheckListView<CitationRelationItem> listView,
Button abortButton,
Button refreshButton,
CitationFetcher.SearchType searchType,
Button importButton,
ProgressIndicator progress) {
Copy link

Choose a reason for hiding this comment

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

UI component (ProgressIndicator) should not be directly included in a data structure. This violates separation of concerns and should be managed in a dedicated UI controller.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is simple parameter object - instead passing 7 parameters, use dedicated object.

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
Expand Down Expand Up @@ -52,6 +53,7 @@
import org.jabref.logic.citation.SearchCitationsRelationsService;
import org.jabref.logic.database.DuplicateCheck;
import org.jabref.logic.exporter.BibWriter;
import org.jabref.logic.importer.fetcher.CrossRef;
import org.jabref.logic.importer.fetcher.citation.CitationFetcher;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.os.OS;
Expand Down Expand Up @@ -199,48 +201,34 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) {
citingVBox.getChildren().addAll(citingHBox, citingListView);
citedByVBox.getChildren().addAll(citedByHBox, citedByListView);

refreshCitingButton.setOnMouseClicked(_ -> {
searchForRelations(
entry,
citingListView,
abortCitingButton,
refreshCitingButton,
CitationFetcher.SearchType.CITES,
importCitingButton,
citingProgress);
});
CitationComponents citingComponents = new CitationComponents(
entry,
citingListView,
abortCitingButton,
refreshCitingButton,
CitationFetcher.SearchType.CITES,
importCitingButton,
citingProgress);

refreshCitedByButton.setOnMouseClicked(_ -> searchForRelations(
entry,
citedByListView,
CitationComponents citedByComponents = new CitationComponents(
entry,
citedByListView,
abortCitedButton,
refreshCitedByButton,
CitationFetcher.SearchType.CITED_BY,
importCitedByButton,
citedByProgress));
refreshCitedByButton,
CitationFetcher.SearchType.CITED_BY,
importCitedByButton,
citedByProgress);

refreshCitingButton.setOnMouseClicked(_ -> searchForRelations(citingComponents, citedByComponents));
refreshCitedByButton.setOnMouseClicked(_ -> searchForRelations(citedByComponents, citingComponents));

// Create SplitPane to hold all nodes above
SplitPane container = new SplitPane(citingVBox, citedByVBox);
styleFetchedListView(citedByListView);
styleFetchedListView(citingListView);

searchForRelations(
entry,
citingListView,
abortCitingButton,
refreshCitingButton,
CitationFetcher.SearchType.CITES,
importCitingButton,
citingProgress);

searchForRelations(
entry,
citedByListView,
abortCitedButton,
refreshCitedByButton,
CitationFetcher.SearchType.CITED_BY,
importCitedByButton,
citedByProgress);
searchForRelations(citingComponents, citedByComponents);
searchForRelations(citedByComponents, citingComponents);

return container;
}
Expand Down Expand Up @@ -271,9 +259,9 @@ private void styleFetchedListView(CheckListView<CitationRelationItem> listView)
jumpTo.getStyleClass().add("addEntryButton");
jumpTo.setOnMouseClicked(_ -> jumpToEntry(entry));
hContainer.setOnMouseClicked(event -> {
if (event.getClickCount() == 2) {
jumpToEntry(entry);
}
if (event.getClickCount() == 2) {
jumpToEntry(entry);
}
});
vContainer.getChildren().add(jumpTo);

Expand Down Expand Up @@ -423,59 +411,89 @@ protected void bindToEntry(BibEntry entry) {
setContent(getPaneAndStartSearch(entry));
}

/**
* Method to start search for relations and display them in the associated ListView
*
* @param entry BibEntry currently selected in Jabref Database
* @param listView ListView to use
* @param abortButton Button to stop the search
* @param refreshButton refresh Button to use
* @param searchType type of search (CITING / CITEDBY)
*/
private void searchForRelations(BibEntry entry, CheckListView<CitationRelationItem> listView, Button abortButton,
Button refreshButton, CitationFetcher.SearchType searchType, Button importButton,
ProgressIndicator progress) {
if (entry.getDOI().isEmpty()) {
hideNodes(abortButton, progress);
showNodes(refreshButton);
listView.getItems().clear();
listView.setPlaceholder(
new Label(Localization.lang("The selected entry doesn't have a DOI linked to it. Lookup a DOI and try again.")));
private void searchForRelations(CitationComponents citationComponents,
CitationComponents otherCitationComponents) {
if (citationComponents.entry().getDOI().isEmpty()) {
setUpEmptyPanel(citationComponents, otherCitationComponents);
return;
}
executeSearch(citationComponents);
}

ObservableList<CitationRelationItem> observableList = FXCollections.observableArrayList();
private void setUpEmptyPanel(CitationComponents citationComponents,
CitationComponents otherCitationComponents) {
hideNodes(citationComponents.abortButton(), citationComponents.progress());
showNodes(citationComponents.refreshButton());

HBox hBox = new HBox();
Label label = new Label(Localization.lang("The selected entry doesn't have a DOI linked to it."));
Hyperlink link = new Hyperlink(Localization.lang("Look up a DOI and try again."));

link.setOnAction(e -> {
CrossRef doiFetcher = new CrossRef();

BackgroundTask.wrap(() -> doiFetcher.findIdentifier(citationComponents.entry()))
.onRunning(() -> {
showNodes(citationComponents.progress(), otherCitationComponents.progress());
setLabelOn(citationComponents.listView(), Localization.lang("Looking up DOI..."));
setLabelOn(otherCitationComponents.listView(), Localization.lang("Looking up DOI..."));
})
.onSuccess(identifier -> {
if (identifier.isPresent()) {
citationComponents.entry().setField(StandardField.DOI, identifier.get().asString());
executeSearch(citationComponents);
executeSearch(otherCitationComponents);
} else {
dialogService.notify(Localization.lang("No DOI found"));
setUpEmptyPanel(citationComponents, otherCitationComponents);
setUpEmptyPanel(otherCitationComponents, citationComponents);
}
}).onFailure(ex -> {
hideNodes(citationComponents.progress(), otherCitationComponents.progress());
setLabelOn(citationComponents.listView(), "Error " + ex.getMessage());
setLabelOn(otherCitationComponents.listView(), "Error " + ex.getMessage());
}).executeWith(taskExecutor);
});

hBox.getChildren().add(label);
hBox.getChildren().add(link);
hBox.setSpacing(2d);
hBox.setStyle("-fx-alignment: center;");
hBox.setFillHeight(true);

citationComponents.listView().getItems().clear();
citationComponents.listView().setPlaceholder(hBox);
}

private static void setLabelOn(CheckListView<CitationRelationItem> listView, String message) {
Label lookingUpDoiLabel = new Label(message);
listView.getItems().clear();
listView.setPlaceholder(lookingUpDoiLabel);
}

listView.setItems(observableList);
private void executeSearch(CitationComponents citationComponents) {
ObservableList<CitationRelationItem> observableList = FXCollections.observableArrayList();
citationComponents.listView().setItems(observableList);

// TODO: It should not be possible to cancel a search task that is already running for same tab
if (citingTask != null && !citingTask.isCancelled() && searchType == CitationFetcher.SearchType.CITES) {
if (citingTask != null && !citingTask.isCancelled() && citationComponents.searchType() == CitationFetcher.SearchType.CITES) {
citingTask.cancel();
} else if (citedByTask != null && !citedByTask.isCancelled() && searchType == CitationFetcher.SearchType.CITED_BY) {
} else if (citedByTask != null && !citedByTask.isCancelled() && citationComponents.searchType() == CitationFetcher.SearchType.CITED_BY) {
citedByTask.cancel();
}

this.createBackgroundTask(entry, searchType)
.consumeOnRunning(task -> prepareToSearchForRelations(
abortButton, refreshButton, importButton, progress, task
))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(
entry,
listView,
abortButton,
refreshButton,
searchType,
importButton,
progress,
fetchedList,
observableList
this.createBackgroundTask(citationComponents.entry(), citationComponents.searchType())
.consumeOnRunning(task -> prepareToSearchForRelations(citationComponents, task))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(citationComponents,
fetchedList,
observableList
))
.onFailure(exception -> {
LOGGER.error("Error while fetching citing Articles", exception);
hideNodes(abortButton, progress, importButton);
listView.setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0",
hideNodes(citationComponents.abortButton(), citationComponents.progress(), citationComponents.importButton());
citationComponents.listView().setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0",
exception.getMessage())));
refreshButton.setVisible(true);
citationComponents.refreshButton().setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(taskExecutor);
Expand Down Expand Up @@ -503,15 +521,13 @@ private BackgroundTask<List<BibEntry>> createBackgroundTask(
};
}

private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationRelationItem> listView,
Button abortButton, Button refreshButton,
CitationFetcher.SearchType searchType, Button importButton,
ProgressIndicator progress, List<BibEntry> fetchedList,
private void onSearchForRelationsSucceed(CitationComponents citationComponents,
List<BibEntry> fetchedList,
ObservableList<CitationRelationItem> observableList) {
hideNodes(abortButton, progress);

BibDatabase database = stateManager.getActiveDatabase().map(BibDatabaseContext::getDatabase)
.orElse(new BibDatabase());
hideNodes(citationComponents.abortButton(), citationComponents.progress());

BibDatabase database = stateManager.getActiveDatabase().map(BibDatabaseContext::getDatabase).orElse(new BibDatabase());
observableList.setAll(
fetchedList.stream().map(entr ->
duplicateCheck.containsDuplicate(
Expand All @@ -524,27 +540,26 @@ private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationR
);

if (!observableList.isEmpty()) {
listView.refresh();
citationComponents.listView().refresh();
} else {
Label placeholder = new Label(Localization.lang("No articles found"));
listView.setPlaceholder(placeholder);
citationComponents.listView().setPlaceholder(placeholder);
}
BooleanBinding booleanBind = Bindings.isEmpty(listView.getCheckModel().getCheckedItems());
importButton.disableProperty().bind(booleanBind);
importButton.setOnMouseClicked(event -> importEntries(listView.getCheckModel().getCheckedItems(), searchType, entry));
showNodes(refreshButton, importButton);
BooleanBinding booleanBind = Bindings.isEmpty(citationComponents.listView().getCheckModel().getCheckedItems());
citationComponents.importButton().disableProperty().bind(booleanBind);
citationComponents.importButton().setOnMouseClicked(event -> importEntries(citationComponents.listView().getCheckModel().getCheckedItems(), citationComponents.searchType(), citationComponents.entry()));
showNodes(citationComponents.refreshButton(), citationComponents.importButton());
}

private void prepareToSearchForRelations(Button abortButton, Button refreshButton, Button importButton,
ProgressIndicator progress, BackgroundTask<List<BibEntry>> task) {
showNodes(abortButton, progress);
hideNodes(refreshButton, importButton);
private void prepareToSearchForRelations(CitationComponents citationComponents, BackgroundTask<List<BibEntry>> task) {
showNodes(citationComponents.abortButton(), citationComponents.progress());
hideNodes(citationComponents.refreshButton(), citationComponents.importButton());

abortButton.setOnAction(event -> {
hideNodes(abortButton, progress, importButton);
showNodes(refreshButton);
citationComponents.abortButton().setOnAction(event -> {
hideNodes(citationComponents.abortButton(), citationComponents.progress(), citationComponents.importButton());
showNodes(citationComponents.refreshButton());
task.cancel();
dialogService.notify(Localization.lang("Search aborted!"));
dialogService.notify(Localization.lang("Search aborted."));
});
}

Expand Down
7 changes: 5 additions & 2 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2801,14 +2801,17 @@ Miscellaneous=Miscellaneous
File-related=File-related

Add\ selected\ entry(s)\ to\ library=Add selected entry(s) to library
The\ selected\ entry\ doesn't\ have\ a\ DOI\ linked\ to\ it.\ Lookup\ a\ DOI\ and\ try\ again.=The selected entry doesn't have a DOI linked to it. Lookup a DOI and try again.
The\ selected\ entry\ doesn't\ have\ a\ DOI\ linked\ to\ it.=The selected entry doesn't have a DOI linked to it.
Look\ up\ a\ DOI\ and\ try\ again.=Look up a DOI and try again.
Looking\ up\ DOI...=Looking up DOI...
No\ DOI\ found=No DOI found
Cited\ By=Cited By
Cites=Cites
No\ articles\ found=No articles found
Restart\ search=Restart search
Cancel\ search=Cancel search
Select\ entry=Select entry
Search\ aborted!=Search aborted!
Search\ aborted.=Search aborted.
Citation\ relations=Citation relations
Show\ articles\ related\ by\ citation=Show articles related by citation
Error\ while\ fetching\ citing\ entries\:\ %0=Error while fetching citing entries: %0
Expand Down
2 changes: 1 addition & 1 deletion jablib/src/main/resources/l10n/JabRef_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,7 @@ Convert\ to\ BibTeX\ format\ (e.g.,\ store\ publication\ date\ in\ year\ and\ mo
Deprecated\ fields=废弃的字段

Shows\ fields\ having\ a\ successor\ in\ biblatex.=Shows fields having a successor in biblatex.
Shows\ fields\ having\ a\ successor\ in\ biblatex.\nFor\ instance,\ the\ publication\ month\ should\ be\ part\ of\ the\ date\ field.\nUse\ the\ Clean\ up\ Entries\ functionality\ to\ convert\ the\ entry\ to\ biblatex.=显示在biblatex中具有后继关系的field。\n例如,发布月份(publication month)应当是日期(date)的一部分。\n使用"清理记录"功能来转换条目为biblatex。
Shows\ fields\ having\ a\ successor\ in\ biblatex.\nFor\ instance,\ the\ publication\ month\ should\ be\ part\ of\ the\ date\ field.\nUse\ the\ Clean\ up\ Entries\ functionality\ to\ convert\ the\ entry\ to\ biblatex.=显示在biblatex中具有后继关系的field。\n例如,发布月份(publication month)应当是日期(date)的一部分。\n使用"清理记录"功能来转换条目为biblatex。


No\ read\ status\ information=无阅读情况信息
Expand Down