Skip to content

Commit 7823af5

Browse files
TheYorouzoyakoppor
andauthored
Add support for automatic ICORE conference ranking lookup [#13476] (#13699)
* Port DuplicateCheck.similarity to StringSimilarity and add tests * Add ICORE2023 Rank Data To Resources Data sourced from ICORE website here: https://portal.core.edu.au/conf-ranks/ to enable ICORE rank lookups. As discussed here: #13699 (comment), only the latest data from ICORE is to be used. At this time, it is the ICORE2023 ranking data. Part of #13476 * Add Core Classes For ICORE Rank Lookup - Append a header row to resources/icore/ICORE2023.csv - Add ConferenceEntry record to represent ICORE conference data - Add ConferenceRepository to load conference data and allow conference lookups using an acronym or a bookTitle with fuzzy match as a fallback - Add utility class to extract an acronym from a bookTitle - Add tests Part of #13476 * Integrate ICORE Rank Lookup Feature Into The GUI - Add ICORERankingEditor and ICORERankingEditorViewModel classes (inspired by other editors and their view models) to the GUI package. - Add ICORERankingEditor.fxml for the editor's layout. - Add field creation logic to FieldEditors#getForField. - Update jablib/module-info to export the icore logic and model packages for use on the GUI side. - Add ICORE rank field to FieldFactory#getDefaultGeneralFields and update preferences migrations as per #13512 (comment). - Fix typos in ConferenceAcronymExtractorTest. - Fix order of modifiers in ConferenceEntry. - Update CHANGELOG. Part of #13476 * Add Missing Localization Keys and Fix Broken Test - Out of the 4 missing keys in a failing test, two new keys were added to JabRef_en.properties while two were edited adapted to already present ones. - The performExportForSingleEntry test in OpenOfficeDocumentCreatorTest was failing because of the missing "Icoreranking" field. Further, since the ordering of JabRef-specific fields was changed in a previous commit to conform to alphabetical order, the hardcoded values in OldOpenOfficeCalcExportFormatContentSingleEntry.xml weren't matching with the exporter's output. This commit reorders the fields in the expected order and adds the "Icoreranking" field at its right place which fixes the broken test. Part of #13476 * Use List.of() and fix grammar * Reorder fields * Fix unsupported operation exception * Rename field * Port DuplicateCheck.similarity to StringSimilarity and add tests * Add ICORE2023 Rank Data To Resources Data sourced from ICORE website here: https://portal.core.edu.au/conf-ranks/ to enable ICORE rank lookups. As discussed here: #13699 (comment), only the latest data from ICORE is to be used. At this time, it is the ICORE2023 ranking data. Part of #13476 * Add Core Classes For ICORE Rank Lookup - Append a header row to resources/icore/ICORE2023.csv - Add ConferenceEntry record to represent ICORE conference data - Add ConferenceRepository to load conference data and allow conference lookups using an acronym or a bookTitle with fuzzy match as a fallback - Add utility class to extract an acronym from a bookTitle - Add tests Part of #13476 * Fix Merge Conflict From Upstream Fetch Part of #13476 * Add Missing Localization Keys and Fix Broken Test - Out of the 4 missing keys in a failing test, two new keys were added to JabRef_en.properties while two were edited adapted to already present ones. - The performExportForSingleEntry test in OpenOfficeDocumentCreatorTest was failing because of the missing "Icoreranking" field. Further, since the ordering of JabRef-specific fields was changed in a previous commit to conform to alphabetical order, the hardcoded values in OldOpenOfficeCalcExportFormatContentSingleEntry.xml weren't matching with the exporter's output. This commit reorders the fields in the expected order and adds the "Icoreranking" field at its right place which fixes the broken test. Part of #13476 * Add Minor Fixes, Documentation, and Refactor - Update ICORERankingEditorViewModel to display a notification when a conference ranking isn't found instead of displaying the "not found" text in the field. - Refactored PreferencesMigrations.addICORERankingFieldToGeneralTab to better align with JabRefCliPreferences.setLanguageDependentDefaultValues. - Minor refactor and remove redundant comment and whitespace in ConferenceRepository. - Update tests to Parameterized tests in ConferenceRepositoryTest and ConferenceAcronymExtractorTest. - Removed unused variables in ICORERankingEditor and ICORERankingEditorViewModel. - Update OldOpenOfficeCalcExportFormatContentSingleEntry.xml to align with the field ordering in StandardField. - Add documentation to PreferencesMigrations.addICORERankingFieldToGeneralTab and ConferenceAcronymExtractor.extract methods. Part of #13476 * Revert "Hotfix: calling of publish.yml" This reverts commit 75d2b47 which I accidentally pulled on my feature branch. * Remove duplicate line in CHANGELOG Part of #13476 * Improve Fuzzy Search Algorithm And Fix Undo Bug - Added utility functions to ConferenceUtils for acronym candidate generation and string normalization - Updated fuzzy search algorithm in ConferenceRepository to include Longest Common Substring similarity combined with Levenshtein similarity - Fixed bug in ICORERankingEditor which was causing Undo to not work - Updated ICORERankingEditorViewModel to use the leaner ConferenceRepository API - Added LCSSimilarity method to StringSimilarity to compute Longest Common Substring similarity rating - Added Javadoc to various non-trivial methods - Add tests to cover the updated functionality Part of #13476 * Discard changes to .github/workflows/tests-code.yml * Fix Broken Tests and Malformed JavaDoc Part of #13476 * Update Test In ConferenceUtilsTest Part of #13476 * Add Minor Fixes To ConferenceUtils Part of #13476 --------- Co-authored-by: Oliver Kopp <[email protected]>
1 parent 4886f9a commit 7823af5

File tree

21 files changed

+2070
-39
lines changed

21 files changed

+2070
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
3535
- When relativizing file names, symlinks are now taken into account. [#12995](https://github.com/JabRef/jabref/issues/12995)
3636
- We added a new button for shortening the DOI near the DOI field in the general tab when viewing an entry. [#13639](https://github.com/JabRef/jabref/issues/13639)
3737
- We added support for finding CSL-Styles based on their short title (e.g. apa instead of "american psychological association"). [#13728](https://github.com/JabRef/jabref/pull/13728)
38+
- We added a field for the latest ICORE conference ranking lookup on the General Tab. [#13476](https://github.com/JabRef/jabref/issues/13476)
3839
- We added BibLaTeX datamodel validation support in order to improve error message quality in entries' fields validation. [#13318](https://github.com/JabRef/jabref/issues/13318)
3940

4041
### Changed

jabgui/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ public static FieldEditorFX getForField(final Field field,
114114
return new CitationKeyEditor(field, suggestionProvider, fieldCheckers, databaseContext, undoAction, redoAction);
115115
} else if (fieldProperties.contains(FieldProperty.MARKDOWN)) {
116116
return new MarkdownEditor(field, suggestionProvider, fieldCheckers, preferences, undoManager, undoAction, redoAction);
117+
} else if (field == StandardField.ICORERANKING) {
118+
return new ICORERankingEditor(field, suggestionProvider, fieldCheckers);
117119
} else {
118120
// There was no specific editor found
119121

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.jabref.gui.fieldeditors;
2+
3+
import java.util.Optional;
4+
5+
import javax.swing.undo.UndoManager;
6+
7+
import javafx.fxml.FXML;
8+
import javafx.scene.Parent;
9+
import javafx.scene.control.Button;
10+
import javafx.scene.control.Tooltip;
11+
import javafx.scene.layout.HBox;
12+
13+
import org.jabref.gui.DialogService;
14+
import org.jabref.gui.autocompleter.SuggestionProvider;
15+
import org.jabref.gui.preferences.GuiPreferences;
16+
import org.jabref.logic.icore.ConferenceRepository;
17+
import org.jabref.logic.integrity.FieldCheckers;
18+
import org.jabref.logic.l10n.Localization;
19+
import org.jabref.model.entry.BibEntry;
20+
import org.jabref.model.entry.field.Field;
21+
22+
import com.airhacks.afterburner.injection.Injector;
23+
import com.airhacks.afterburner.views.ViewLoader;
24+
import jakarta.inject.Inject;
25+
26+
public class ICORERankingEditor extends HBox implements FieldEditorFX {
27+
@FXML private ICORERankingEditorViewModel viewModel;
28+
@FXML private EditorTextField textField;
29+
@FXML private Button lookupICORERankButton;
30+
@FXML private Button visitICOREConferencePageButton;
31+
32+
@Inject private DialogService dialogService;
33+
@Inject private UndoManager undoManager;
34+
@Inject private GuiPreferences preferences;
35+
@Inject private ConferenceRepository conferenceRepository;
36+
37+
private Optional<BibEntry> entry = Optional.empty();
38+
39+
public ICORERankingEditor(Field field,
40+
SuggestionProvider<?> suggestionProvider,
41+
FieldCheckers fieldCheckers) {
42+
43+
Injector.registerExistingAndInject(this);
44+
45+
ViewLoader.view(this)
46+
.root(this)
47+
.load();
48+
49+
this.viewModel = new ICORERankingEditorViewModel(
50+
field,
51+
suggestionProvider,
52+
fieldCheckers,
53+
dialogService,
54+
undoManager,
55+
preferences,
56+
conferenceRepository
57+
);
58+
59+
textField.textProperty().bindBidirectional(viewModel.textProperty());
60+
61+
lookupICORERankButton.setTooltip(
62+
new Tooltip(Localization.lang("Look up conference rank"))
63+
);
64+
visitICOREConferencePageButton.setTooltip(
65+
new Tooltip(Localization.lang("Visit ICORE conference page"))
66+
);
67+
visitICOREConferencePageButton.disableProperty().bind(textField.textProperty().isEmpty());
68+
}
69+
70+
@Override
71+
public void bindToEntry(BibEntry entry) {
72+
this.entry = Optional.of(entry);
73+
viewModel.bindToEntry(entry);
74+
}
75+
76+
@Override
77+
public Parent getNode() {
78+
return this;
79+
}
80+
81+
@FXML
82+
private void lookupRank() {
83+
entry.ifPresent(viewModel::lookupIdentifier);
84+
}
85+
86+
@FXML
87+
private void openExternalLink() {
88+
viewModel.openExternalLink();
89+
}
90+
}
91+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package org.jabref.gui.fieldeditors;
2+
3+
import java.io.IOException;
4+
import java.util.Optional;
5+
6+
import javax.swing.undo.UndoManager;
7+
8+
import org.jabref.gui.DialogService;
9+
import org.jabref.gui.autocompleter.SuggestionProvider;
10+
import org.jabref.gui.desktop.os.NativeDesktop;
11+
import org.jabref.gui.preferences.GuiPreferences;
12+
import org.jabref.logic.icore.ConferenceRepository;
13+
import org.jabref.logic.integrity.FieldCheckers;
14+
import org.jabref.logic.l10n.Localization;
15+
import org.jabref.model.entry.BibEntry;
16+
import org.jabref.model.entry.field.Field;
17+
import org.jabref.model.entry.field.StandardField;
18+
import org.jabref.model.icore.ConferenceEntry;
19+
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
public class ICORERankingEditorViewModel extends AbstractEditorViewModel {
24+
private static final Logger LOGGER = LoggerFactory.getLogger(ICORERankingEditorViewModel.class);
25+
26+
private final DialogService dialogService;
27+
private final GuiPreferences preferences;
28+
private final ConferenceRepository repo;
29+
30+
private ConferenceEntry matchedConference;
31+
32+
public ICORERankingEditorViewModel(
33+
Field field,
34+
SuggestionProvider<?> suggestionProvider,
35+
FieldCheckers fieldCheckers,
36+
DialogService dialogService,
37+
UndoManager undoManager,
38+
GuiPreferences preferences,
39+
ConferenceRepository conferenceRepository
40+
) {
41+
super(field, suggestionProvider, fieldCheckers, undoManager);
42+
this.dialogService = dialogService;
43+
this.preferences = preferences;
44+
this.repo = conferenceRepository;
45+
}
46+
47+
public void lookupIdentifier(BibEntry bibEntry) {
48+
Optional<String> bookTitle = bibEntry.getFieldOrAlias(StandardField.BOOKTITLE);
49+
50+
if (bookTitle.isEmpty()) {
51+
bookTitle = bibEntry.getFieldOrAlias(StandardField.JOURNAL);
52+
}
53+
54+
if (bookTitle.isEmpty()) {
55+
return;
56+
}
57+
58+
Optional<ConferenceEntry> conference = repo.getConferenceFromBookTitle(bookTitle.get());
59+
if (conference.isPresent()) {
60+
entry.setField(field, conference.get().rank());
61+
matchedConference = conference.get();
62+
} else {
63+
dialogService.notify(Localization.lang("not found"));
64+
}
65+
}
66+
67+
public void openExternalLink() {
68+
if (matchedConference != null) {
69+
try {
70+
NativeDesktop.openBrowser(matchedConference.getICOREURL(), preferences.getExternalApplicationsPreferences());
71+
} catch (IOException e) {
72+
LOGGER.error("Error opening external link in browser", e);
73+
dialogService.showErrorDialogAndWait(Localization.lang("Could not open website."), e);
74+
}
75+
}
76+
}
77+
}

jabgui/src/main/java/org/jabref/migrations/PreferencesMigrations.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import java.util.Map;
88
import java.util.Optional;
9+
import java.util.Set;
910
import java.util.function.UnaryOperator;
1011
import java.util.prefs.BackingStoreException;
1112
import java.util.prefs.Preferences;
@@ -21,10 +22,13 @@
2122
import org.jabref.logic.citationkeypattern.GlobalCitationKeyPatterns;
2223
import org.jabref.logic.cleanup.CleanupPreferences;
2324
import org.jabref.logic.cleanup.FieldFormatterCleanups;
25+
import org.jabref.logic.l10n.Localization;
2426
import org.jabref.logic.os.OS;
2527
import org.jabref.logic.preferences.JabRefCliPreferences;
2628
import org.jabref.logic.shared.security.Password;
2729
import org.jabref.model.entry.BibEntryTypesManager;
30+
import org.jabref.model.entry.field.Field;
31+
import org.jabref.model.entry.field.FieldFactory;
2832
import org.jabref.model.entry.field.SpecialField;
2933
import org.jabref.model.entry.field.StandardField;
3034
import org.jabref.model.entry.types.EntryTypeFactory;
@@ -65,6 +69,7 @@ public static void runMigrations(JabRefGuiPreferences preferences) {
6569
upgradeCleanups(preferences);
6670
moveApiKeysToKeyring(preferences);
6771
removeCommentsFromCustomEditorTabs(preferences);
72+
addICORERankingFieldToGeneralTab(preferences);
6873
upgradeResolveBibTeXStringsFields(preferences);
6974
}
7075

@@ -558,6 +563,41 @@ static void moveApiKeysToKeyring(JabRefCliPreferences preferences) {
558563
}
559564
}
560565

566+
/**
567+
* Updates the default preferences for the editor fields under the "General" tab to include the ICORE Ranking Field
568+
* if it is missing.
569+
* <p>
570+
* The function first ensures that the current preferences match the previous default (before the ICORE field was added)
571+
* and only then does the update.
572+
* </p>
573+
*
574+
* @implNote The default fields for the "General" tab are defined by {@link FieldFactory#getDefaultGeneralFields()}.
575+
* @param preferences the user's current preferences
576+
*/
577+
static void addICORERankingFieldToGeneralTab(GuiPreferences preferences) {
578+
Map<String, Set<Field>> entryEditorPrefs = preferences.getEntryEditorPreferences().getEntryEditorTabs();
579+
Set<Field> currentGeneralPrefs = entryEditorPrefs.get(Localization.lang("General"));
580+
581+
Set<Field> expectedGeneralPrefs = Set.of(
582+
StandardField.DOI, StandardField.CROSSREF, StandardField.KEYWORDS, StandardField.EPRINT,
583+
StandardField.URL, StandardField.FILE, StandardField.GROUPS, StandardField.OWNER,
584+
StandardField.TIMESTAMP,
585+
586+
SpecialField.PRINTED, SpecialField.PRIORITY, SpecialField.QUALITY, SpecialField.RANKING,
587+
SpecialField.READ_STATUS, SpecialField.RELEVANCE
588+
);
589+
590+
if (!currentGeneralPrefs.equals(expectedGeneralPrefs)) {
591+
return;
592+
}
593+
594+
entryEditorPrefs.put(
595+
Localization.lang("General"),
596+
FieldFactory.getDefaultGeneralFields().stream().collect(Collectors.toSet())
597+
);
598+
preferences.getEntryEditorPreferences().setEntryEditorTabList(entryEditorPrefs);
599+
}
600+
561601
/**
562602
* The tab "Comments" is hard coded using {@link CommentsTab} since v5.10 (and thus hard-wired in {@link org.jabref.gui.entryeditor.EntryEditor#createTabs()}.
563603
* Thus, the configuration ih the preferences is obsolete
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<?import javafx.scene.control.Button?>
4+
<?import javafx.scene.layout.HBox?>
5+
<?import org.jabref.gui.fieldeditors.EditorTextField?>
6+
<?import org.jabref.gui.icon.JabRefIconView?>
7+
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="HBox" xmlns="http://javafx.com/javafx/8.0.112"
8+
fx:controller="org.jabref.gui.fieldeditors.ICORERankingEditor">
9+
<EditorTextField fx:id="textField"/>
10+
<Button fx:id="lookupICORERankButton"
11+
onAction="#lookupRank"
12+
styleClass="icon-button">
13+
<graphic>
14+
<JabRefIconView glyph="LOOKUP_IDENTIFIER"/>
15+
</graphic>
16+
</Button>
17+
<Button fx:id="visitICOREConferencePageButton"
18+
onAction="#openExternalLink"
19+
styleClass="icon-button">
20+
<graphic>
21+
<JabRefIconView glyph="OPEN_LINK"/>
22+
</graphic>
23+
</Button>
24+
</fx:root>

jablib/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@
115115
exports org.jabref.logic.command;
116116
exports org.jabref.logic.git.util;
117117
exports org.jabref.logic.git.preferences;
118+
exports org.jabref.logic.icore;
119+
exports org.jabref.model.icore;
118120

119121
requires java.base;
120122

jablib/src/main/java/org/jabref/logic/database/DuplicateCheck.java

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,10 @@ public static double correlateByWords(final String s1, final String s2) {
289289
final String[] w1 = s1.split("\\s");
290290
final String[] w2 = s2.split("\\s");
291291
final int n = Math.min(w1.length, w2.length);
292+
final StringSimilarity match = new StringSimilarity();
292293
int misses = 0;
293294
for (int i = 0; i < n; i++) {
294-
double corr = similarity(w1[i], w2[i]);
295+
double corr = match.similarity(w1[i], w2[i]);
295296
if (corr < 0.75) {
296297
misses++;
297298
}
@@ -300,33 +301,6 @@ public static double correlateByWords(final String s1, final String s2) {
300301
return 1 - missRate;
301302
}
302303

303-
/**
304-
* Calculates the similarity (a number within 0 and 1) between two strings.
305-
* http://stackoverflow.com/questions/955110/similarity-string-comparison-in-java
306-
*/
307-
private static double similarity(final String first, final String second) {
308-
final String longer;
309-
final String shorter;
310-
311-
if (first.length() < second.length()) {
312-
longer = second;
313-
shorter = first;
314-
} else {
315-
longer = first;
316-
shorter = second;
317-
}
318-
319-
final int longerLength = longer.length();
320-
// both strings are zero length
321-
if (longerLength == 0) {
322-
return 1.0;
323-
}
324-
final double distanceIgnoredCase = new StringSimilarity().editDistanceIgnoreCase(longer, shorter);
325-
final double similarity = (longerLength - distanceIgnoredCase) / longerLength;
326-
LOGGER.trace("Longer string: {} Shorter string: {} Similarity: {}", longer, shorter, similarity);
327-
return similarity;
328-
}
329-
330304
/**
331305
* Checks if the two entries represent the same publication.
332306
*/

0 commit comments

Comments
 (0)