Skip to content

Add support for loading .blg warnings in Integrity Check Dialog #12866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Apr 18, 2025

Conversation

wanling0000
Copy link
Contributor

@wanling0000 wanling0000 commented Mar 31, 2025

Closes #11998

This PR adds support for: Check integrity: Support for BibTeX .blg files


Users can now:

  • Automatically load the default .blg file from the same folder as the .bib file;
  • Select a custom .blg file via Browse and load BibTeX warnings;
  • Click Reset to revert to the default .blg path;
  • Replace old .blg warnings when switching files (i.e., only show current .blg file's messages);
  • Persist .blg file path in MetaData (saved along with .bib file).

Change Overview

Layer Component Responsibility
Model BibWarning Represent parsed .blg warning
MetaData.get/set/clearBlgFilePath() Store user-defined .blg file path
Logic BibtexLogParser Parse .blg lines into BibWarning objects
BibWarningToIntegrityMessageConverter Convert warnings to IntegrityMessage
BibLogPathResolver Resolve .blg file path: user-defined or fallback
MetaDataParser Add support for BLG_FILE_PATH in .bib metadata
MetaDataSerializer Serialize .blg file paths into metadata
ViewModel BibLogSettingsViewModel Wraps logic and exposes path + warning state to UI
UI BibLogSettingsPane.fxml Browse/Reset path selection UI
BibLogSettingsPane.java Controller handling Browse/Reset and notifying changes
IntegrityCheckDialog.java Load .blg warnings and refresh table display
IntegrityCheckAction.java Include .blg warnings during initial dialog show

Screenshots

Snipaste_2025-03-31_20-18-08


❓Limitations of .blg Parsing – I would appreciate feedback

Right now, the .blg parser in this PR (BibtexLogParser) only supports a very simple warning format:

Warning--[message] in [entryKey]

But I found that in real .blg files, there are other kinds of warnings that don’t follow this format.
For example, in this GitHub issue: James-Yu/LaTeX-Workshop#2324

Warning--string name "ccr" is undefined
--line 35 of file paper.bib

This kind of warning does not point to a specific entry or field, so currently it is ignored by the parser.

  • In this PR, I used KEY_FIELD as a fallback when the field is missing. This is just a temporary solution so the UI can still display something. But the UI currently assumes that every IntegrityMessage has a field, otherwise it may not work properly.

I’m wondering:

Would it make sense to introduce an interface like IntegrityMessage, or is it better to wait and see more .blg formats before making this change?

At the moment I don’t have many .blg test files, so I’m not sure how common these “fieldless” warnings are.
If you have any suggestions on how to investigate this, I’d be happy to look into it!

Any feedback or thoughts on this would be really helpful, thank you!


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 (if applicable)
  • 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: Is the information available and up to date? If not, I outlined it in this pull request.
  • Checked documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request to the documentation repository.

Comment on lines 1197 to 1199
public DialogService getDialogService() {
return dialogService;
}
Copy link

Choose a reason for hiding this comment

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

The new public method 'getDialogService' returns 'null' if 'dialogService' is not initialized. It should return an Optional to avoid potential NullPointerExceptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Modified to pass DialogService from the constructor parameter

Copy link
Member

Choose a reason for hiding this comment

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

Do DI by constructor. Getters for singletons are a no go.

*/
private void loadBibLogSettingsPane() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("BibLogSettingsPane.fxml"));
Copy link
Member

Choose a reason for hiding this comment

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

@calixtus Don't we use the afterburner fx stuff here?

