diff --git a/CHANGELOG.md b/CHANGELOG.md index ed836945ff6..22db2799aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where Document Viewer showed technical exceptions when opening entries with non-PDF files. [#13198](https://github.com/JabRef/jabref/issues/13198) - When creating a library, if you drag a PDF file containing only a single column, the dialog will now automatically close. [#13262](https://github.com/JabRef/jabref/issues/13262) - We fixed an issue where the tab showing the fulltext search results would appear blank after switching library. [#13241](https://github.com/JabRef/jabref/issues/13241) +- Enhanced field selection logic in the Merge Entries dialog when fetching from DOI to prefer valid years and entry types. [#12549](https://github.com/JabRef/jabref/issues/12549) ### Removed diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index 638b1406645..ad7984f3810 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -107,6 +107,7 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF dialog.setTitle(Localization.lang("Merge entry with %0 information", fetcher.getName())); dialog.setLeftHeaderText(Localization.lang("Original entry")); dialog.setRightHeaderText(Localization.lang("Entry from %0", fetcher.getName())); + dialog.autoSelectBetterFields(); Optional mergedEntry = dialogService.showCustomDialogAndWait(dialog).map(EntriesMergeResult::mergedEntry); if (mergedEntry.isPresent()) { diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java index dc485757be0..8cc8fab556d 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java @@ -64,4 +64,8 @@ public void setRightHeaderText(String rightHeaderText) { public void configureDiff(ShowDiffConfig diffConfig) { threeWayMergeView.showDiff(diffConfig); } + + public void autoSelectBetterFields() { + threeWayMergeView.autoSelectBetterFields(); + } } diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java index db760b107c8..0d3c0f4af73 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java @@ -208,6 +208,10 @@ public boolean hasEqualLeftAndRightValues() { return viewModel.hasEqualLeftAndRightValues(); } + public void autoSelectBetterValue() { + viewModel.autoSelectBetterValue(); + } + @Override public String toString() { return "FieldRowView [shouldShowDiffs=" + shouldShowDiffs.get() + ", fieldNameCell=" + fieldNameCell + ", leftValueCell=" + leftValueCell + ", rightValueCell=" + rightValueCell + ", mergedValueCell=" + mergedValueCell + "]"; diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java index 46886d9bee1..c3a028a4fa1 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java @@ -16,10 +16,14 @@ import org.jabref.gui.mergeentries.newmergedialog.fieldsmerger.FieldMerger; import org.jabref.gui.mergeentries.newmergedialog.fieldsmerger.FieldMergerFactory; +import org.jabref.logic.bibtex.comparator.ComparisonResult; +import org.jabref.logic.bibtex.comparator.YearFieldValuePlausibilityComparator; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.InternalField; +import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryTypeFactory; +import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.strings.StringUtil; import com.tobiasdiez.easybind.EasyBind; @@ -120,6 +124,25 @@ public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, B EasyBind.subscribe(hasEqualLeftAndRightBinding(), this::setIsFieldsMerged); } + public void autoSelectBetterValue() { + String leftValue = getLeftFieldValue(); + String rightValue = getRightFieldValue(); + + if (StandardField.YEAR == field) { + YearFieldValuePlausibilityComparator comparator = new YearFieldValuePlausibilityComparator(); + ComparisonResult comparison = comparator.compare(leftValue, rightValue); + if (ComparisonResult.RIGHT_BETTER == comparison) { + selectRightValue(); + } else if (ComparisonResult.LEFT_BETTER == comparison) { + selectLeftValue(); + } + } else if (InternalField.TYPE_HEADER == field) { + if (leftValue.equalsIgnoreCase(StandardEntryType.Misc.getName())) { + selectRightValue(); + } + } + } + public void selectNonEmptyValue() { if (StringUtil.isNullOrEmpty(leftFieldValue.get())) { selectRightValue(); diff --git a/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java b/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java index e70d8ff8807..a6aef1b954f 100644 --- a/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java +++ b/jabgui/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java @@ -200,4 +200,10 @@ public BibEntry getRightEntry() { public void saveConfiguration() { toolbar.saveToolbarConfiguration(); } + + public void autoSelectBetterFields() { + for (FieldRowView row : fieldRows) { + row.autoSelectBetterValue(); + } + } } diff --git a/jabgui/src/test/java/org/jabref/gui/mergeentries/FieldRowViewModelTest.java b/jabgui/src/test/java/org/jabref/gui/mergeentries/FieldRowViewModelTest.java index a50cec8e7c7..da51cd702c7 100644 --- a/jabgui/src/test/java/org/jabref/gui/mergeentries/FieldRowViewModelTest.java +++ b/jabgui/src/test/java/org/jabref/gui/mergeentries/FieldRowViewModelTest.java @@ -214,6 +214,24 @@ void unmergeFieldsShouldDoNothingIfFieldsAreNotMerged() { assertEquals(oldRightGroups, groupsField.getRightFieldValue()); } + @Test + void newYearShouldBeSelectedForYearsWithLargeValueGap() { + BibEntry leftEntry = new BibEntry().withField(StandardField.YEAR, "1990"); + BibEntry rightEntry = new BibEntry().withField(StandardField.YEAR, "2020"); + FieldRowViewModel yearField = new FieldRowViewModel(StandardField.YEAR, leftEntry, rightEntry, mergedEntry, fieldMergerFactory); + yearField.autoSelectBetterValue(); + assertEquals(FieldRowViewModel.Selection.RIGHT, yearField.getSelection()); + } + + @Test + void yearInRangeShouldBeSelected() { + BibEntry leftEntry = new BibEntry().withField(StandardField.YEAR, "1700"); + BibEntry rightEntry = new BibEntry().withField(StandardField.YEAR, "2000"); + FieldRowViewModel yearField = new FieldRowViewModel(StandardField.YEAR, leftEntry, rightEntry, mergedEntry, fieldMergerFactory); + yearField.autoSelectBetterValue(); + assertEquals(FieldRowViewModel.Selection.RIGHT, yearField.getSelection()); + } + public FieldRowViewModel createViewModelForField(Field field) { return new FieldRowViewModel(field, leftEntry, rightEntry, mergedEntry, fieldMergerFactory); } diff --git a/jablib/src/main/java/module-info.java b/jablib/src/main/java/module-info.java index 069a32f3e9e..07bd3b6884e 100644 --- a/jablib/src/main/java/module-info.java +++ b/jablib/src/main/java/module-info.java @@ -249,5 +249,6 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; + requires org.jetbrains.annotations; // endregion } diff --git a/jablib/src/main/java/org/jabref/logic/bibtex/comparator/ComparisonResult.java b/jablib/src/main/java/org/jabref/logic/bibtex/comparator/ComparisonResult.java new file mode 100644 index 00000000000..1f2b02629bd --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/bibtex/comparator/ComparisonResult.java @@ -0,0 +1,7 @@ +package org.jabref.logic.bibtex.comparator; + +public enum ComparisonResult { + LEFT_BETTER, + RIGHT_BETTER, + UNDETERMINED +} diff --git a/jablib/src/main/java/org/jabref/logic/bibtex/comparator/FieldValuePlausibilityComparator.java b/jablib/src/main/java/org/jabref/logic/bibtex/comparator/FieldValuePlausibilityComparator.java new file mode 100644 index 00000000000..0ddca78d10f --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/bibtex/comparator/FieldValuePlausibilityComparator.java @@ -0,0 +1,12 @@ +package org.jabref.logic.bibtex.comparator; + +public abstract class FieldValuePlausibilityComparator { + /** + * Compares the plausibility of two field values. + * + * @param leftValue value from the library (or candidate) + * @param rightValue value from the fetcher (or existing record) + * @return ComparisonResult indicating which field is more plausible: RIGHT_BETTER, LEFT_BETTER, or UNDETERMINED + */ + public abstract ComparisonResult compare(String leftValue, String rightValue); +} diff --git a/jablib/src/main/java/org/jabref/logic/bibtex/comparator/YearFieldValuePlausibilityComparator.java b/jablib/src/main/java/org/jabref/logic/bibtex/comparator/YearFieldValuePlausibilityComparator.java new file mode 100644 index 00000000000..5a9b48195ce --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/bibtex/comparator/YearFieldValuePlausibilityComparator.java @@ -0,0 +1,62 @@ +package org.jabref.logic.bibtex.comparator; + +import java.time.Year; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.logic.integrity.YearChecker; + +public class YearFieldValuePlausibilityComparator extends FieldValuePlausibilityComparator { + + private static final Pattern YEAR_PATTERN = Pattern.compile("(\\d{4})"); + + /** + * Compares the plausibility of two field values. + * + * @param leftValue value from the library (or candidate) + * @param rightValue value from the fetcher (or existing record) + * @return ComparisonResult indicating which year is more plausible: RIGHT_BETTER, LEFT_BETTER, or UNDETERMINED + */ + + @Override + public ComparisonResult compare(String leftValue, String rightValue) { + YearChecker checker = new YearChecker(); + + boolean leftValid = checker.checkValue(leftValue).isEmpty(); + + if (leftValid) { + Optional leftYear = extractYear(leftValue); + Optional rightYear = extractYear(rightValue); + + boolean leftYearInRange = (leftYear.get() >= 1800) && (leftYear.get() <= Year.now().getValue() + 2); + + if (leftYearInRange) { + int diff = Math.abs(leftYear.get() - rightYear.get()); + if (diff > 10) { + return rightYear.get() > leftYear.get() + ? ComparisonResult.RIGHT_BETTER + : ComparisonResult.LEFT_BETTER; + } + return ComparisonResult.UNDETERMINED; // years are close, undetermined + } + return ComparisonResult.RIGHT_BETTER; + } + return ComparisonResult.RIGHT_BETTER; + } + + /** + * Extracts the first 4-digit number found in the string. + * Used to identify year-like values such as "About 2000" or "Published in 1999". + * + * @param value the input string possibly containing a year + * @return Optional containing the 4-digit year if found, otherwise Optional.empty() + */ + private Optional extractYear(String value) { + Matcher matcher = YEAR_PATTERN.matcher(value); + if (matcher.find()) { + return Optional.of(Integer.parseInt(matcher.group(1))); + } + return Optional.empty(); + } +}