diff --git a/CHANGELOG.md b/CHANGELOG.md index caf8b06c061..acef15fcc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,13 +24,16 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We call backup files `.bak` and temporary writing files now `.sav`. - JabRef keeps 10 older versions of a `.bib` file in the [user data dir](https://github.com/harawata/appdirs#supported-directories) (instead of a single `.sav` (now: `.bak`) file in the directory of the `.bib` file) - We changed the button label from "Return to JabRef" to "Return to library" to better indicate the purpose of the action. -- We removed "last-search-date" from the SLR feature, because the last-search-date can be deducted from the git logs. +- We removed "last-search-date" from the SLR feature, because the last-search-date can be deducted from the git logs. [#9116](https://github.com/JabRef/jabref/pull/9116) +- A user can now add arbitrary data into `study.yml`. JabRef just ignores this data. [#9124](https://github.com/JabRef/jabref/pull/9124) - We reworked the External Changes Resolver dialog. [#9021](https://github.com/JabRef/jabref/pull/9021) - The fallback directory of the file folder now is the general file directory. In case there was a directory configured for a library and this directory was not found, JabRef placed the PDF next to the .bib file and not into the general file directory. - The global default directory for storing PDFs is now the subdirectory "JabRef" in the user's home. +- We reworked the Define study parameters dialog. [#9123](https://github.com/JabRef/jabref/pull/9123) ### Fixed +- We fixed an issue where author names with tilde accents (for example ñ) were marked as "Names are not in the standard BibTex format" [#8071](https://github.com/JabRef/jabref/issues/8071) - We fixed an issue where the possibility to generate a subdatabase from an aux file was writing empty files when called from the commandline [#9115](https://github.com/JabRef/jabref/issues/9115), [forum#3516](https://discourse.jabref.org/t/export-subdatabase-from-aux-file-on-macos-command-line/3516) - We fixed the display of issue, number, eid and pages fields in the entry preview. [#8607](https://github.com/JabRef/jabref/pull/8607), [#8372](https://github.com/JabRef/jabref/issues/8372), [Koppor#514](https://github.com/koppor/jabref/issues/514), [forum#2390](https://discourse.jabref.org/t/unable-to-edit-my-bibtex-file-that-i-used-before-vers-5-1/2390), [forum#3462](https://discourse.jabref.org/t/jabref-5-6-need-help-with-export-from-jabref-to-microsoft-word-entry-preview-of-apa-7-not-rendering-correctly/3462) - We fixed the page ranges checker to detect article numbers in the pages field (used at [Check Integrity](https://docs.jabref.org/finding-sorting-and-cleaning-entries/checkintegrity)). [#8607](https://github.com/JabRef/jabref/pull/8607) diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml b/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml index b6ea1d41f7a..297ee2c1318 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml @@ -2,7 +2,6 @@ - @@ -19,50 +18,86 @@ - +
- + - + - + - - + + - - + + - + - - + + - + @@ -70,32 +105,48 @@ - + - + - - + + - + @@ -103,35 +154,54 @@ - + - + - + - - + + - + @@ -139,33 +209,33 @@ - + - - + - + - + @@ -196,6 +277,11 @@
- - + +
diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java index ce1c5f10e3e..ebd7e89619f 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java @@ -13,7 +13,6 @@ import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; -import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -22,6 +21,7 @@ import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseButton; import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; @@ -29,23 +29,26 @@ import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.ValueTableCellFactory; -import org.jabref.gui.util.ViewModelListCellFactory; +import org.jabref.gui.util.ViewModelTableRowFactory; import org.jabref.logic.l10n.Localization; import org.jabref.model.study.Study; import org.jabref.preferences.PreferencesService; import com.airhacks.afterburner.views.ViewLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class controls the user interface of the study definition management dialog. The UI elements and their layout * are defined in the FXML file. */ public class ManageStudyDefinitionView extends BaseDialog { + private static final Logger LOGGER = LoggerFactory.getLogger(ManageStudyDefinitionView.class); + @FXML private TextField studyTitle; @FXML private TextField addAuthor; @FXML private TextField addResearchQuestion; @FXML private TextField addQuery; - @FXML private ComboBox databaseSelector; @FXML private TextField studyDirectory; @FXML private ButtonType saveButtonType; @@ -66,7 +69,6 @@ public class ManageStudyDefinitionView extends BaseDialog @FXML private TableView databaseTable; @FXML private TableColumn databaseEnabledColumn; @FXML private TableColumn databaseColumn; - @FXML private TableColumn databaseActionColumn; @Inject private DialogService dialogService; @Inject private PreferencesService prefs; @@ -80,7 +82,7 @@ public class ManageStudyDefinitionView extends BaseDialog /** * This can be used to either create new study objects or edit existing ones. * - * @param study null if a new study is created. Otherwise the study object to edit. + * @param study null if a new study is created. Otherwise, the study object to edit. * @param studyDirectory the directory where the study to edit is located (null if a new study is created) */ public ManageStudyDefinitionView(Study study, Path studyDirectory, Path workingDirectory) { @@ -118,11 +120,14 @@ private void setupSaveButton() { @FXML private void initialize() { - viewModel = new ManageStudyDefinitionViewModel( - study, - workingDirectory, - prefs.getImportFormatPreferences(), - prefs.getImporterPreferences()); + if (Objects.isNull(study)) { + viewModel = new ManageStudyDefinitionViewModel( + prefs.getImportFormatPreferences(), + prefs.getImporterPreferences()); + } else { + LOGGER.error("Not yet implemented"); + return; + } // Listen whether any databases are removed from selection -> Add back to the database selector studyTitle.textProperty().bindBidirectional(viewModel.titleProperty()); @@ -161,25 +166,24 @@ private void initQueriesTab() { } private void initDatabasesTab() { - new ViewModelListCellFactory().withText(StudyDatabaseItem::getName) - .install(databaseSelector); - databaseSelector.setItems(viewModel.getNonSelectedDatabases()); + new ViewModelTableRowFactory() + .withOnMouseClickedEvent((entry, event) -> { + if (event.getButton() == MouseButton.PRIMARY) { + entry.setEnabled(!entry.isEnabled()); + } + }) + .install(databaseTable); - setupCommonPropertiesForTables(databaseSelector, this::addDatabase, databaseColumn, databaseActionColumn); + databaseColumn.setReorderable(false); + databaseColumn.setCellFactory(TextFieldTableCell.forTableColumn()); databaseEnabledColumn.setResizable(false); databaseEnabledColumn.setReorderable(false); - databaseEnabledColumn.setCellValueFactory(param -> param.getValue().enabledProperty()); databaseEnabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(databaseEnabledColumn)); + databaseEnabledColumn.setCellValueFactory(param -> param.getValue().enabledProperty()); + databaseColumn.setEditable(false); databaseColumn.setCellValueFactory(param -> param.getValue().nameProperty()); - databaseActionColumn.setCellValueFactory(param -> param.getValue().nameProperty()); - new ValueTableCellFactory() - .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove")) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeDatabase(item)) - .install(databaseActionColumn); databaseTable.setItems(viewModel.getDatabases()); } @@ -231,14 +235,6 @@ private void addQuery() { addQuery.setText(""); } - /** - * Add selected entry from combobox, push onto database pop from nonselecteddatabase (combobox) - */ - @FXML - private void addDatabase() { - viewModel.addDatabase(databaseSelector.getSelectionModel().getSelectedItem()); - } - @FXML public void selectStudyDirectory() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java index 01350ab64bb..3f87b2232c3 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java @@ -2,9 +2,10 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.util.Comparator; +import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import javafx.beans.property.Property; @@ -17,6 +18,11 @@ import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fetcher.ACMPortalFetcher; +import org.jabref.logic.importer.fetcher.CompositeSearchBasedFetcher; +import org.jabref.logic.importer.fetcher.DBLPFetcher; +import org.jabref.logic.importer.fetcher.IEEE; +import org.jabref.logic.importer.fetcher.SpringerFetcher; import org.jabref.model.study.Study; import org.jabref.model.study.StudyDatabase; import org.jabref.model.study.StudyQuery; @@ -31,45 +37,69 @@ public class ManageStudyDefinitionViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ManageStudyDefinitionViewModel.class); + private static final Set DEFAULT_SELECTION = Set.of( + ACMPortalFetcher.FETCHER_NAME, + IEEE.FETCHER_NAME, + SpringerFetcher.FETCHER_NAME, + DBLPFetcher.FETCHER_NAME); + + private final StringProperty title = new SimpleStringProperty(); private final ObservableList authors = FXCollections.observableArrayList(); private final ObservableList researchQuestions = FXCollections.observableArrayList(); private final ObservableList queries = FXCollections.observableArrayList(); private final ObservableList databases = FXCollections.observableArrayList(); + // Hold the complement of databases for the selector - private final ObservableList nonSelectedDatabases = FXCollections.observableArrayList(); private final SimpleStringProperty directory = new SimpleStringProperty(); - private Study study; + /** + * Constructor for a new study + */ + public ManageStudyDefinitionViewModel(ImportFormatPreferences importFormatPreferences, + ImporterPreferences importerPreferences) { + databases.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) + .stream() + .map(SearchBasedFetcher::getName) + // The user wants to select specific fetchers + // The fetcher summarizing ALL fetchers can be emulated by selecting ALL fetchers (which happens rarely when doing an SLR) + .filter(name -> !name.equals(CompositeSearchBasedFetcher.FETCHER_NAME)) + .map(name -> { + boolean enabled = DEFAULT_SELECTION.contains(name); + return new StudyDatabaseItem(name, enabled); + }) + .toList()); + } + + /** + * Constructor for an existing study + * + * @param study The study to initialize the UI from + * @param studyDirectory The path where the study resides + */ public ManageStudyDefinitionViewModel(Study study, Path studyDirectory, ImportFormatPreferences importFormatPreferences, ImporterPreferences importerPreferences) { - if (Objects.isNull(study)) { - computeNonSelectedDatabases(importFormatPreferences, importerPreferences); - return; - } - this.study = study; + // copy the content of the study object into the UI fields + authors.addAll(Objects.requireNonNull(study).getAuthors()); title.setValue(study.getTitle()); - authors.addAll(study.getAuthors()); researchQuestions.addAll(study.getResearchQuestions()); queries.addAll(study.getQueries().stream().map(StudyQuery::getQuery).toList()); - databases.addAll(study.getDatabases() - .stream() - .map(studyDatabase -> new StudyDatabaseItem(studyDatabase.getName(), studyDatabase.isEnabled())) - .toList()); - computeNonSelectedDatabases(importFormatPreferences, importerPreferences); - if (!Objects.isNull(studyDirectory)) { - this.directory.set(studyDirectory.toString()); - } - } - - private void computeNonSelectedDatabases(ImportFormatPreferences importFormatPreferences, ImporterPreferences importerPreferences) { - nonSelectedDatabases.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) - .stream() - .map(SearchBasedFetcher::getName) - .map(s -> new StudyDatabaseItem(s, true)) - .filter(studyDatabase -> !databases.contains(studyDatabase)).toList()); + List studyDatabases = study.getDatabases(); + databases.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) + .stream() + .map(SearchBasedFetcher::getName) + // The user wants to select specific fetchers + // The fetcher summarizing ALL fetchers can be emulated by selecting ALL fetchers (which happens rarely when doing an SLR) + .filter(name -> !name.equals(CompositeSearchBasedFetcher.FETCHER_NAME)) + .map(name -> { + boolean enabled = studyDatabases.contains(new StudyDatabase(name, true)); + return new StudyDatabaseItem(name, enabled); + }) + .toList()); + + this.directory.set(Objects.requireNonNull(studyDirectory).toString()); } public StringProperty getTitle() { @@ -96,10 +126,6 @@ public ObservableList getDatabases() { return databases; } - public ObservableList getNonSelectedDatabases() { - return nonSelectedDatabases; - } - public void addAuthor(String author) { if (author.isBlank()) { return; @@ -121,29 +147,18 @@ public void addQuery(String query) { queries.add(query); } - public void addDatabase(StudyDatabaseItem database) { - if (Objects.isNull(database)) { - return; - } - nonSelectedDatabases.remove(database); - if (!databases.contains(database)) { - databases.add(database); - } - } - public SlrStudyAndDirectory saveStudy() { - if (Objects.isNull(study)) { - study = new Study(); - } - study.setTitle(title.getValueSafe()); - study.setAuthors(authors); - study.setResearchQuestions(researchQuestions); - study.setQueries(queries.stream().map(StudyQuery::new).collect(Collectors.toList())); - study.setDatabases(databases.stream().map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), studyDatabaseItem.isEnabled())).collect(Collectors.toList())); + Study study = new Study( + authors, + title.getValueSafe(), + researchQuestions, + queries.stream().map(StudyQuery::new).collect(Collectors.toList()), + databases.stream().map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), studyDatabaseItem.isEnabled())).filter(StudyDatabase::isEnabled).collect(Collectors.toList())); Path studyDirectory = null; try { studyDirectory = Path.of(directory.getValueSafe()); - } catch (InvalidPathException e) { + } catch ( + InvalidPathException e) { LOGGER.error("Invalid path was provided: {}", directory); } return new SlrStudyAndDirectory(study, studyDirectory); @@ -153,20 +168,6 @@ public Property titleProperty() { return title; } - public void removeDatabase(String database) { - // If a database is added from the combo box it should be enabled by default - Optional correspondingDatabase = databases.stream().filter(studyDatabaseItem -> studyDatabaseItem.getName().equals(database)).findFirst(); - if (correspondingDatabase.isEmpty()) { - return; - } - StudyDatabaseItem databaseToRemove = correspondingDatabase.get(); - databases.remove(databaseToRemove); - databaseToRemove.setEnabled(true); - nonSelectedDatabases.add(databaseToRemove); - // Resort list - nonSelectedDatabases.sort(Comparator.comparing(StudyDatabaseItem::getName)); - } - public void setStudyDirectory(Optional studyRepositoryRoot) { getDirectory().setValue(studyRepositoryRoot.map(Path::toString).orElseGet(() -> getDirectory().getValueSafe())); } diff --git a/src/main/java/org/jabref/gui/slr/StudyDatabaseItem.java b/src/main/java/org/jabref/gui/slr/StudyDatabaseItem.java index 50ceda09143..bc2cb7ac859 100644 --- a/src/main/java/org/jabref/gui/slr/StudyDatabaseItem.java +++ b/src/main/java/org/jabref/gui/slr/StudyDatabaseItem.java @@ -1,16 +1,23 @@ package org.jabref.gui.slr; +import java.util.Objects; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import org.jabref.model.study.StudyDatabase; + +/** + * View representation of {@link StudyDatabase} + */ public class StudyDatabaseItem { private final StringProperty name; private final BooleanProperty enabled; public StudyDatabaseItem(String name, boolean enabled) { - this.name = new SimpleStringProperty(name); + this.name = new SimpleStringProperty(Objects.requireNonNull(name)); this.enabled = new SimpleBooleanProperty(enabled); } @@ -38,6 +45,14 @@ public BooleanProperty enabledProperty() { return enabled; } + @Override + public String toString() { + return "StudyDatabaseItem{" + + "name=" + name.get() + + ", enabled=" + enabled.get() + + '}'; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -46,19 +61,12 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - StudyDatabaseItem that = (StudyDatabaseItem) o; - - if (isEnabled() != that.isEnabled()) { - return false; - } - return getName() != null ? getName().equals(that.getName()) : that.getName() == null; + return Objects.equals(getName(), that.getName()) && Objects.equals(isEnabled(), that.isEnabled()); } @Override public int hashCode() { - int result = getName() != null ? getName().hashCode() : 0; - result = 31 * result + (isEnabled() ? 1 : 0); - return result; + return Objects.hash(getName(), isEnabled()); } } diff --git a/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java b/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java index df2138434d2..db1bb1b9a9f 100644 --- a/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java +++ b/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java @@ -8,10 +8,8 @@ import org.jabref.model.study.Study; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class StudyYamlParser { @@ -20,7 +18,6 @@ public class StudyYamlParser { */ public Study parseStudyYamlFile(Path studyYamlFile) throws IOException { ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); - yamlMapper.registerModule(new JavaTimeModule()); try (InputStream fileInputStream = new FileInputStream(studyYamlFile.toFile())) { return yamlMapper.readValue(fileInputStream, Study.class); } @@ -32,8 +29,6 @@ public Study parseStudyYamlFile(Path studyYamlFile) throws IOException { public void writeStudyYamlFile(Study study, Path studyYamlFile) throws IOException { ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)); - yamlMapper.registerModule(new JavaTimeModule()); - yamlMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); yamlMapper.writeValue(studyYamlFile.toFile(), study); } } diff --git a/src/main/java/org/jabref/logic/importer/AuthorListParser.java b/src/main/java/org/jabref/logic/importer/AuthorListParser.java index bc4835c92f2..41acfd2a503 100644 --- a/src/main/java/org/jabref/logic/importer/AuthorListParser.java +++ b/src/main/java/org/jabref/logic/importer/AuthorListParser.java @@ -447,7 +447,7 @@ private Token getToken() { if (c == '\\') { currentBackslash = tokenEnd; } - if ((bracesLevel == 0) && ((",;~-".indexOf(c) != -1) || Character.isWhitespace(c))) { + if ((bracesLevel == 0) && ((",;-".indexOf(c) != -1) || Character.isWhitespace(c))) { break; } tokenEnd++; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ACMPortalFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/ACMPortalFetcher.java index 10ba0e1b1b4..5fd153a07f2 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ACMPortalFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ACMPortalFetcher.java @@ -19,6 +19,8 @@ public class ACMPortalFetcher implements SearchBasedParserFetcher { + public static final String FETCHER_NAME = "ACM Portal"; + private static final String SEARCH_URL = "https://dl.acm.org/action/doSearch"; public ACMPortalFetcher() { @@ -28,7 +30,7 @@ public ACMPortalFetcher() { @Override public String getName() { - return "ACM Portal"; + return FETCHER_NAME; } @Override diff --git a/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java index 97506c284a6..bee6f3c7fc9 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/CompositeSearchBasedFetcher.java @@ -17,6 +17,8 @@ public class CompositeSearchBasedFetcher implements SearchBasedFetcher { + public static final String FETCHER_NAME = "SearchAll"; + private static final Logger LOGGER = LoggerFactory.getLogger(CompositeSearchBasedFetcher.class); private final Set fetchers; @@ -36,7 +38,7 @@ public CompositeSearchBasedFetcher(Set searchBasedFetchers, @Override public String getName() { - return "SearchAll"; + return FETCHER_NAME; } @Override diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java index d961ce0b3bc..19d1d67ec1a 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java @@ -33,6 +33,7 @@ * @see Basic API documentation */ public class DBLPFetcher implements SearchBasedParserFetcher { + public static final String FETCHER_NAME = "DBLP"; private static final String BASIC_SEARCH_URL = "https://dblp.org/search/publ/api"; @@ -76,7 +77,7 @@ public void doPostCleanup(BibEntry entry) { @Override public String getName() { - return "DBLP"; + return FETCHER_NAME; } @Override diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index 3ca57ff3cbf..fe9860c3f44 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -48,9 +48,9 @@ */ public class IEEE implements FulltextFetcher, PagedSearchBasedParserFetcher, CustomizableKeyFetcher { - private static final Logger LOGGER = LoggerFactory.getLogger(IEEE.class); + public static final String FETCHER_NAME = "IEEEXplore"; - private static final String FETCHER_NAME = "IEEEXplore"; + private static final Logger LOGGER = LoggerFactory.getLogger(IEEE.class); private static final String STAMP_BASE_STRING_DOCUMENT = "/stamp/stamp.jsp?tp=&arnumber="; private static final Pattern STAMP_PATTERN = Pattern.compile("(/stamp/stamp.jsp\\?t?p?=?&?arnumber=[0-9]+)"); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java index d0aa60e4494..e1764120e14 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java @@ -40,13 +40,14 @@ */ public class SpringerFetcher implements PagedSearchBasedParserFetcher, CustomizableKeyFetcher { + public static final String FETCHER_NAME = "Springer"; + private static final Logger LOGGER = LoggerFactory.getLogger(SpringerFetcher.class); private static final String API_URL = "https://api.springernature.com/meta/v1/json"; private static final String API_KEY = new BuildInfo().springerNatureAPIKey; // Springer query using the parameter 'q=doi:10.1007/s11276-008-0131-4s=1' will respond faster private static final String TEST_URL_WITHOUT_API_KEY = "https://api.springernature.com/meta/v1/json?q=doi:10.1007/s11276-008-0131-4s=1&p=1&api_key="; - private static final String FETCHER_NAME = "Springer"; private final ImporterPreferences importerPreferences; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerLink.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerLink.java index 78a7a420718..769e7f6c200 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerLink.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerLink.java @@ -27,12 +27,13 @@ * Uses Springer API, see https://dev.springer.com */ public class SpringerLink implements FulltextFetcher { + public static final String FETCHER_NAME = "Springer"; + private static final Logger LOGGER = LoggerFactory.getLogger(SpringerLink.class); private static final String API_URL = "https://api.springer.com/meta/v1/json"; private static final String API_KEY = new BuildInfo().springerNatureAPIKey; private static final String CONTENT_HOST = "link.springer.com"; - private static final String FETCHER_NAME = "Springer"; private final ImporterPreferences importerPreferences; diff --git a/src/main/java/org/jabref/logic/util/strings/HTMLUnicodeConversionMaps.java b/src/main/java/org/jabref/logic/util/strings/HTMLUnicodeConversionMaps.java index 587f7197218..86973053a09 100644 --- a/src/main/java/org/jabref/logic/util/strings/HTMLUnicodeConversionMaps.java +++ b/src/main/java/org/jabref/logic/util/strings/HTMLUnicodeConversionMaps.java @@ -762,6 +762,7 @@ public class HTMLUnicodeConversionMaps { {"119978", "Oscr", "$\\mathcal{O}$"}, // script capital O -- possibly use \mathscr {"119984", "Uscr", "$\\mathcal{U}$"}, // script capital U -- possibly use \mathscr {"120598", "", "$\\epsilon$"}, // mathematical italic epsilon U+1D716 -- requires amsmath + {"120599", "", "{{\\˜{n}}}"}, // n with tide }; // List of combining accents @@ -888,7 +889,6 @@ public class HTMLUnicodeConversionMaps { // Manual corrections LATEX_HTML_CONVERSION_MAP.put("AA", "Å"); // Overwritten by Å which is less supported LATEX_UNICODE_CONVERSION_MAP.put("AA", "Å"); // Overwritten by Ångstrom symbol - LATEX_UNICODE_CONVERSION_MAP.put("'n", "ń"); // Manual additions // Support relax to the extent that it is simply removed diff --git a/src/main/java/org/jabref/logic/util/strings/RtfCharMap.java b/src/main/java/org/jabref/logic/util/strings/RtfCharMap.java index 46f49f37bfc..393f9685ffb 100644 --- a/src/main/java/org/jabref/logic/util/strings/RtfCharMap.java +++ b/src/main/java/org/jabref/logic/util/strings/RtfCharMap.java @@ -4,7 +4,7 @@ public class RtfCharMap { - private HashMap rtfMap = new HashMap<>(); + private final HashMap rtfMap = new HashMap<>(); public RtfCharMap() { put("`a", "\\'e0"); diff --git a/src/main/java/org/jabref/model/study/Study.java b/src/main/java/org/jabref/model/study/Study.java index 1c85a1d77c2..c9a416eda4e 100644 --- a/src/main/java/org/jabref/model/study/Study.java +++ b/src/main/java/org/jabref/model/study/Study.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -12,6 +13,8 @@ * This class defines all aspects of a scientific study relevant to the application. It is a proxy for the file based study definition. */ @JsonPropertyOrder({"authors", "title", "research-questions", "queries", "databases"}) +// The user might add arbitrary content to the YAML +@JsonIgnoreProperties(ignoreUnknown = true) public class Study { private List authors; @@ -35,7 +38,7 @@ public Study(List authors, String title, List researchQuestions, /** * Used for Jackson deserialization */ - public Study() { + private Study() { } public List getAuthors() { diff --git a/src/main/java/org/jabref/model/study/StudyDatabase.java b/src/main/java/org/jabref/model/study/StudyDatabase.java index ac71a7160c2..375b59fb57c 100644 --- a/src/main/java/org/jabref/model/study/StudyDatabase.java +++ b/src/main/java/org/jabref/model/study/StudyDatabase.java @@ -1,5 +1,8 @@ package org.jabref.model.study; +/** + * data model for the view {@link org.jabref.gui.slr.StudyDatabaseItem} + */ public class StudyDatabase { private String name; private boolean enabled; diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 6dff22b68d0..c560fc1b280 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2406,7 +2406,6 @@ Database=Database Databases=Databases Manage\ study\ definition=Manage study definition Add\ Author\:=Add Author\: -Add\ Database\:=Add Database\: Add\ Query\:=Add Query\: Add\ Research\ Question\:=Add Research Question\: Perform\ search\ for\ existing\ systematic\ literature\ review=Perform search for existing systematic literature review @@ -2416,6 +2415,7 @@ Searching=Searching Start\ new\ systematic\ literature\ review=Start new systematic literature review Study\ Title\:=Study Title\: Study\ repository\ could\ not\ be\ created=Study repository could not be created +Select\ Databases\:=Select Databases: All\ query\ terms\ are\ joined\ using\ the\ logical\ AND,\ and\ OR\ operators=All query terms are joined using the logical AND, and OR operators Finalize=Finalize diff --git a/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java b/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java new file mode 100644 index 00000000000..f6f9d927909 --- /dev/null +++ b/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java @@ -0,0 +1,98 @@ +package org.jabref.gui.slr; + +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ImporterPreferences; +import org.jabref.model.study.Study; +import org.jabref.model.study.StudyDatabase; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ManageStudyDefinitionViewModelTest { + private ImportFormatPreferences importFormatPreferences; + private ImporterPreferences importerPreferences; + + @BeforeEach + void setUp() { + // code taken from org.jabref.logic.importer.WebFetchersTest.setUp + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + importerPreferences = mock(ImporterPreferences.class); + FieldContentFormatterPreferences fieldContentFormatterPreferences = mock(FieldContentFormatterPreferences.class); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences); + } + + @Test + public void emptyStudyConstructorFillsDatabasesCorrectly() { + ManageStudyDefinitionViewModel manageStudyDefinitionViewModel = new ManageStudyDefinitionViewModel(importFormatPreferences, importerPreferences); + assertEquals(List.of( + new StudyDatabaseItem("ACM Portal", true), + new StudyDatabaseItem("ArXiv", false), + new StudyDatabaseItem("Biodiversity Heritage", false), + new StudyDatabaseItem("CiteSeerX", false), + new StudyDatabaseItem("Collection of Computer Science Bibliographies", false), + new StudyDatabaseItem("Crossref", false), + new StudyDatabaseItem("DBLP", true), + new StudyDatabaseItem("DOAB", false), + new StudyDatabaseItem("DOAJ", false), + new StudyDatabaseItem("GVK", false), + new StudyDatabaseItem("IEEEXplore", true), + new StudyDatabaseItem("INSPIRE", false), + new StudyDatabaseItem("MathSciNet", false), + new StudyDatabaseItem("Medline/PubMed", false), + new StudyDatabaseItem("ResearchGate", false), + new StudyDatabaseItem("SAO/NASA ADS", false), + new StudyDatabaseItem("SemanticScholar", false), + new StudyDatabaseItem("Springer", true), + new StudyDatabaseItem("zbMATH", false) + ), manageStudyDefinitionViewModel.getDatabases()); + } + + @Test + public void studyConstructorFillsDatabasesCorrectly(@TempDir Path tempDir) { + List databases = List.of( + new StudyDatabase("ACM Portal", true)); + Study study = new Study( + List.of("Name"), + "title", + List.of("Q1"), + List.of(), + databases + ); + ManageStudyDefinitionViewModel manageStudyDefinitionViewModel = new ManageStudyDefinitionViewModel( + study, + tempDir, + importFormatPreferences, + importerPreferences); + assertEquals(List.of( + new StudyDatabaseItem("ACM Portal", true), + new StudyDatabaseItem("ArXiv", false), + new StudyDatabaseItem("Biodiversity Heritage", false), + new StudyDatabaseItem("CiteSeerX", false), + new StudyDatabaseItem("Collection of Computer Science Bibliographies", false), + new StudyDatabaseItem("Crossref", false), + new StudyDatabaseItem("DBLP", false), + new StudyDatabaseItem("DOAB", false), + new StudyDatabaseItem("DOAJ", false), + new StudyDatabaseItem("GVK", false), + new StudyDatabaseItem("IEEEXplore", false), + new StudyDatabaseItem("INSPIRE", false), + new StudyDatabaseItem("MathSciNet", false), + new StudyDatabaseItem("Medline/PubMed", false), + new StudyDatabaseItem("ResearchGate", false), + new StudyDatabaseItem("SAO/NASA ADS", false), + new StudyDatabaseItem("SemanticScholar", false), + new StudyDatabaseItem("Springer", false), + new StudyDatabaseItem("zbMATH", false) + ), manageStudyDefinitionViewModel.getDatabases()); + } +} diff --git a/src/test/java/org/jabref/logic/crawler/StudyYamlParserTest.java b/src/test/java/org/jabref/logic/crawler/StudyYamlParserTest.java index 1b169f7f00a..d4e63f64bc1 100644 --- a/src/test/java/org/jabref/logic/crawler/StudyYamlParserTest.java +++ b/src/test/java/org/jabref/logic/crawler/StudyYamlParserTest.java @@ -18,6 +18,7 @@ class StudyYamlParserTest { @TempDir static Path testDirectory; + Study expectedStudy; @BeforeEach @@ -48,4 +49,15 @@ public void writeStudyFileSuccessfully() throws Exception { Study study = new StudyYamlParser().parseStudyYamlFile(testDirectory.resolve("study.yml")); assertEquals(expectedStudy, study); } + + @Test + public void readsJabRef57StudySuccessfully() throws Exception { + // The field "last-search-date" was removed + // If the field is "just" removed from the datamodel, one gets following exception: + // com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "last-search-date" (class org.jabref.model.study.Study), not marked as ignorable (5 known properties: "authors", "research-questions", "queries", "title", "databases"]) + // This tests ensures that this exception does not occur + URL studyDefinition = StudyYamlParser.class.getResource("study-jabref-5.7.yml"); + Study study = new StudyYamlParser().parseStudyYamlFile(Path.of(studyDefinition.toURI())); + assertEquals(expectedStudy, study); + } } diff --git a/src/test/java/org/jabref/logic/formatter/bibtexfields/HtmlToUnicodeFormatterTest.java b/src/test/java/org/jabref/logic/formatter/bibtexfields/HtmlToUnicodeFormatterTest.java index 81997715f25..64f14737bb9 100644 --- a/src/test/java/org/jabref/logic/formatter/bibtexfields/HtmlToUnicodeFormatterTest.java +++ b/src/test/java/org/jabref/logic/formatter/bibtexfields/HtmlToUnicodeFormatterTest.java @@ -1,7 +1,11 @@ package org.jabref.logic.formatter.bibtexfields; +import java.util.stream.Stream; + import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -9,52 +13,28 @@ public class HtmlToUnicodeFormatterTest { private HtmlToUnicodeFormatter formatter; + private static Stream data() { + return Stream.of( + Arguments.of("abc", "abc"), + Arguments.of("åäö", "åäö"), + Arguments.of("í", "í"), + Arguments.of("Ε", "Ε"), + Arguments.of("ä", "ä"), + Arguments.of("ä", "ä"), + Arguments.of("ä", "ä"), + Arguments.of("ñ", "ñ"), + Arguments.of("aaa", "

aaa

"), + Arguments.of("bread & butter", "bread & butter")); + } + @BeforeEach public void setUp() { formatter = new HtmlToUnicodeFormatter(); } - @Test - public void formatWithoutHtmlCharactersReturnsSameString() { - assertEquals("abc", formatter.format("abc")); - } - - @Test - public void formatMultipleHtmlCharacters() { - assertEquals("åäö", formatter.format("åäö")); - } - - @Test - public void formatCombinedAccent() { - assertEquals("í", formatter.format("í")); - } - - @Test - public void testBasic() { - assertEquals("aaa", formatter.format("aaa")); - } - - @Test - public void testUmlauts() { - assertEquals("ä", formatter.format("ä")); - assertEquals("ä", formatter.format("ä")); - assertEquals("ä", formatter.format("ä")); - } - - @Test - public void testGreekLetter() { - assertEquals("Ε", formatter.format("Ε")); - } - - @Test - public void testHTMLRemoveTags() { - assertEquals("aaa", formatter.format("

aaa

")); - } - - @Test - public void formatExample() { - assertEquals("bread & butter", formatter.format(formatter.getExampleInput())); + @ParameterizedTest + @MethodSource("data") + void testFormatterWorksCorrectly(String expected, String input) { + assertEquals(expected, formatter.format(input)); } } - - diff --git a/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java b/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java index 7c765d283f9..1ec4e02879c 100644 --- a/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java +++ b/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java @@ -25,7 +25,8 @@ private static Stream data() { Arguments.of("de la Vallée Poussin, Jean Charles Gabriel", new Author("Jean Charles Gabriel", "J. C. G.", "de la", "Vallée Poussin", null)), Arguments.of("de la Vallée Poussin, J. C. G.", new Author("J. C. G.", "J. C. G.", "de la", "Vallée Poussin", null)), Arguments.of("{K}ent-{B}oswell, E. S.", new Author("E. S.", "E. S.", null, "{K}ent-{B}oswell", null)), - Arguments.of("Uhlenhaut, N Henriette", new Author("N Henriette", "N. H.", null, "Uhlenhaut", null)) + Arguments.of("Uhlenhaut, N Henriette", new Author("N Henriette", "N. H.", null, "Uhlenhaut", null)), + Arguments.of("Nu{\\~{n}}ez, Jose", new Author("Jose", "J.", null, "Nu{\\~{n}}ez", null)) ); } diff --git a/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java b/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java index e6ed2c2b743..b64900e6122 100644 --- a/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java +++ b/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java @@ -29,48 +29,35 @@ public void setUp() throws Exception { checkerb = new PersonNamesChecker(database); } - @Test - public void validNameFirstnameAuthor() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("Kolb, Stefan")); + @ParameterizedTest + @MethodSource("provideValidNames") + public void validNames(String name) { + assertEquals(Optional.empty(), checker.checkValue(name)); } - @Test - public void validNameFirstnameAuthors() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("Kolb, Stefan and Harrer, Simon")); - } + private static Stream provideValidNames() { + return Stream.of( + "Kolb, Stefan", // single [Name, Firstname] + "Kolb, Stefan and Harrer, Simon", // multiple [Name, Firstname] + "Stefan Kolb", // single [Firstname Name] + "Stefan Kolb and Simon Harrer", // multiple [Firstname Name] - @Test - public void validFirstnameNameAuthor() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("Stefan Kolb")); - } + "M. J. Gotay", // second name in front - @Test - public void validFirstnameNameAuthors() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("Stefan Kolb and Simon Harrer")); + "{JabRef}", // corporate name in brackets + "{JabRef} and Stefan Kolb", // mixed corporate name with name + "{JabRef} and Kolb, Stefan", + + "hugo Para{\\~n}os" // tilde in name + ); } @Test - public void complainAboutPersonStringWithTwoManyCommas() throws Exception { + public void complainAboutPersonStringWithTwoManyCommas() { assertEquals(Optional.of("Names are not in the standard BibTeX format."), checker.checkValue("Test1, Test2, Test3, Test4, Test5, Test6")); } - @Test - public void doNotComplainAboutSecondNameInFront() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("M. J. Gotay")); - } - - @Test - public void validCorporateNameInBrackets() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("{JabRef}")); - } - - @Test - public void validCorporateNameAndPerson() throws Exception { - assertEquals(Optional.empty(), checker.checkValue("{JabRef} and Stefan Kolb")); - assertEquals(Optional.empty(), checker.checkValue("{JabRef} and Kolb, Stefan")); - } - @ParameterizedTest @MethodSource("provideCorrectFormats") public void authorNameInCorrectFormatsShouldNotComplain(String input) { @@ -84,13 +71,19 @@ public void authorNameInIncorrectFormatsShouldComplain(String input) { } private static Stream provideCorrectFormats() { - return Stream.of("", "Knuth", "Donald E. Knuth and Kurt Cobain and A. Einstein"); + return Stream.of( + "", + "Knuth", + "Donald E. Knuth and Kurt Cobain and A. Einstein"); } private static Stream provideIncorrectFormats() { - return Stream.of(" Knuth, Donald E. ", - "Knuth, Donald E. and Kurt Cobain and A. Einstein", - ", and Kurt Cobain and A. Einstein", "Donald E. Knuth and Kurt Cobain and ,", - "and Kurt Cobain and A. Einstein", "Donald E. Knuth and Kurt Cobain and"); + return Stream.of( + " Knuth, Donald E. ", + "Knuth, Donald E. and Kurt Cobain and A. Einstein", + ", and Kurt Cobain and A. Einstein", + "Donald E. Knuth and Kurt Cobain and ,", + "and Kurt Cobain and A. Einstein", + "Donald E. Knuth and Kurt Cobain and"); } } diff --git a/src/test/resources/org/jabref/logic/crawler/study-jabref-5.7.yml b/src/test/resources/org/jabref/logic/crawler/study-jabref-5.7.yml new file mode 100644 index 00000000000..3b5b45bbb66 --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/study-jabref-5.7.yml @@ -0,0 +1,17 @@ +authors: + - Jab Ref +title: TestStudyName +last-search-date: 2020-11-26 +research-questions: + - Question1 + - Question2 +queries: + - query: Quantum + - query: Cloud Computing + - query: '"Software Engineering"' +databases: + - name: Springer + - name: ArXiv + - name: Medline/PubMed + - name: IEEEXplore + enabled: false