Skip to content

Commit

Permalink
Merge pull request #8129 from colinhex/generateEntryFromId
Browse files Browse the repository at this point in the history
Generate an entry from ID
  • Loading branch information
Siedlerchr committed Oct 26, 2021
2 parents 7c2a9a2 + 3e705f6 commit 2153a05
Show file tree
Hide file tree
Showing 12 changed files with 514 additions and 2 deletions.
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 @@ -151,7 +151,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 @@ -475,6 +478,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 @@ -491,6 +496,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 @@ -905,6 +911,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

0 comments on commit 2153a05

Please sign in to comment.