Skip to content
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

Add cites field to bib entries for citation relation #10752

Merged
merged 9 commits into from
Jan 8, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- When importing entries form the "Citation relations" tab, the field [cites](https://docs.jabref.org/advanced/entryeditor/entrylinks) is now filled according to the relationship between the entries. [#10572](https://github.com/JabRef/jabref/pull/10752)

### Changed

- The Custom export format now uses the custom DOI base URI in the preferences for the `DOICheck`, if activated [forum#4084](https://discourse.jabref.org/t/export-html-disregards-custom-doi-base-uri/4084)
- We changed the order of the lists in the "Citation relations" tab. `Cites` are now on the left and `Cited by` on the right [#10572](https://github.com/JabRef/jabref/pull/10752)

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/entryeditor/EntryEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ private List<EntryEditorTab> createTabs() {
entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache()));
entryEditorTabs.add(new RelatedArticlesTab(entryEditorPreferences, preferencesService, dialogService, taskExecutor));
entryEditorTabs.add(new CitationRelationsTab(entryEditorPreferences, dialogService, databaseContext,
undoManager, stateManager, fileMonitor, preferencesService, libraryTab));
undoManager, stateManager, fileMonitor, preferencesService, libraryTab, taskExecutor));

sourceTab = new SourceTab(
databaseContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,7 @@
/**
* Class to hold a BibEntry and a boolean value whether it is already in the current database or not.
*/
public class CitationRelationItem {
private final BibEntry entry;
private final boolean isLocal;

public CitationRelationItem(BibEntry entry, boolean isLocal) {
this.entry = entry;
this.isLocal = isLocal;
}

public BibEntry getEntry() {
return entry;
}

public boolean isLocal() {
return isLocal;
}
public record CitationRelationItem(
BibEntry entry,
boolean isLocal) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
import org.jabref.gui.entryeditor.EntryEditorTab;
import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher;
import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.SemanticScholarFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.NoSelectionModel;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
Expand Down Expand Up @@ -66,12 +66,14 @@ public class CitationRelationsTab extends EntryEditorTab {
private final FileUpdateMonitor fileUpdateMonitor;
private final PreferencesService preferencesService;
private final LibraryTab libraryTab;
private final TaskExecutor taskExecutor;
private final BibEntryRelationsRepository bibEntryRelationsRepository;
private final CitationsRelationsTabViewModel citationsRelationsTabViewModel;

public CitationRelationsTab(EntryEditorPreferences preferences, DialogService dialogService,
BibDatabaseContext databaseContext, UndoManager undoManager,
StateManager stateManager, FileUpdateMonitor fileUpdateMonitor,
PreferencesService preferencesService, LibraryTab lTab) {
PreferencesService preferencesService, LibraryTab lTab, TaskExecutor taskExecutor) {
this.preferences = preferences;
this.dialogService = dialogService;
this.databaseContext = databaseContext;
Expand All @@ -80,11 +82,13 @@ public CitationRelationsTab(EntryEditorPreferences preferences, DialogService di
this.fileUpdateMonitor = fileUpdateMonitor;
this.preferencesService = preferencesService;
this.libraryTab = lTab;
this.taskExecutor = taskExecutor;
setText(Localization.lang("Citation relations"));
setTooltip(new Tooltip(Localization.lang("Show articles related by citation")));

this.bibEntryRelationsRepository = new BibEntryRelationsRepository(new SemanticScholarFetcher(preferencesService.getImporterPreferences()),
new BibEntryRelationsCache());
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(databaseContext, preferencesService, undoManager, stateManager, dialogService, fileUpdateMonitor, Globals.TASK_EXECUTOR);
}

/**
Expand All @@ -107,7 +111,7 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) {
citedByHBox.setPrefHeight(40);

// Create Heading Lab
Label citingLabel = new Label(Localization.lang("Citing"));
Label citingLabel = new Label(Localization.lang("Cites"));
styleLabel(citingLabel);
Label citedByLabel = new Label(Localization.lang("Cited By"));
styleLabel(citedByLabel);
Expand Down Expand Up @@ -160,20 +164,19 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) {

refreshCitingButton.setOnMouseClicked(event -> {
searchForRelations(entry, citingListView, abortCitingButton,
refreshCitingButton, CitationFetcher.SearchType.CITING, importCitingButton, citingProgress, true);
refreshCitingButton, CitationFetcher.SearchType.CITES, importCitingButton, citingProgress, true);
});

refreshCitedByButton.setOnMouseClicked(event -> searchForRelations(entry, citedByListView, abortCitedButton,
refreshCitedByButton, CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress, true));

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

styleFetchedListView(citingListView);
SplitPane container = new SplitPane(citingVBox, citedByVBox);
styleFetchedListView(citedByListView);
styleFetchedListView(citingListView);

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

searchForRelations(entry, citedByListView, abortCitedButton, refreshCitedByButton,
CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress, false);
Expand All @@ -193,7 +196,7 @@ private void styleFetchedListView(CheckListView<CitationRelationItem> listView)

HBox separator = new HBox();
HBox.setHgrow(separator, Priority.SOMETIMES);
Node entryNode = BibEntryView.getEntryNode(entry.getEntry());
Node entryNode = BibEntryView.getEntryNode(entry.entry());
HBox.setHgrow(entryNode, Priority.ALWAYS);
HBox hContainer = new HBox();
hContainer.prefWidthProperty().bind(listView.widthProperty().subtract(25));
Expand All @@ -203,8 +206,8 @@ private void styleFetchedListView(CheckListView<CitationRelationItem> listView)
jumpTo.setTooltip(new Tooltip(Localization.lang("Jump to entry in database")));
jumpTo.getStyleClass().add("addEntryButton");
jumpTo.setOnMouseClicked(event -> {
libraryTab.showAndEdit(entry.getEntry());
libraryTab.clearAndSelect(entry.getEntry());
libraryTab.showAndEdit(entry.entry());
libraryTab.clearAndSelect(entry.entry());
citingTask.cancel();
citedByTask.cancel();
});
Expand Down Expand Up @@ -285,7 +288,7 @@ protected void bindToEntry(BibEntry entry) {
*
* @param entry BibEntry currently selected in Jabref Database
* @param listView ListView to use
* @param abortButton Button to stop the search
* @param abortButton Button to stop the search
* @param refreshButton refresh Button to use
* @param searchType type of search (CITING / CITEDBY)
*/
Expand All @@ -305,15 +308,15 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt

listView.setItems(observableList);

if (citingTask != null && !citingTask.isCanceled() && searchType == CitationFetcher.SearchType.CITING) {
if (citingTask != null && !citingTask.isCanceled() && searchType == CitationFetcher.SearchType.CITES) {
citingTask.cancel();
} else if (citedByTask != null && !citedByTask.isCanceled() && searchType == CitationFetcher.SearchType.CITED_BY) {
citedByTask.cancel();
}

BackgroundTask<List<BibEntry>> task;

if (searchType == CitationFetcher.SearchType.CITING) {
if (searchType == CitationFetcher.SearchType.CITES) {
task = BackgroundTask.wrap(() -> {
if (shouldRefresh) {
bibEntryRelationsRepository.forceRefreshReferences(entry);
Expand All @@ -332,18 +335,18 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt
}

task.onRunning(() -> prepareToSearchForRelations(abortButton, refreshButton, importButton, progress, task))
.onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton,
searchType, importButton, progress, 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",
exception.getMessage())));

refreshButton.setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(Globals.TASK_EXECUTOR);
.onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton,
searchType, importButton, progress, 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",
exception.getMessage())));

refreshButton.setVisible(true);
dialogService.notify(exception.getMessage());
})
.executeWith(taskExecutor);
}

