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

Generate an entry from ID #8129

Merged
merged 51 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
afca0ef
add prototype
Oct 7, 2021
a9f6aec
Add CompositeIdFetcherTest
Oct 8, 2021
899784e
Add CHANGELOG.md update
Oct 8, 2021
549626b
clean checkstyle warnings
Oct 8, 2021
73e0f37
update prototype
Oct 8, 2021
0863df7
clean up checkstyle in test
Oct 8, 2021
04e65ea
add binding for newEntryFromIdButton
Oct 8, 2021
195b3be
optimize
Oct 8, 2021
993edbc
delete unnecessary lines
Oct 8, 2021
6478b11
Update CHANGELOG.md
colinhex Oct 9, 2021
3a0b625
adjust button style class and focus
Oct 9, 2021
5fc8114
Merge branch 'generateEntryFromId' of github.com:colinhex/jabref-1 in…
Oct 9, 2021
8c48be0
exclude CompositeIdFetcher from WebFetchersTest
Oct 9, 2021
79da4e9
add key in en.properties
Oct 9, 2021
1ca0272
Merge branch 'main' of github.com:JabRef/jabref into generateEntryFromId
Oct 9, 2021
871e413
optimize CompositeIdFetcher.java
Oct 10, 2021
92c1bb7
Revert "optimize CompositeIdFetcher.java"
Oct 11, 2021
fd6cf56
UI tweaks
Oct 12, 2021
6aa788a
optimize imports
Oct 12, 2021
0e1570a
change log level in CompositeFetcher
Oct 12, 2021
2534f1f
optimize code
Oct 12, 2021
d10cc26
fix checkstyle warnings
Oct 12, 2021
5cf64ba
Merge branch 'main' of github.com:JabRef/jabref into generateEntryFromId
Oct 12, 2021
cea0eab
pass strings to localization.lang
Oct 17, 2021
d89a84b
optimize code
Oct 17, 2021
b00a749
add IacrEprint identifier
Oct 17, 2021
b58fbdb
add IacrEprint identifier and tests
Oct 17, 2021
97344b0
Merge branch 'main' of github.com:JabRef/jabref into generateEntryFromId
Oct 17, 2021
1220f2f
adjust CompositeIdFetcher
Oct 17, 2021
450eb3f
optimize CompositeIdFetcher
Oct 17, 2021
c669ceb
delete unnecessary lines
Oct 17, 2021
c168695
separately handle fetcherexceptions in CompositeIdFetcher
Oct 17, 2021
3d6db7b
Revert "separately handle fetcherexceptions in CompositeIdFetcher"
Oct 17, 2021
832c185
optimize imports
Oct 17, 2021
311aa63
delete empty line
Oct 17, 2021
2b3070f
fix label not appearing
Oct 17, 2021
836e347
clear checkstyle errors
Oct 17, 2021
c563ff0
convert to parametrized test
Oct 25, 2021
0a2a828
optimize parametrized tests
Oct 25, 2021
f8a0ec3
add logger to CompositeIdFetcher
Oct 25, 2021
bcbb143
minor tweaks
Oct 25, 2021
5a90ec7
change status messages
Oct 25, 2021
2cd6332
add tooltip
Oct 25, 2021
950ad1f
close PopOver in success case
Oct 25, 2021
cbb702c
handle doi first
Oct 26, 2021
87ddfac
change status messages, include exceptions
Oct 26, 2021
8259ffc
delete unused logger
Oct 26, 2021
4aa20bc
catch fetcher exception in test
Oct 26, 2021
0be09f0
Revert "catch fetcher exception in test"
Oct 26, 2021
14d5885
add throws to test methods
Oct 26, 2021
3e705f6
remove obsolete localization key
Oct 26, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We readded the possibility to keep the search string when switching tabs. It is implemented by a toggle button. [#4096](https://github.com/JabRef/jabref/issues/4096#issuecomment-575986882)
- We allowed the user to also preview the available citation styles in the preferences besides the selected ones [#8108](https://github.com/JabRef/jabref/issues/8108)
- We added an option to search the available citation styles by name in the preferences [#8108](https://github.com/JabRef/jabref/issues/8108)
- We added an option to generate bib-entries from ID through a popover in the toolbar. [#4183](https://github.com/JabRef/jabref/issues/4183)

### Changed

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/EntryTypeViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void runFetcherWorker() {
ImportCleanup cleanup = new ImportCleanup(libraryTab.getBibDatabaseContext().getMode());
cleanup.doPostCleanup(entry);
Optional<BibEntry> duplicate = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(libraryTab.getDatabase(), entry, libraryTab.getBibDatabaseContext().getMode());
if ((duplicate.isPresent())) {
if (duplicate.isPresent()) {
DuplicateResolverDialog dialog = new DuplicateResolverDialog(entry, duplicate.get(), DuplicateResolverDialog.DuplicateResolverType.IMPORT_CHECK, libraryTab.getBibDatabaseContext(), stateManager);
switch (dialogService.showCustomDialogAndWait(dialog).orElse(DuplicateResolverDialog.DuplicateResolverResult.BREAK)) {
case KEEP_LEFT:
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/jabref/gui/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@
import org.jabref.gui.help.ErrorConsoleAction;
import org.jabref.gui.help.HelpAction;
import org.jabref.gui.help.SearchForUpdateAction;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.importer.GenerateEntryFromIdDialog;
import org.jabref.gui.importer.ImportCommand;
import org.jabref.gui.importer.ImportEntriesDialog;
import org.jabref.gui.importer.NewDatabaseAction;
Expand Down Expand Up @@ -174,6 +176,7 @@ public class JabRefFrame extends BorderPane {
private SidePane sidePane;
private TabPane tabbedPane;
private PopOver progressViewPopOver;
private PopOver entryFromIdPopOver;

private final TaskExecutor taskExecutor;

Expand Down Expand Up @@ -477,6 +480,8 @@ private Node createToolbar() {
final Button pushToApplicationButton = factory.createIconButton(pushToApplicationAction.getActionInformation(), pushToApplicationAction);
pushToApplicationsManager.registerReconfigurable(pushToApplicationButton);

// Setup Toolbar

ToolBar toolBar = new ToolBar(

new HBox(
Expand All @@ -493,6 +498,7 @@ private Node createToolbar() {
new HBox(
factory.createIconButton(StandardActions.NEW_ARTICLE, new NewEntryAction(this, StandardEntryType.Article, dialogService, prefs, stateManager)),
factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(this, dialogService, prefs, stateManager)),
createNewEntryFromIdButton(),
factory.createIconButton(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT, new ExtractBibtexAction(dialogService, prefs, stateManager)),
factory.createIconButton(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, this, stateManager))
),
Expand Down Expand Up @@ -907,6 +913,36 @@ private MenuBar createMenu() {
return menu;
}

private Button createNewEntryFromIdButton() {
Button newEntryFromIdButton = new Button();

newEntryFromIdButton.setGraphic(IconTheme.JabRefIcons.IMPORT.getGraphicNode());
newEntryFromIdButton.getStyleClass().setAll("icon-button");
newEntryFromIdButton.setFocusTraversable(false);
newEntryFromIdButton.disableProperty().bind(ActionHelper.needsDatabase(stateManager).not());
newEntryFromIdButton.setOnMouseClicked(event -> {
GenerateEntryFromIdDialog entryFromId = new GenerateEntryFromIdDialog(getCurrentLibraryTab(), dialogService, prefs, taskExecutor);

if (entryFromIdPopOver == null) {
entryFromIdPopOver = new PopOver(entryFromId.getDialogPane());
entryFromIdPopOver.setTitle(Localization.lang("Import by ID"));
entryFromIdPopOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER);
entryFromIdPopOver.setContentNode(entryFromId.getDialogPane());
entryFromIdPopOver.show(newEntryFromIdButton);
entryFromId.setEntryFromIdPopOver(entryFromIdPopOver);
} else if (entryFromIdPopOver.isShowing()) {
entryFromIdPopOver.hide();
} else {
entryFromIdPopOver.setContentNode(entryFromId.getDialogPane());
entryFromIdPopOver.show(newEntryFromIdButton);
entryFromId.setEntryFromIdPopOver(entryFromIdPopOver);
}
});
newEntryFromIdButton.setTooltip(new Tooltip(Localization.lang("Import by ID")));

return newEntryFromIdButton;
}

private Group createTaskIndicator() {
ProgressIndicator indicator = new ProgressIndicator();
indicator.getStyleClass().add("progress-indicatorToolbar");
Expand Down
1 change: 0 additions & 1 deletion src/main/java/org/jabref/gui/actions/StandardActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ public enum StandardActions implements Action {
LIBRARY_PROPERTIES(Localization.lang("Library properties")),
EDIT_PREAMBLE(Localization.lang("Edit preamble")),
EDIT_STRINGS(Localization.lang("Edit string constants"), IconTheme.JabRefIcons.EDIT_STRINGS, KeyBinding.EDIT_STRINGS),

FIND_DUPLICATES(Localization.lang("Find duplicates"), IconTheme.JabRefIcons.FIND_DUPLICATES),
MERGE_ENTRIES(Localization.lang("Merge entries"), IconTheme.JabRefIcons.MERGE_ENTRIES, KeyBinding.MERGE_ENTRIES),
RESOLVE_DUPLICATE_KEYS(Localization.lang("Resolve duplicate citation keys"), Localization.lang("Find and remove duplicate citation keys"), KeyBinding.RESOLVE_DUPLICATE_CITATION_KEYS),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.jabref.gui.importer;

import java.util.Optional;

import org.jabref.gui.DialogService;
import org.jabref.gui.Globals;
import org.jabref.gui.LibraryTab;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.JabRefException;
import org.jabref.logic.database.DuplicateCheck;
import org.jabref.logic.importer.CompositeIdFetcher;
import org.jabref.logic.importer.FetcherException;
import org.jabref.logic.importer.ImportCleanup;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntry;
import org.jabref.preferences.PreferencesService;

import org.controlsfx.control.PopOver;

public class GenerateEntryFromIdAction extends SimpleCommand {

private final LibraryTab libraryTab;
private final DialogService dialogService;
private final PreferencesService preferencesService;
private final String identifier;
private final TaskExecutor taskExecutor;
private final PopOver entryFromIdPopOver;

public GenerateEntryFromIdAction(LibraryTab libraryTab, DialogService dialogService, PreferencesService preferencesService, TaskExecutor taskExecutor, PopOver entryFromIdPopOver, String identifier) {
this.libraryTab = libraryTab;
this.dialogService = dialogService;
this.preferencesService = preferencesService;
this.identifier = identifier;
this.taskExecutor = taskExecutor;
this.entryFromIdPopOver = entryFromIdPopOver;
}

@Override
public void execute() {
BackgroundTask<Optional<BibEntry>> backgroundTask = searchAndImportEntryInBackground();
backgroundTask.titleProperty().set(Localization.lang("Import by ID"));
backgroundTask.showToUser(true);
backgroundTask.onRunning(() -> dialogService.notify("%s".formatted(backgroundTask.messageProperty().get())));
backgroundTask.onFailure((e) -> dialogService.notify(e.getMessage()));
backgroundTask.onSuccess((bibEntry) -> bibEntry.ifPresentOrElse((entry) -> {
libraryTab.insertEntry(entry);
entryFromIdPopOver.hide();
dialogService.notify(Localization.lang("Imported one entry"));
},
() -> dialogService.notify(Localization.lang("Import canceled"))
));
backgroundTask.executeWith(taskExecutor);
}

private BackgroundTask<Optional<BibEntry>> searchAndImportEntryInBackground() {
return new BackgroundTask<>() {
@Override
protected Optional<BibEntry> call() throws JabRefException {
if (isCanceled()) {
return Optional.empty();
}

updateMessage(Localization.lang("Searching..."));
try {
Optional<BibEntry> result = new CompositeIdFetcher(preferencesService.getImportFormatPreferences()).performSearchById(identifier);
if (result.isPresent()) {
final BibEntry entry = result.get();
ImportCleanup cleanup = new ImportCleanup(libraryTab.getBibDatabaseContext().getMode());
cleanup.doPostCleanup(entry);
// DuplicateCheck only covers DOI and ISBN at the moment.
Optional<BibEntry> duplicate = new DuplicateCheck(Globals.entryTypesManager).containsDuplicate(libraryTab.getDatabase(), entry, libraryTab.getBibDatabaseContext().getMode());
if (duplicate.isPresent()) {
throw new JabRefException(Localization.lang("Entry already exists"));
}
} else {
throw new JabRefException(Localization.lang("Could not find any bibliographic information."));
}
updateMessage(Localization.lang("Imported one entry"));
return result;
} catch (FetcherException fetcherException) {
throw new JabRefException("Fetcher error: %s".formatted(fetcherException.getMessage()));
}
}
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.DialogPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.Pane?>

<DialogPane fx:id="dialogPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="92.0" prefWidth="392.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jabref.gui.importer.GenerateEntryFromIdDialog">
<content>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="102.0" prefWidth="360.0">
<TextField fx:id="idTextField" layoutX="14.0" layoutY="40.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="29.0" prefWidth="287.0" />
<Button fx:id="generateButton" layoutX="314.0" layoutY="40.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#generateEntry" prefHeight="30.0" prefWidth="55.0" textAlignment="CENTER" />
<Label layoutX="14.0" layoutY="14.0" prefHeight="16.0" prefWidth="328.0" text="Import new entry from ID"/>
</Pane>
</content>
</DialogPane>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.jabref.gui.importer;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.DialogPane;
import javafx.scene.control.TextField;

import org.jabref.gui.DialogService;
import org.jabref.gui.LibraryTab;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.util.TaskExecutor;
import org.jabref.logic.l10n.Localization;
import org.jabref.preferences.PreferencesService;

import com.airhacks.afterburner.views.ViewLoader;
import org.controlsfx.control.PopOver;

public class GenerateEntryFromIdDialog {

@FXML DialogPane dialogPane;
@FXML TextField idTextField;
@FXML Button generateButton;

private final PreferencesService preferencesService;
private final DialogService dialogService;
private final LibraryTab libraryTab;
private final TaskExecutor taskExecutor;

private PopOver entryFromIdPopOver;

public GenerateEntryFromIdDialog(LibraryTab libraryTab, DialogService dialogService, PreferencesService preferencesService, TaskExecutor taskExecutor) {
ViewLoader.view(this).load();
this.preferencesService = preferencesService;
this.dialogService = dialogService;
this.libraryTab = libraryTab;
this.taskExecutor = taskExecutor;

this.generateButton.setGraphic(IconTheme.JabRefIcons.IMPORT.getGraphicNode());
this.generateButton.setDefaultButton(true);
}

@FXML private void generateEntry() {
if (idTextField.getText().isEmpty()) {
dialogService.notify(Localization.lang("Enter a valid ID"));
return;
}

this.idTextField.requestFocus();

GenerateEntryFromIdAction generateEntryFromIdAction = new GenerateEntryFromIdAction(
libraryTab,
dialogService,
preferencesService,
taskExecutor,
entryFromIdPopOver,
idTextField.getText()
);
generateEntryFromIdAction.execute();
}

public void setEntryFromIdPopOver(PopOver entryFromIdPopOver) {
this.entryFromIdPopOver = entryFromIdPopOver;
}

public DialogPane getDialogPane() {
return dialogPane;
}

}
47 changes: 47 additions & 0 deletions src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jabref.logic.importer;

import java.util.Optional;

import org.jabref.logic.importer.fetcher.ArXiv;
import org.jabref.logic.importer.fetcher.DoiFetcher;
import org.jabref.logic.importer.fetcher.IacrEprintFetcher;
import org.jabref.logic.importer.fetcher.IsbnFetcher;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.identifier.ArXivIdentifier;
import org.jabref.model.entry.identifier.DOI;
import org.jabref.model.entry.identifier.ISBN;
import org.jabref.model.entry.identifier.IacrEprint;

public class CompositeIdFetcher {

private final ImportFormatPreferences importFormatPreferences;

public CompositeIdFetcher(ImportFormatPreferences importFormatPreferences) {
this.importFormatPreferences = importFormatPreferences;
}

public Optional<BibEntry> performSearchById(String identifier) throws FetcherException {
Optional<DOI> doi = DOI.parse(identifier);
if (doi.isPresent()) {
return new DoiFetcher(importFormatPreferences).performSearchById(doi.get().getNormalized());
}
Optional<ArXivIdentifier> arXivIdentifier = ArXivIdentifier.parse(identifier);
if (arXivIdentifier.isPresent()) {
return new ArXiv(importFormatPreferences).performSearchById(arXivIdentifier.get().getNormalized());
}
Optional<ISBN> isbn = ISBN.parse(identifier);
if (isbn.isPresent()) {
return new IsbnFetcher(importFormatPreferences).performSearchById(isbn.get().getNormalized());
}
Optional<IacrEprint> iacrEprint = IacrEprint.parse(identifier);
if (iacrEprint.isPresent()) {
return new IacrEprintFetcher(importFormatPreferences).performSearchById(iacrEprint.get().getNormalized());
}

return Optional.empty();
}

public String getName() {
return "CompositeIdFetcher";
}
}
77 changes: 77 additions & 0 deletions src/main/java/org/jabref/model/entry/identifier/IacrEprint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.jabref.model.entry.identifier;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.StandardField;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class IacrEprint implements Identifier {
public static final URI RESOLVER = URI.create("https://ia.cr");
private static final Logger LOGGER = LoggerFactory.getLogger(IacrEprint.class);

private static final String IACR_EPRINT_EXP = "\\d{4}\\/\\d{3,5}";
private final String iacrEprint;

IacrEprint(String iacrEprint) {
Objects.requireNonNull(iacrEprint);

String trimmedId = iacrEprint.trim();

if (matchesExcepted(trimmedId)) {
Matcher matcher = Pattern.compile(IACR_EPRINT_EXP).matcher(trimmedId);
matcher.find();
this.iacrEprint = matcher.group(0);
} else {
throw new IllegalArgumentException(trimmedId + " is not a valid IacrEprint identifier.");
}
}

private static boolean matchesExcepted(String identifier) {
return identifier.matches(
"(https\\:\\/\\/)?(ia\\.cr\\/|eprint\\.iacr\\.org\\/)?" + IACR_EPRINT_EXP
);
}

public static Optional<IacrEprint> parse(String identifier) {
String trimmed = identifier.strip();
try {
return Optional.of(new IacrEprint(trimmed));
} catch (IllegalArgumentException illegalArgumentException) {
return Optional.empty();
}
}

@Override
public String getNormalized() {
return iacrEprint;
}

@Override
public Field getDefaultField() {
return StandardField.EPRINT;
}

@Override
public Optional<URI> getExternalURI() {
try {
URI uri = new URI(RESOLVER.getScheme(), RESOLVER.getHost(), "/" + iacrEprint, null);
return Optional.of(uri);
} catch (URISyntaxException e) {
// should never happen
LOGGER.error(iacrEprint + " could not be encoded as URI.", e);
return Optional.empty();
}
}

public String getAsciiUrl() {
return getExternalURI().map(URI::toASCIIString).orElse("");
}
}
Loading