Skip to content

feat: fetcher from clipboard and auto-select identifier type if valid#13111

Merged
koppor merged 21 commits into
JabRef:mainfrom
lydia-yan:fix-for-DeterminedTypeSelected
May 21, 2025
Merged

feat: fetcher from clipboard and auto-select identifier type if valid#13111
koppor merged 21 commits into
JabRef:mainfrom
lydia-yan:fix-for-DeterminedTypeSelected

Conversation

@lydia-yan

@lydia-yan lydia-yan commented May 13, 2025

Copy link
Copy Markdown
Contributor

feat: fetcher from clipboard and auto-select identifier type if valid
Update #13091 #13087

Summary of changes

This PR improves the user experience when creating a new entry by automatically detecting the identifier type (DOI, ISBN, arXiv, RFC, SSRN) from the clipboard content and selecting the appropriate fetcher in the "Enter Identifier" tab.

Changes made:

  • Clipboard content is parsed via CompositeIdFetcher.getIdentifier(...)
  • Added logic to reject suspicious identifiers (e.g., raw numbers like 123456789)
  • If a valid identifier is detected, the dialog switches to "Specify identifier type"
  • The corresponding fetcher is auto-selected (e.g., DoiFetcher, IsbnFetcher, etc.)

Unit tests verify identifier detection and validation logic.
UI switching (radio selection and fetcher assignment) was manually verified using valid/invalid identifier clipboard content.

Screenshots

Before (macOS):
Clipboard with 10.1109/MCOM.2010.5673082 → No tab switch, user must manually select type
image

After (macOS):
Clipboard with 10.1109/MCOM.2010.5673082 → Automatically switches and pre-selects to DOI
image

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license
  • Change in CHANGELOG.md described in a way that is understandable for the average user (if change is visible to the user)
  • Tests created for changes
  • Manually tested changed features in running JabRef (always required)
  • Screenshots added in PR description (if change is visible to the user)
  • Checked developer's documentation: No update needed
  • Checked documentation: No update needed

Collaborators

@lydia-yan @yoasaaa @brandon-lau0 @FlyJoanne

Comment on lines +274 to +275
// :TODO: Better validation would be nice here, so clipboard text is only copied over if it matches a
// supported identifier format.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - just move it to the isvalid section?`

General thing: The wish is that when JabRef is ready to expect user input, the currently visible text field should contain the clipboard contents and be selected. - Don't know there in the code this is exactly implemented. Currently, it seems to implemented for each tab; but it could also be implemetned after the whole initalization is run - the input text field of the selected tab should be focused.

This could be a good excercise to train Requirements Tracking - see https://devdocs.jabref.org/requirements/ - It could be placed below Focus (https://devdocs.jabref.org/requirements/focus.html) or as a seperate markdown document for the new-entry-dialog.

Comment thread jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java Outdated
Comment thread jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java Outdated
Comment thread jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java Outdated
Comment thread jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java Outdated

