diff --git a/CHANGELOG.md b/CHANGELOG.md index 558ed92ec92..e75963161bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added a GUI for bibliography consistency check. [#11950](https://github.com/JabRef/jabref/issues/11950) - We added a feature for copying entries to libraries, available via the context menu, with an option to include cross-references. [#12374](https://github.com/JabRef/jabref/pull/12374) ### Changed diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 8ef696aa10c..611844e912a 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -142,6 +142,7 @@ public enum StandardActions implements Action { 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), CHECK_INTEGRITY(Localization.lang("Check integrity"), KeyBinding.CHECK_INTEGRITY), + CHECK_CONSISTENCY(Localization.lang("Check consistency"), KeyBinding.CHECK_CONSISTENCY), FIND_UNLINKED_FILES(Localization.lang("Search for unlinked local files"), IconTheme.JabRefIcons.SEARCH, KeyBinding.FIND_UNLINKED_FILES), AUTO_LINK_FILES(Localization.lang("Automatically set file links"), IconTheme.JabRefIcons.AUTO_FILE_LINK, KeyBinding.AUTOMATICALLY_LINK_FILES), LOOKUP_DOC_IDENTIFIER(Localization.lang("Search document identifier online")), diff --git a/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java new file mode 100644 index 00000000000..016a4656827 --- /dev/null +++ b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckAction.java @@ -0,0 +1,66 @@ +package org.jabref.gui.consistency; + +import java.util.List; +import java.util.function.Supplier; + +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheck; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; + +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + +public class ConsistencyCheckAction extends SimpleCommand { + + Supplier tabSupplier; + private final DialogService dialogService; + private final StateManager stateManager; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final UiTaskExecutor taskExecutor; + + public ConsistencyCheckAction(Supplier tabSupplier, + DialogService dialogService, + StateManager stateManager, + GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, + UiTaskExecutor taskExecutor) { + this.tabSupplier = tabSupplier; + this.dialogService = dialogService; + this.stateManager = stateManager; + this.preferences = preferences; + this.entryTypesManager = entryTypesManager; + this.taskExecutor = taskExecutor; + + this.executable.bind(needsDatabase(stateManager)); + } + + @Override + public void execute() { + BackgroundTask.wrap(() -> { + BibDatabaseContext databaseContext = stateManager.getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); + List entries = databaseContext.getDatabase().getEntries(); + + BibliographyConsistencyCheck consistencyCheck = new BibliographyConsistencyCheck(); + return consistencyCheck.check(entries); + }).onSuccess(result -> { + if (result.entryTypeToResultMap().isEmpty()) { + dialogService.notify(Localization.lang("No problems found.")); + } else { + dialogService.showCustomDialogAndWait( + new ConsistencyCheckDialog(tabSupplier.get(), dialogService, preferences, entryTypesManager, result)); + } + }).onFailure(exception -> + dialogService.showErrorDialogAndWait(Localization.lang("Consistency check failed."), exception) + ).executeWith(taskExecutor); + } +} diff --git a/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.fxml b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.fxml new file mode 100644 index 00000000000..5b37f6d9964 --- /dev/null +++ b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.fxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java new file mode 100644 index 00000000000..e329b27001d --- /dev/null +++ b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialog.java @@ -0,0 +1,149 @@ +package org.jabref.gui.consistency; + +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.collections.ListChangeListener; +import javafx.collections.transformation.FilteredList; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.stage.Modality; + +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.util.BaseDialog; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheck; +import org.jabref.logic.quality.consistency.ConsistencyMessage; +import org.jabref.model.entry.BibEntryTypesManager; + +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +public class ConsistencyCheckDialog extends BaseDialog { + + @FXML private TableView tableView; + @FXML private ComboBox entryTypeCombo; + + private final LibraryTab libraryTab; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + private final BibliographyConsistencyCheck.Result result; + + private ConsistencyCheckDialogViewModel viewModel; + + public ConsistencyCheckDialog(LibraryTab libraryTab, + DialogService dialogService, + GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, + BibliographyConsistencyCheck.Result result) { + this.libraryTab = libraryTab; + this.dialogService = dialogService; + this.preferences = preferences; + this.entryTypesManager = entryTypesManager; + this.result = result; + + this.setTitle(Localization.lang("Check consistency")); + this.initModality(Modality.NONE); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + } + + private void onSelectionChanged(ListChangeListener.Change change) { + if (change.next()) { + change.getAddedSubList().stream().findFirst().ifPresent(message -> + libraryTab.showAndEdit(message.bibEntry())); + } + } + + public ConsistencyCheckDialogViewModel getViewModel() { + return viewModel; + } + + @FXML + public void initialize() { + viewModel = new ConsistencyCheckDialogViewModel(dialogService, preferences, entryTypesManager, result); + + tableView.getSelectionModel().getSelectedItems().addListener(this::onSelectionChanged); + + entryTypeCombo.getItems().addAll(viewModel.getEntryTypes()); + entryTypeCombo.valueProperty().bindBidirectional(viewModel.selectedEntryTypeProperty()); + EasyBind.listen(entryTypeCombo.getEditor().textProperty(), observable -> entryTypeCombo.commitValue()); + entryTypeCombo.getSelectionModel().selectFirst(); + + FilteredList filteredData = new FilteredList<>(viewModel.getTableData(), message -> + message.message().split("\\s+")[0].equals(viewModel.selectedEntryTypeProperty().get()) + ); + + viewModel.selectedEntryTypeProperty().addListener((obs, oldValue, newValue) -> { + filteredData.setPredicate(message -> + message.message().split("\\s+")[0].equals(newValue) + ); + }); + + tableView.setItems(filteredData); + + for (int i = 0; i < viewModel.getColumnNames().size(); i++) { + int columnIndex = i; + TableColumn tableColumn = new TableColumn<>(viewModel.getColumnNames().get(i)); + + tableColumn.setCellValueFactory(row -> { + String[] message = row.getValue().message().split("\\s+"); + return new ReadOnlyStringWrapper(message[columnIndex]); + }); + + tableColumn.setCellFactory(column -> new TableCell() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setGraphic(null); + } else { + switch (item.toLowerCase()) { + case "-": + setGraphic(IconTheme.JabRefIcons.HYPHEN.getGraphicNode()); + break; + case "x": + setGraphic(IconTheme.JabRefIcons.CROSS.getGraphicNode()); + break; + case "?": + setGraphic(IconTheme.JabRefIcons.QUESTION_MARK.getGraphicNode()); + break; + case "o": + setGraphic(IconTheme.JabRefIcons.CIRCLE.getGraphicNode()); + break; + default: + setGraphic(null); + setText(item); + } + } + } + }); + + tableView.getColumns().add(tableColumn); + } + } + + @FXML + private void exportAsCsv() { + viewModel.startExportAsCsv(); + } + + @FXML + private void exportAsTxt() { + viewModel.startExportAsTxt(); + } + + @FXML + private void showInfo() { + ConsistencySymbolsDialog consistencySymbolsDialog = new ConsistencySymbolsDialog(); + dialogService.showCustomDialog(consistencySymbolsDialog); + } +} diff --git a/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java new file mode 100644 index 00000000000..78c97f09f18 --- /dev/null +++ b/src/main/java/org/jabref/gui/consistency/ConsistencyCheckDialogViewModel.java @@ -0,0 +1,207 @@ +package org.jabref.gui.consistency; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.SequencedCollection; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.DialogService; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheck; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheckResultCsvWriter; +import org.jabref.logic.quality.consistency.BibliographyConsistencyCheckResultTxtWriter; +import org.jabref.logic.quality.consistency.ConsistencyMessage; +import org.jabref.logic.util.StandardFileType; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.BibField; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.types.EntryType; + +import org.jooq.lambda.Unchecked; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConsistencyCheckDialogViewModel extends AbstractViewModel { + + protected static final String REQUIRED_FIELD_AT_ENTRY_TYPE_CELL_ENTRY = "x"; + protected static final String OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY = "o"; + protected static final String UNKNOWN_FIELD_AT_ENTRY_TYPE_CELL_ENTRY = "?"; + protected static final String UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY = "-"; + + private final Logger LOGGER = LoggerFactory.getLogger(ConsistencyCheckDialogViewModel.class); + + private final BibliographyConsistencyCheck.Result result; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final BibEntryTypesManager entryTypesManager; + + private final List allReportedFields; + private final int columnCount; + private final ObservableList tableData = FXCollections.observableArrayList(); + private final StringProperty selectedEntryType = new SimpleStringProperty(); + + public ConsistencyCheckDialogViewModel(DialogService dialogService, + GuiPreferences preferences, + BibEntryTypesManager entryTypesManager, + BibliographyConsistencyCheck.Result result) { + this.dialogService = dialogService; + this.preferences = preferences; + this.entryTypesManager = entryTypesManager; + this.result = result; + + this.allReportedFields = result.entryTypeToResultMap().values().stream() + .flatMap(entryTypeResult -> entryTypeResult.fields().stream()) + .sorted(Comparator.comparing(Field::getName)) + .distinct() + .toList(); + this.columnCount = getColumnNames().size(); + + result.entryTypeToResultMap().entrySet().stream() + .sorted(Comparator.comparing(entry -> entry.getKey().getName())) + .forEach(Unchecked.consumer(mapEntry -> { + writeMapEntry(mapEntry); + })); + } + + public StringProperty selectedEntryTypeProperty() { + return selectedEntryType; + } + + public List getEntryTypes() { + List entryTypes = new ArrayList<>(); + result.entryTypeToResultMap().forEach((entrySet, entryTypeResult) -> { + entryTypes.add(entrySet.toString()); + }); + return entryTypes; + } + + public ObservableList getTableData() { + return tableData; + } + + public List getColumnNames() { + List result = new ArrayList(columnCount + 2); + result.add("Entry Type"); + result.add("Citation Key"); + allReportedFields.forEach(field -> { + result.add(field.getDisplayName()); + }); + return result; + } + + private void writeMapEntry(Map.Entry mapEntry) { + BibDatabaseMode bibDatabaseMode = BibDatabaseMode.BIBTEX; + String entryType = mapEntry.getKey().getDisplayName(); + + Optional bibEntryType = this.entryTypesManager.enrich(mapEntry.getKey(), bibDatabaseMode); + Set requiredFields = bibEntryType + .map(BibEntryType::getRequiredFields) + .stream() + .flatMap(orFieldsCollection -> orFieldsCollection.stream()) + .flatMap(orFields -> orFields.getFields().stream()) + .collect(Collectors.toSet()); + Set optionalFields = bibEntryType + .map(BibEntryType::getOptionalFields) + .stream() + .flatMap(bibFieldSet -> bibFieldSet.stream()) + .map(BibField::field) + .collect(Collectors.toSet()); + + BibliographyConsistencyCheck.EntryTypeResult entries = mapEntry.getValue(); + SequencedCollection bibEntries = entries.sortedEntries(); + + bibEntries.forEach(Unchecked.consumer(bibEntry -> { + writeBibEntry(bibEntry, entryType, requiredFields, optionalFields); + })); + } + + private void writeBibEntry(BibEntry bibEntry, String entryType, Set requiredFields, Set optionalFields) throws IOException { + List theRecord = getFindingsAsList(bibEntry, entryType, requiredFields, optionalFields); + StringBuilder sb = new StringBuilder(); + for (String s: theRecord) { + String modifiedString = s.replaceAll("\\s+", " "); + sb.append(modifiedString).append(" "); + } + tableData.add(new ConsistencyMessage(sb.toString(), bibEntry)); + } + + private List getFindingsAsList(BibEntry bibEntry, String entryType, Set requiredFields, Set optionalFields) { + List result = new ArrayList(columnCount + 2); + result.add(entryType); + result.add(bibEntry.getCitationKey().orElse("")); + allReportedFields.forEach(field -> { + result.add(bibEntry.getField(field).map(value -> { + if (requiredFields.contains(field)) { + return REQUIRED_FIELD_AT_ENTRY_TYPE_CELL_ENTRY; + } else if (optionalFields.contains(field)) { + return OPTIONAL_FIELD_AT_ENTRY_TYPE_CELL_ENTRY; + } else { + return UNKNOWN_FIELD_AT_ENTRY_TYPE_CELL_ENTRY; + } + }).orElse(UNSET_FIELD_AT_ENTRY_TYPE_CELL_ENTRY)); + }); + return result; + } + + protected void startExportAsTxt() { + FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .addExtensionFilter(StandardFileType.TXT) + .withDefaultExtension(StandardFileType.TXT) + .build(); + Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); + + if (exportPath.isEmpty()) { + return; + } + + try (Writer writer = new OutputStreamWriter(Files.newOutputStream(exportPath.get()))) { + BibliographyConsistencyCheckResultTxtWriter bibliographyConsistencyCheckResultTxtWriter = new BibliographyConsistencyCheckResultTxtWriter(result, writer); + bibliographyConsistencyCheckResultTxtWriter.writeFindings(); + } catch (IOException e) { + LOGGER.error(Localization.lang("Problem when exporting file"), e); + dialogService.showErrorDialogAndWait(Localization.lang("Failed to export file!")); + } + } + + protected void startExportAsCsv() { + FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() + .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) + .addExtensionFilter(StandardFileType.CSV) + .withDefaultExtension(StandardFileType.CSV) + .build(); + Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); + + if (exportPath.isEmpty()) { + return; + } + + try (Writer writer = new OutputStreamWriter(Files.newOutputStream(exportPath.get()))) { + BibliographyConsistencyCheckResultCsvWriter bibliographyConsistencyCheckResultCsvWriter = new BibliographyConsistencyCheckResultCsvWriter(result, writer); + bibliographyConsistencyCheckResultCsvWriter.writeFindings(); + } catch (IOException e) { + LOGGER.error(Localization.lang("Problem when exporting file"), e); + dialogService.showErrorDialogAndWait(Localization.lang("Failed to export file!")); + } + } +} diff --git a/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.fxml b/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.fxml new file mode 100644 index 00000000000..3e3b2f64880 --- /dev/null +++ b/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.fxml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java b/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java new file mode 100644 index 00000000000..f9216c62542 --- /dev/null +++ b/src/main/java/org/jabref/gui/consistency/ConsistencySymbolsDialog.java @@ -0,0 +1,21 @@ +package org.jabref.gui.consistency; + +import javafx.stage.Modality; + +import org.jabref.gui.util.BaseDialog; +import org.jabref.logic.l10n.Localization; + +import com.airhacks.afterburner.views.ViewLoader; + +public class ConsistencySymbolsDialog extends BaseDialog { + + public ConsistencySymbolsDialog() { + this.setTitle(Localization.lang("Symbols Information")); + this.initModality(Modality.NONE); + this.setResizable(false); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + } +} diff --git a/src/main/java/org/jabref/gui/frame/MainMenu.java b/src/main/java/org/jabref/gui/frame/MainMenu.java index 2782eb6d5fc..a320bdd6348 100644 --- a/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -17,6 +17,7 @@ import org.jabref.gui.auximport.NewSubLibraryAction; import org.jabref.gui.citationkeypattern.GenerateCitationKeyAction; import org.jabref.gui.cleanup.CleanupAction; +import org.jabref.gui.consistency.ConsistencyCheckAction; import org.jabref.gui.copyfiles.CopyFilesAction; import org.jabref.gui.documentviewer.ShowDocumentViewerAction; import org.jabref.gui.duplicationFinder.DuplicateSearch; @@ -250,6 +251,7 @@ private void createMenu() { factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, entryTypesManager, taskExecutor)), factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)), factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, (UiTaskExecutor) taskExecutor, abbreviationRepository)), + factory.createMenuItem(StandardActions.CHECK_CONSISTENCY, new ConsistencyCheckAction(frame::getCurrentLibraryTab, dialogService, stateManager, preferences, entryTypesManager, (UiTaskExecutor) taskExecutor)), factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, new CleanupAction(frame::getCurrentLibraryTab, preferences, dialogService, stateManager, taskExecutor, undoManager)), new SeparatorMenuItem(), diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index 3788b551b18..e2660d351b5 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -360,7 +360,11 @@ public enum JabRefIcons implements JabRefIcon { ACCEPT_LEFT(MaterialDesignS.SUBDIRECTORY_ARROW_LEFT), ACCEPT_RIGHT(MaterialDesignS.SUBDIRECTORY_ARROW_RIGHT), MERGE_GROUPS(MaterialDesignS.SOURCE_MERGE), - ADD_OR_MAKE_BIBLIOGRAPHY(JabRefMaterialDesignIcon.BIBLIOGRAPHY); + ADD_OR_MAKE_BIBLIOGRAPHY(JabRefMaterialDesignIcon.BIBLIOGRAPHY), + HYPHEN(MaterialDesignM.MINUS), + CROSS(MaterialDesignC.CLOSE), + CIRCLE(MaterialDesignC.CIRCLE_OUTLINE), + QUESTION_MARK(MaterialDesignH.HELP); private final JabRefIcon icon; diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index d0cb6100338..eceab35ec86 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -30,6 +30,7 @@ public enum KeyBinding { ACCEPT("Accept", Localization.lang("Accept"), "ctrl+ENTER", KeyBindingCategory.EDIT), AUTOMATICALLY_LINK_FILES("Automatically link files", Localization.lang("Automatically set file links"), "F7", KeyBindingCategory.QUALITY), CHECK_INTEGRITY("Check integrity", Localization.lang("Check integrity"), "ctrl+F8", KeyBindingCategory.QUALITY), + CHECK_CONSISTENCY("Check consistency", Localization.lang("Check consistency"), "ctrl+F9", KeyBindingCategory.QUALITY), CLEANUP("Cleanup", Localization.lang("Cleanup entries"), "alt+F8", KeyBindingCategory.QUALITY), CLOSE_DATABASE("Close library", Localization.lang("Close library"), "ctrl+W", KeyBindingCategory.FILE), CLOSE("Close dialog", Localization.lang("Close dialog"), "Esc", KeyBindingCategory.VIEW), diff --git a/src/main/java/org/jabref/logic/quality/consistency/ConsistencyMessage.java b/src/main/java/org/jabref/logic/quality/consistency/ConsistencyMessage.java new file mode 100644 index 00000000000..0e606cc9bac --- /dev/null +++ b/src/main/java/org/jabref/logic/quality/consistency/ConsistencyMessage.java @@ -0,0 +1,16 @@ +package org.jabref.logic.quality.consistency; + +import org.jabref.model.entry.BibEntry; + +public record ConsistencyMessage(String message, BibEntry bibEntry) implements Cloneable { + + @Override + public String toString() { + return "[" + message() + "]"; + } + + @Override + public Object clone() { + return new ConsistencyMessage(message, bibEntry); + } +} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 1e90419d3d8..27b116a4069 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1029,11 +1029,20 @@ Line\ %0\:\ Found\ corrupted\ citation\ key\ %1\ (comma\ missing).=Line %0: Foun Check\ integrity=Check integrity Checking\ integrity...=Checking integrity... -Field\ Presence\ Consistency\ Check\ Result=Field Presence Consistency Check Result -required\ field\ is\ present=required field is present -optional\ field\ is\ present=optional field is present -unknown\ field\ is\ present=unknown field is present -field\ is\ absent=field is absent +Check\ consistency=Check consistency +Consistency\ check\ failed.=Consistency check failed. + +Entry\ type=Entry type +Export\ as\ csv\ file=Export as csv file +Export\ as\ txt\ file=Export as txt file +Failed\ to\ export\ file!=Failed to export file! +Problem\ when\ exporting\ file=Problem when exporting file + +Symbols\ information=Symbols Information +Required\ Field=Required Field +Optional\ Field=Optional Field +Unknown\ Field=Unknown Field +Unset\ Field=Unset Field No\ full\ text\ document\ found=No full text document found Download\ from\ URL=Download from URL