/**
*
*/
public class BibWarning {
Copy link
Member

Choose a reason for hiding this comment

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

(quick initial comment)

Can this be a record? 😅

(and no empty JavaDoc please)

Copy link
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 converted it to a record and removed the empty JavaDoc

*/
private Optional<BibWarning> parseWarningLine(String line) {
// TODO: Support additional warning formats
Pattern compile = Pattern.compile("^Warning--([a-zA-Z ]+) in ([^\\s]+)$");
Copy link
Member

Choose a reason for hiding this comment

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

Put this into private static final. - and use named groups 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated as suggested 👍


@FXML
private void onBrowse() {
FileChooser fileChooser = new FileChooser();
Copy link
Member

Choose a reason for hiding this comment

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

You can use jabref's dialogService for this: dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(path -> ...)

and with FileDialogConfiguration offers the Builder pattern.
(see e.g NewLibraryFromPdfAction)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, already updated as suggested

return blgFilePath;
}

public void setBlgFilePath(Path path) {
Copy link

Choose a reason for hiding this comment

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

The method uses Objects.requireNonNull to check for null, which should be replaced with @nonnull annotation as per the guidelines.


List<IntegrityMessage> messages = BibWarningToIntegrityMessageConverter.convert(warnings, context);

assertEquals(1, messages.size());
Copy link

Choose a reason for hiding this comment

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

Assertion statements should not include the message parameter. The method name should already convey the expected behavior.

Copy link
Member

Choose a reason for hiding this comment

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

@trag-bot This is wrong

Comment on lines +63 to +65
metaData.getBlgFilePaths().forEach((user, path) -> {
stringyMetaData.put(MetaData.BLG_FILE_PATH + "-" + user, Collections.singletonList(path.toString().trim()));
});
Copy link

Choose a reason for hiding this comment

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

The addition of .blg file paths serialization in org.jabref.logic.exporter.MetaDataSerializer requires corresponding test updates to ensure the new functionality is covered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

already covered this change in the test method serializeUserSpecificBlgPath() ・ࡇ・

Copy link
Member

Choose a reason for hiding this comment

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

What does this - do? Path, naming separator?

Maybe this could be a constant?

Copy link
Member

Choose a reason for hiding this comment

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

I think, the other meta data fields do it, too.

I am OK without constant - follow-up can refactor the whole MetaDataParser ^^

@@ -60,6 +61,9 @@ public static Map<String, String> getSerializedStringMap(MetaData metaData,
.put(MetaData.FILE_DIRECTORY_LATEX + '-' + user, Collections.singletonList(path.toString().trim())));
metaData.getVersionDBStructure().ifPresent(
versionDBStructure -> stringyMetaData.put(MetaData.VERSION_DB_STRUCT, Collections.singletonList(versionDBStructure.trim())));
metaData.getBlgFilePaths().forEach((user, path) -> {
Copy link

Choose a reason for hiding this comment

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

The addition of serializing .blg file paths requires corresponding tests to be updated or added to ensure the new functionality is correctly implemented and verified.


import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.FileChooser;
Copy link

Choose a reason for hiding this comment

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

The import statement for FileChooser is unnecessary as the dialogService is used for file dialogs, which aligns with the project's architecture guidelines.

Copy link
Member

Choose a reason for hiding this comment

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

I think, the comment of trag bot is correct.

@koppor
Copy link
Member

koppor commented Apr 4, 2025

Tried out now with a MWE generated by https://github.com/latextemplates/generator-latex-template. Works.

@wanling0000
Copy link
Contributor Author

Tried out now with a MWE generated by https://github.com/latextemplates/generator-latex-template. Works.

Thanks @koppor

I noticed some of the checks are failing, I’ll take a look and try pushing a fix over this week. Let me know if there’s anything else I should work on :)

@wanling0000 wanling0000 marked this pull request as ready for review April 7, 2025 11:52
@wanling0000 wanling0000 marked this pull request as draft April 7, 2025 11:53
}

@Test
void returnsDefaultBlgPathWhenUserPathIsAbsent() {
Copy link

Choose a reason for hiding this comment

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

The test method names are descriptive enough, so using @DisplayName is unnecessary. The method name should be comprehensive enough to describe the test case.

}

@Test
void returnsEmptyWhenNoUserPathAndNoBibPath() {
Copy link

Choose a reason for hiding this comment

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

The test method names are descriptive enough, so using @DisplayName is unnecessary. The method name should be comprehensive enough to describe the test case.

* Ensures that the trailing semicolon (used as separator in .bib metadata) is handled and stripped properly.
*/
@Test
void parsesUserSpecificBlgPathSuccessfully() throws Exception {
Copy link

Choose a reason for hiding this comment

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

The method name 'parsesUserSpecificBlgPathSuccessfully' is not comprehensive enough. It should describe the test case more clearly without needing a @DisplayName annotation.

@wanling0000 wanling0000 requested a review from koppor April 15, 2025 13:02
private Runnable onBlgPathChanged;

public void initializeViewModel(BibDatabaseContext context, Runnable onBlgPathChanged) throws JabRefException {
if (context == null || context.getMetaData() == null) {
Copy link
Member

Choose a reason for hiding this comment

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

is it necessary to check for nulls here?


public Path getInitialDirectory() {
return bibPath.flatMap(path -> Optional.ofNullable(path.getParent()))
.orElse(Path.of(System.getProperty("user.home")));
Copy link
Member

Choose a reason for hiding this comment

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

Is this cross-platform ("user.home")

Copy link
Member

Choose a reason for hiding this comment

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

Not on Windows XP, but this is acceptable: https://stackoverflow.com/q/585534/873282

@wanling0000
Copy link
Contributor Author

I only meant to leave a single comment here, but I think I accidentally triggered a full review by clicking the wrong button 😅

Also, I noticed that the Trag review has been marked as “in progress” for two days now, not sure if that’s expected or if something got stuck?

@subhramit
Copy link
Member

Also, I noticed that the Trag review has been marked as “in progress” for two days now, not sure if that’s expected or if something got stuck?

It sometimes does that - pushing another commit refreshes it.

Copy link
Member

@koppor koppor left a comment

Choose a reason for hiding this comment

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

some small comments towards a really JabRef-high-quality code 😅

Comment on lines 28 to 32
/**
* 1. Connects MetaData with the view.
* 2. Wraps .blg warnings as IntegrityMessages.
* 3. Supports file browsing and reset actions.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Either html or Markdown - I propose markdown

Suggested change
/**
* 1. Connects MetaData with the view.
* 2. Wraps .blg warnings as IntegrityMessages.
* 3. Supports file browsing and reset actions.
*/
/// 1. Connects MetaData with the view.
/// 2. Wraps .blg warnings as IntegrityMessages.
/// 3. Supports file browsing and reset actions.

Comment on lines 49 to 53
this.path.set(resolvedPath.toString());
if (metaData.getBlgFilePath(user).isEmpty()) {
metaData.setBlgFilePath(user, resolvedPath);
this.lastResolvedBlgPath = Optional.of(resolvedPath);
}
Copy link
Member

Choose a reason for hiding this comment

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

indent seems to be off

Comment on lines 9 to 15
/**
* Resolves custom or default .blg path for this library.
*
* Priority:
* 1. User-defined path from MetaData
* 2. Default: same name as .bib file with .blg extension
*/
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/**
* Resolves custom or default .blg path for this library.
*
* Priority:
* 1. User-defined path from MetaData
* 2. Default: same name as .bib file with .blg extension
*/
/// Resolves custom or default .blg path for this library.
///
/// Priority:
///
/// 1. User-defined path from MetaData
/// 2. Default: same name as .bib file with .blg extension

Comment on lines 16 to 28
/**
* Converts {@link BibWarning}s into {@link IntegrityMessage}s for integration with the existing integrity check UI.
*
* Notes:
* - The current IntegrityMessage interface expects a {@link BibEntry} and a {@link Field},
* but .blg warnings come from a different source and may not include a field.
* - For now, we map missing fields to a placeholder (InternalField.KEY_FIELD) to make it compatible with the UI.
* - This is a minimal MVP solution to reuse the Integrity tab without changing the UI structure.
*
* Future direction:
* - Consider defining a proper interface (e.g., IntegrityMessageWithField / WithoutField)
* to support warnings without fields cleanly.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Please also convert to Markdown (to have the notes list rendered properly)

Comment on lines +63 to +65
metaData.getBlgFilePaths().forEach((user, path) -> {
stringyMetaData.put(MetaData.BLG_FILE_PATH + "-" + user, Collections.singletonList(path.toString().trim()));
});
Copy link
Member

Choose a reason for hiding this comment

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

I think, the other meta data fields do it, too.

I am OK without constant - follow-up can refactor the whole MetaDataParser ^^

Comment on lines 39 to 49
assertEquals(2, messages.size());

IntegrityMessage msg1 = messages.getFirst();
assertEquals("empty journal", msg1.message());
assertEquals(firstEntry, msg1.entry());
assertEquals("journal", msg1.field().getName());

IntegrityMessage msg2 = messages.get(1);
assertEquals("empty year", msg2.message());
assertEquals(secondEntry, msg2.entry());
assertEquals("year", msg2.field().getName());
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to compare two lists directly? Or are IntegrityMessage objects not good with equals?

Comment on lines +42 to +49
BibLogPathResolver.resolve(metaData, bibPath, user)
.ifPresent(resolvedPath -> {
this.path.set(resolvedPath.toString());
if (metaData.getBlgFilePath(user).isEmpty()) {
metaData.setBlgFilePath(user, resolvedPath);
this.lastResolvedBlgPath = Optional.of(resolvedPath);
}
});
Copy link

Choose a reason for hiding this comment

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

The code does not follow the fail-fast principle. It should return early if the condition is not met, instead of nesting logic inside an if statement.

Comment on lines +101 to +114
public void resetBlgFilePath() {
metaData.clearBlgFilePath(user);
userManuallySelectedBlgFile = false;
Optional<Path> resolved = BibLogPathResolver.resolve(metaData, bibPath, user);
if (resolved.isEmpty()) {
path.set("");
lastResolvedBlgPath = Optional.empty();
return;
}

Path resolvedPath = resolved.get();
path.set(resolvedPath.toString());
lastResolvedBlgPath = Optional.of(resolvedPath);
}
Copy link

Choose a reason for hiding this comment

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

The method does not follow the fail-fast principle. It should return early if the condition is not met, instead of nesting logic inside an if statement.

Comment on lines +35 to +37
if (context.getDatabase().getEntryByCitationKey(bibWarning.entryKey()).isEmpty()) {
continue;
}
Copy link

Choose a reason for hiding this comment

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

The code should follow the fail fast principle by immediately handling invalid states and returning early instead of nesting logic inside else branches. This code should return early if the entry is not found.

new IntegrityMessage("empty year", secondEntry, FieldFactory.parseField("year"))
);

assertEquals(expectedMessages, actualMessages);
Copy link

Choose a reason for hiding this comment

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

The test uses assertEquals to compare lists of objects. It's better to assert the contents of objects directly to ensure more precise validation of the test results.

@wanling0000 wanling0000 requested a review from koppor April 18, 2025 10:36
@koppor
Copy link
Member

koppor commented Apr 18, 2025

@wanling0000 One last thing: submodules src/main/resources/csl-styles were modified - can you follow https://devdocs.jabref.org/code-howtos/faq.html#submodules to fix?

Copy link

trag-bot bot commented Apr 18, 2025

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

@wanling0000
Copy link
Contributor Author

@wanling0000 One last thing: submodules src/main/resources/csl-styles were modified - can you follow https://devdocs.jabref.org/code-howtos/faq.html#submodules to fix?

Fixed, thanks for the reminder!

@koppor koppor added this pull request to the merge queue Apr 18, 2025
Merged via the queue into JabRef:main with commit ccc379c Apr 18, 2025
36 checks passed
@subhramit
Copy link
Member

@wanling0000 good work!

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.

Check integrity: Support for BibTeX .blg files
6 participants