@Test
void detectsValidDOI() {
Optional<Identifier> result = CompositeIdFetcher.getIdentifier("10.1109/MCOM.2010.5673082");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better include the tests in org.jabref.logic.importer.fetcher.CompositeIdFetcherTest to have it more closerer to the method.

(Maybe, the whole getIdentifier should move to org.jabref.model.entry.identifier.Identifier as `from(String identifier))

@lydia-yan

Copy link
Copy Markdown
Contributor Author

Hi @koppor, I've implemented the identifier detection and fetcher matching logic in NewEntryView, and relocated the relevant unit tests to CompositeIdFetcherTest as discussed.

Regarding your suggestion to move getIdentifier() to Identifier.from(String), I wanted to clarify: do you envision this as a static factory method within the Identifier class? If so, would you prefer this method to return an Optional—to indicate possible failure—or should it return a concrete subclass such as DOI or ISBN based on the input? I'd appreciate your guidance on this before proceeding. Thank you!

@lydia-yan lydia-yan requested a review from koppor May 19, 2025 06:25
@koppor

koppor commented May 19, 2025

Copy link
Copy Markdown
Member

Hi @koppor, I've implemented the identifier detection and fetcher matching logic in NewEntryView, and relocated the relevant unit tests to CompositeIdFetcherTest as discussed.

nice!

Regarding your suggestion to move getIdentifier() to Identifier.from(String)

I need to find the comment again. Maybe, next time, please just put a link to the comment. GitHub is pretty good in deep links.. Reason: I am commenting on dozens of issues and pull requests and don't remember all details. - I did not find the comment, thus, I have to guess.

, I wanted to clarify: do you envision this as a static factory method within the Identifier class? If so, would you prefer this method to return an Optional—to indicate possible failure—or should it return a concrete subclass such as DOI or ISBN based on the input? I'd appreciate your guidance on this before proceeding. Thank you!

Just move the method from org.jabref.logic.importer.CompositeIdFetcher to org.jabref.model.entry.identifier.Identifier and rename it from getIdentifier to from. - The class already returns a concrete subclass. The java inheritance ensrues that a concrete class is returned....

@koppor

koppor commented May 19, 2025

Copy link
Copy Markdown
Member

@lydia-yan Thank you for adressing OpenFastTrace! Looks good.

for (IdBasedFetcher fetcher : idFetcher.getItems()) {
if ((id instanceof DOI && fetcher instanceof DoiFetcher) ||
// Use instanceof for structured check; fallback to name matching since some ISBN fetchers (like IsbnFetcher) don't extend AbstractIsbnFetcher.
(id instanceof ISBN && (fetcher instanceof AbstractIsbnFetcher ||

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is enough if you just do an instanceof ISBNFetcher, because it calls the AbstractISBNFetcher under the hood and will try them one after another if no entry is found.

if (isbn.isPresent()) {
bibEntry = gvkIsbnFetcher.performSearchById(isbn.get().asString());
}
} catch (FetcherException ex) {
LOGGER.debug("Got a fetcher exception for IBSN search", ex);
if (retryIsbnFetcher.isEmpty()) {
throw ex;
}
} finally {
// do not move the iterator in the loop as this would always return a new one and thus create and endless loop
Iterator<AbstractIsbnFetcher> iterator = retryIsbnFetcher.iterator();
while (bibEntry.isEmpty() && iterator.hasNext()) {
LOGGER.debug("Trying using the alternate ISBN fetchers to find an entry.");
AbstractIsbnFetcher fetcher = iterator.next();
LOGGER.debug("No entry found for ISBN {}; trying {} next.", identifier, fetcher.getName());
bibEntry = fetcher.performSearchById(identifier);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I've updated the condition to use instanceof IsbnFetcher as suggested, and exported the package in module-info.java to resolve visibility issues.

lydia-yan added 2 commits May 20, 2025 13:52
- Moved getIdentifier() from CompositeIdFetcher to Identifier as static factory method 'from(String)'
- Updated all call sites to use Identifier.from(...)
lydia-yan added 3 commits May 20, 2025 13:53
- Replaced string matching with instanceof IsbnFetcher in NewEntryView
- Exported isbn fetcher package from jablib to enable module access
@trag-bot

trag-bot Bot commented May 20, 2025

Copy link
Copy Markdown

@trag-bot didn't find any issues in the code! ✅✨

@lydia-yan

Copy link
Copy Markdown
Contributor Author

Just move the method from org.jabref.logic.importer.CompositeIdFetcher to org.jabref.model.entry.identifier.Identifier and rename it from getIdentifier to from. - The class already returns a concrete subclass. The java inheritance ensrues that a concrete class is returned....

@koppor Thanks! I've moved the method to Identifier.from(String) and updated all call sites accordingly. Let me know if you'd like any further refinements.

@lydia-yan lydia-yan requested a review from Siedlerchr May 20, 2025 21:11
Comment on lines +157 to +159
assertTrue(result.isPresent());
assertInstanceOf(ISBN.class, result.get());
assertTrue(((ISBN) result.get()).isValid());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, in this case, it is OK to "unzip" the Optional checkings.

@koppor koppor added this pull request to the merge queue May 21, 2025
Merged via the queue into JabRef:main with commit e2948d2 May 21, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants