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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- 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 added context-menu actions to search Google Scholar or Semantic Scholar using the selected entry's title. [#12268](https://github.com/JabRef/jabref/issues/12268)
- We distribute arm64 images for Linux. [#10842](https://github.com/JabRef/jabref/issues/10842)
- When adding an entry to a library, a warning is displayed if said entry already exists in an active library. [#13261](https://github.com/JabRef/jabref/issues/13261)
- We added the field `monthfiled` to the default list of fields to resolve BibTeX-Strings for [#13375](https://github.com/JabRef/jabref/issues/13375)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public enum StandardActions implements Action {
EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR),
OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI),
SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")),
SEARCH_GOOGLE_SCHOLAR(Localization.lang("Search Google Scholar")),
SEARCH_SEMANTIC_SCHOLAR(Localization.lang("Search Semantic Scholar")),
MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/..."), KeyBinding.MERGE_WITH_FETCHED_ENTRY),
BATCH_MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0 (fully automated)", "DOI/ISBN/...")),
ATTACH_FILE(Localization.lang("Attach file"), IconTheme.JabRefIcons.ATTACH_FILE),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public static ContextMenu create(BibEntryTableViewModel entry,

factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferences)),
factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferences)),
factory.createMenuItem(StandardActions.SEARCH_GOOGLE_SCHOLAR, new SearchGoogleScholarAction(dialogService, stateManager, preferences)),
factory.createMenuItem(StandardActions.SEARCH_SEMANTIC_SCHOLAR, new SearchSemanticScholarAction(dialogService, stateManager, preferences)),

new SeparatorMenuItem(),

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jabref.gui.maintable;

import java.io.IOException;
import java.util.List;

import javafx.beans.binding.BooleanExpression;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.ExternalLinkCreator;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;

import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry;
import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;

public class SearchGoogleScholarAction extends SimpleCommand {

private final DialogService dialogService;
private final StateManager stateManager;
private final GuiPreferences preferences;

public SearchGoogleScholarAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) {
this.dialogService = dialogService;
this.stateManager = stateManager;
this.preferences = preferences;

BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet));
}

@Override
public void execute() {
stateManager.getActiveDatabase().ifPresent(databaseContext -> {
final List<BibEntry> bibEntries = stateManager.getSelectedEntries();

if (bibEntries.size() != 1) {
dialogService.notify(Localization.lang("This operation requires exactly one item to be selected."));
return;
}
ExternalLinkCreator.getGoogleScholarSearchURL(bibEntries.getFirst()).ifPresent(url -> {
try {
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
} catch (IOException ex) {
dialogService.showErrorDialogAndWait(Localization.lang("Unable to open Google Scholar."), ex);
}
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jabref.gui.maintable;

import java.io.IOException;
import java.util.List;

import javafx.beans.binding.BooleanExpression;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.ExternalLinkCreator;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;

import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry;
import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected;

public class SearchSemanticScholarAction extends SimpleCommand {

private final DialogService dialogService;
private final StateManager stateManager;
private final GuiPreferences preferences;

public SearchSemanticScholarAction(DialogService dialogService, StateManager stateManager, GuiPreferences preferences) {
this.dialogService = dialogService;
this.stateManager = stateManager;
this.preferences = preferences;

BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager);
this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet));
}

@Override
public void execute() {
stateManager.getActiveDatabase().ifPresent(databaseContext -> {
final List<BibEntry> bibEntries = stateManager.getSelectedEntries();

if (bibEntries.size() != 1) {
dialogService.notify(Localization.lang("This operation requires exactly one item to be selected."));
return;
}
ExternalLinkCreator.getSemanticScholarSearchURL(bibEntries.getFirst()).ifPresent(url -> {
try {
NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst());
} catch (IOException ex) {
dialogService.showErrorDialogAndWait(Localization.lang("Unable to open Semantic Scholar."), ex);
}
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

public class ExternalLinkCreator {
private static final String SHORTSCIENCE_SEARCH_URL = "https://www.shortscience.org/internalsearch";
private static final String GOOGLE_SCHOLAR_SEARCH_URL = "https://scholar.google.com/scholar";
private static final String SEMANTIC_SCHOLAR_SEARCH_URL = "https://www.semanticscholar.org/search";

/**
* Get a URL to the search results of ShortScience for the BibEntry's title
Expand All @@ -37,4 +39,47 @@ public static Optional<String> getShortScienceSearchURL(BibEntry entry) {
return uriBuilder.toString();
});
}

/**
* Get a URL to the search results of Google Scholar for the BibEntry's title.
*
* @param entry The entry to search for. Expects the BibEntry's title to be set for successful return.
* @return The URL if it was successfully created
*/
public static Optional<String> getGoogleScholarSearchURL(BibEntry entry) {
return entry.getField(StandardField.TITLE).map(title -> {
URIBuilder uriBuilder;
try {
uriBuilder = new URIBuilder(GOOGLE_SCHOLAR_SEARCH_URL);
} catch (URISyntaxException e) {
// This should never be able to happen as it would require the field to be misconfigured.
throw new AssertionError("Google Scholar URL is invalid.", e);
}

String filteredTitle = LatexToUnicodeAdapter.format(title);
uriBuilder.addParameter("q", filteredTitle.trim());
return uriBuilder.toString();
});
}

/**
* Get a URL to the search results of Semantic Scholar for the BibEntry's title.
*
* @param entry The entry to search for. Expects the BibEntry's title to be set for successful return.
* @return The URL if it was successfully created
*/
public static Optional<String> getSemanticScholarSearchURL(BibEntry entry) {
return entry.getField(StandardField.TITLE).map(title -> {
URIBuilder uriBuilder;
try {
uriBuilder = new URIBuilder(SEMANTIC_SCHOLAR_SEARCH_URL);
} catch (URISyntaxException e) {
throw new AssertionError("Semantic Scholar URL is invalid.", e);
}

String filteredTitle = LatexToUnicodeAdapter.format(title);
uriBuilder.addParameter("q", filteredTitle.trim());
return uriBuilder.toString();
});
}
}
4 changes: 4 additions & 0 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2292,6 +2292,10 @@ Text\ editor=Text editor

Search\ ShortScience=Search ShortScience
Unable\ to\ open\ ShortScience.=Unable to open ShortScience.
Search\ Google\ Scholar=Search Google Scholar
Unable\ to\ open\ Google\ Scholar.=Unable to open Google Scholar.
Search\ Semantic\ Scholar=Search Semantic Scholar
Unable\ to\ open\ Semantic\ Scholar.=Unable to open Semantic Scholar.

Shared\ database=Shared database
Lookup=Lookup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;

import static org.jabref.logic.util.ExternalLinkCreator.getGoogleScholarSearchURL;
import static org.jabref.logic.util.ExternalLinkCreator.getSemanticScholarSearchURL;
import static org.jabref.logic.util.ExternalLinkCreator.getShortScienceSearchURL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -48,6 +50,26 @@ void getShortScienceSearchURLEncodesSpecialCharacters(String title) {
assertTrue(urlIsValid(url.get()));
}

@ParameterizedTest
@MethodSource("specialCharactersProvider")
void getGoogleScholarSearchURLEncodesSpecialCharacters(String title) {
BibEntry entry = new BibEntry();
entry.setField(StandardField.TITLE, title);
Optional<String> url = getGoogleScholarSearchURL(entry);
assertTrue(url.isPresent());
assertTrue(urlIsValid(url.get()));
}

@ParameterizedTest
@MethodSource("specialCharactersProvider")
void getSemanticScholarSearchURLEncodesSpecialCharacters(String title) {
BibEntry entry = new BibEntry();
entry.setField(StandardField.TITLE, title);
Optional<String> url = getSemanticScholarSearchURL(entry);
assertTrue(url.isPresent());
assertTrue(urlIsValid(url.get()));
}

@ParameterizedTest
@CsvSource({
"'歷史書 📖 📚', 'https://www.shortscience.org/internalsearch?q=%E6%AD%B7%E5%8F%B2%E6%9B%B8%20%F0%9F%93%96%20%F0%9F%93%9A'",
Expand All @@ -61,12 +83,50 @@ void getShortScienceSearchURLEncodesCharacters(String title, String expectedUrl)
assertEquals(Optional.of(expectedUrl), url);
}

@ParameterizedTest
@CsvSource({
"'歷史書 📖 📚', 'https://scholar.google.com/scholar?q=%E6%AD%B7%E5%8F%B2%E6%9B%B8%20%F0%9F%93%96%20%F0%9F%93%9A'",
"' History Textbook ', 'https://scholar.google.com/scholar?q=History%20Textbook'",
"'History%20Textbook', 'https://scholar.google.com/scholar?q=History%2520Textbook'",
"'JabRef bibliography management', 'https://scholar.google.com/scholar?q=JabRef%20bibliography%20management'"
})
void getGoogleScholarSearchURLEncodesCharacters(String title, String expectedUrl) {
BibEntry entry = new BibEntry().withField(StandardField.TITLE, title);
Optional<String> url = getGoogleScholarSearchURL(entry);
assertEquals(Optional.of(expectedUrl), url);
}

@ParameterizedTest
@CsvSource({
"'歷史書 📖 📚', 'https://www.semanticscholar.org/search?q=%E6%AD%B7%E5%8F%B2%E6%9B%B8%20%F0%9F%93%96%20%F0%9F%93%9A'",
"' History Textbook ', 'https://www.semanticscholar.org/search?q=History%20Textbook'",
"'History%20Textbook', 'https://www.semanticscholar.org/search?q=History%2520Textbook'",
"'JabRef bibliography management', 'https://www.semanticscholar.org/search?q=JabRef%20bibliography%20management'"
})
void getSemanticScholarSearchURLEncodesCharacters(String title, String expectedUrl) {
BibEntry entry = new BibEntry().withField(StandardField.TITLE, title);
Optional<String> url = getSemanticScholarSearchURL(entry);
assertEquals(Optional.of(expectedUrl), url);
}

@Test
void getShortScienceSearchURLReturnsEmptyOnMissingTitle() {
BibEntry entry = new BibEntry();
assertEquals(Optional.empty(), getShortScienceSearchURL(entry));
}

@Test
void getGoogleScholarSearchURLReturnsEmptyOnMissingTitle() {
BibEntry entry = new BibEntry();
assertEquals(Optional.empty(), getGoogleScholarSearchURL(entry));
}

@Test
void getSemanticScholarSearchURLReturnsEmptyOnMissingTitle() {
BibEntry entry = new BibEntry();
assertEquals(Optional.empty(), getSemanticScholarSearchURL(entry));
}

@Test
void getShortScienceSearchURLWithoutLaTeX() {
BibEntry entry = new BibEntry();
Expand All @@ -77,4 +137,26 @@ void getShortScienceSearchURLWithoutLaTeX() {
String expectedUrl = "https://www.shortscience.org/internalsearch?q=The%20Difference%20Between%20Graph-Based%20and%20Block-Structured%20Business%20Process%20Modelling%20Languages";
assertEquals(Optional.of(expectedUrl), url);
}

@Test
void getGoogleScholarSearchURLWithoutLaTeX() {
BibEntry entry = new BibEntry();
entry.withField(StandardField.TITLE, "{The Difference Between Graph-Based and Block-Structured Business Process Modelling Languages}");

Optional<String> url = getGoogleScholarSearchURL(entry);

String expectedUrl = "https://scholar.google.com/scholar?q=The%20Difference%20Between%20Graph-Based%20and%20Block-Structured%20Business%20Process%20Modelling%20Languages";
assertEquals(Optional.of(expectedUrl), url);
}

@Test
void getSemanticScholarSearchURLWithoutLaTeX() {
BibEntry entry = new BibEntry();
entry.withField(StandardField.TITLE, "{The Difference Between Graph-Based and Block-Structured Business Process Modelling Languages}");

Optional<String> url = getSemanticScholarSearchURL(entry);

String expectedUrl = "https://www.semanticscholar.org/search?q=The%20Difference%20Between%20Graph-Based%20and%20Block-Structured%20Business%20Process%20Modelling%20Languages";
assertEquals(Optional.of(expectedUrl), url);
}
}
Loading