private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationRelationItem> listView,
Expand All @@ -354,7 +357,7 @@ private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationR
hideNodes(abortButton, progress);

observableList.setAll(fetchedList.stream().map(entr -> new CitationRelationItem(entr, false))
.collect(Collectors.toList()));
.collect(Collectors.toList()));

if (!observableList.isEmpty()) {
listView.refresh();
Expand Down Expand Up @@ -396,19 +399,11 @@ private void showNodes(Node... nodes) {
*
* @param entriesToImport entries to import
*/
private void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry entry) {
private void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry existingEntry) {
Siedlerchr marked this conversation as resolved.
Show resolved Hide resolved
citingTask.cancel();
citedByTask.cancel();
List<BibEntry> entries = entriesToImport.stream().map(CitationRelationItem::getEntry).collect(Collectors.toList());
ImportHandler importHandler = new ImportHandler(
databaseContext,
preferencesService,
fileUpdateMonitor,
undoManager,
stateManager,
dialogService,
Globals.TASK_EXECUTOR);
importHandler.importEntries(entries);

citationsRelationsTabViewModel.importEntries(entriesToImport, searchType, existingEntry);

dialogService.notify(Localization.lang("Number of entries successfully imported") + ": " + entriesToImport.size());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.jabref.gui.entryeditor.citationrelationtab;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.swing.undo.UndoManager;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.citationkeypattern.CitationKeyGenerator;
import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.util.FileUpdateMonitor;
import org.jabref.preferences.PreferencesService;

public class CitationsRelationsTabViewModel {

private final BibDatabaseContext databaseContext;
private final PreferencesService preferencesService;
private final UndoManager undoManager;
private final StateManager stateManager;
private final DialogService dialogService;
private final FileUpdateMonitor fileUpdateMonitor;
private final TaskExecutor taskExecutor;

public CitationsRelationsTabViewModel(BibDatabaseContext databaseContext, PreferencesService preferencesService, UndoManager undoManager, StateManager stateManager, DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor) {
this.databaseContext = databaseContext;
this.preferencesService = preferencesService;
this.undoManager = undoManager;
this.stateManager = stateManager;
this.dialogService = dialogService;
this.fileUpdateMonitor = fileUpdateMonitor;
this.taskExecutor = taskExecutor;
}

public void importEntries(List<CitationRelationItem> entriesToImport, CitationFetcher.SearchType searchType, BibEntry existingEntry) {
Siedlerchr marked this conversation as resolved.
Show resolved Hide resolved
List<BibEntry> entries = entriesToImport.stream().map(CitationRelationItem::entry).toList();

ImportHandler importHandler = new ImportHandler(
databaseContext,
preferencesService,
fileUpdateMonitor,
undoManager,
stateManager,
dialogService,
taskExecutor);

switch (searchType) {
case CITES -> importCites(entries, existingEntry, importHandler);
case CITED_BY -> importCitedBy(entries, existingEntry, importHandler);
}
}

private void importCites(List<BibEntry> entries, BibEntry existingEntry, ImportHandler importHandler) {
CitationKeyPatternPreferences citationKeyPatternPreferences = preferencesService.getCitationKeyPatternPreferences();
CitationKeyGenerator generator = new CitationKeyGenerator(databaseContext, citationKeyPatternPreferences);
boolean generateNewKeyOnImport = preferencesService.getImporterPreferences().generateNewKeyOnImportProperty().get();

List<String> citeKeys = getExistingEntriesFromCiteField(existingEntry);
citeKeys.removeIf(String::isEmpty);
for (BibEntry entryToCite : entries) {
if (generateNewKeyOnImport || entryToCite.getCitationKey().isEmpty()) {
String key = generator.generateKey(entryToCite);
entryToCite.setCitationKey(key);
addToKeyToList(citeKeys, key);
} else {
addToKeyToList(citeKeys, entryToCite.getCitationKey().get());
}
}
existingEntry.setField(StandardField.CITES, toCommaSeparatedString(citeKeys));
importHandler.importEntries(entries);
}

private void importCitedBy(List<BibEntry> entries, BibEntry existingEntry, ImportHandler importHandler) {
CitationKeyPatternPreferences citationKeyPatternPreferences = preferencesService.getCitationKeyPatternPreferences();
CitationKeyGenerator generator = new CitationKeyGenerator(databaseContext, citationKeyPatternPreferences);
boolean generateNewKeyOnImport = preferencesService.getImporterPreferences().generateNewKeyOnImportProperty().get();

for (BibEntry entryThatCitesOurExistingEntry : entries) {
List<String> existingCites = getExistingEntriesFromCiteField(entryThatCitesOurExistingEntry);
existingCites.removeIf(String::isEmpty);
String key;
if (generateNewKeyOnImport || entryThatCitesOurExistingEntry.getCitationKey().isEmpty()) {
key = generator.generateKey(entryThatCitesOurExistingEntry);
entryThatCitesOurExistingEntry.setCitationKey(key);
} else {
key = existingEntry.getCitationKey().get();
}
addToKeyToList(existingCites, key);
entryThatCitesOurExistingEntry.setField(StandardField.CITES, toCommaSeparatedString(existingCites));
}

importHandler.importEntries(entries);
}

private void addToKeyToList(List<String> list, String key) {
if (!list.contains(key)) {
list.add(key);
}
}

private List<String> getExistingEntriesFromCiteField(BibEntry entry) {
return Arrays.stream(entry.getField(StandardField.CITES).orElse("").split(",")).collect(Collectors.toList());
}

private String toCommaSeparatedString(List<String> citeentries) {
return String.join(",", citeentries);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jabref.gui.entryeditor.citationrelationtab.semanticscholar;

/**
* Used for GSON
*/
public class AuthorResponse {
private String authorId;
private String name;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jabref.gui.entryeditor.citationrelationtab.semanticscholar;

/**
* Used for GSON
*/
public class CitationDataItem {
private PaperDetails citingPaper;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface CitationFetcher {
* Possible search methods
*/
enum SearchType {
CITING("reference"),
CITES("reference"),
CITED_BY("citation");

public final String label;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.List;

/**
* Used for GSON
*/
public class CitationsResponse {
private int offset;
private int next;
Expand Down
Loading