Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3d9673
Remove superfluous String.repeat with jdk native
calixtus Apr 13, 2026
605cb87
Refactor checkBraces and add sanitizeUnbalancedBraces
calixtus Apr 13, 2026
4969731
Remove superfluous FieldWriter constructor and factory method
calixtus Apr 15, 2026
c9717a3
Fix NPE
calixtus Apr 15, 2026
99cab1d
Remove comment
calixtus Apr 15, 2026
a5879be
Modify tests
calixtus Apr 15, 2026
8fad675
Relax SourceTab validation
calixtus Apr 15, 2026
5397e4c
Relax BackupManager
calixtus Apr 15, 2026
5ec4d05
Enhance robustness of BstVMVisitor
calixtus Apr 15, 2026
edad226
Remove remaining InvalidFieldValueException
calixtus Apr 15, 2026
fd649b0
Remove additional checking for log
calixtus Apr 15, 2026
9cb68d1
Fix test
calixtus Apr 15, 2026
df06692
Simplify BracketChecker
calixtus Apr 15, 2026
e64fe14
Modify tests
calixtus Apr 15, 2026
82e0946
Fix checkBalancedBraces
calixtus Apr 15, 2026
5e38328
l10n
calixtus Apr 15, 2026
22b3d8e
Replace Stack with Deque for modern Java
calixtus Apr 15, 2026
702cef2
Add nullable annotations to UndoableFieldChange
calixtus Apr 15, 2026
962750a
Apply makeup to error message
calixtus Apr 15, 2026
956914c
Fix tests and remove obsolete test
calixtus Apr 15, 2026
6cb5ece
Optimize sanitize method
calixtus Apr 15, 2026
c8a23a5
Fix l10n
calixtus Apr 15, 2026
5200f35
Fix tests
calixtus Apr 15, 2026
2580850
Fix l10n
calixtus Apr 15, 2026
2223f12
Merge branch 'main' into braces
calixtus Apr 15, 2026
3778a89
Merge branch 'main' into braces
calixtus Apr 16, 2026
f7b15d3
Add dependency on e-adr
koppor Apr 17, 2026
0351e8c
More dependencies fixed
calixtus Apr 17, 2026
2c9f6b6
Merge branch 'main' into braces
calixtus Apr 17, 2026
e5d9045
Update doi_to_bibtex.java
calixtus Apr 18, 2026
51e950f
Update ieee_pdf_references_to_bibtex.java
calixtus Apr 18, 2026
05c2d2b
Update jablib/src/main/java/org/jabref/logic/bibtex/FieldWriter.java
calixtus Apr 19, 2026
c84f7ec
Apply review
calixtus Apr 19, 2026
116191c
Merge remote-tracking branch 'upstream/main' into braces
calixtus Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.jabref.gui.LibraryTab;
import org.jabref.gui.maintable.BibEntryTableViewModel;
import org.jabref.gui.maintable.columns.MainTableColumn;
import org.jabref.logic.bibtex.InvalidFieldValueException;
import org.jabref.logic.exporter.AtomicFileWriter;
import org.jabref.logic.exporter.BibDatabaseWriter;
import org.jabref.logic.exporter.BibWriter;
Expand Down Expand Up @@ -272,7 +271,7 @@ void performBackup(Path backupPath) {
// Thus, we currently do not need any new backup
this.needsBackup = false;
} catch (IOException e) {
logIfCritical(backupPath, e);
LOGGER.error("Error while saving to file {}", backupPath, e);
}
}

Expand All @@ -293,19 +292,6 @@ public void discardBackup(Path backupDir) {
}
}

private void logIfCritical(Path backupPath, IOException e) {
Throwable innermostCause = e;
while (innermostCause.getCause() != null) {
innermostCause = innermostCause.getCause();
}
boolean isErrorInField = innermostCause instanceof InvalidFieldValueException;

// do not print errors in field values into the log during autosave
if (!isErrorInField) {
LOGGER.error("Error while saving to file {}", backupPath, e);
}
}

@Subscribe
public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) {
if (!event.isFilteredOut()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private String getSourceString(BibEntry entry,
BibEntryTypesManager entryTypesManager) throws IOException {
StringWriter writer = new StringWriter();
BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE);
FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences);
FieldWriter fieldWriter = new FieldWriter(fieldPreferences);
new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type);
return writer.toString();
}
Expand Down
157 changes: 81 additions & 76 deletions jabgui/src/main/java/org/jabref/gui/entryeditor/SourceTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import org.jabref.logic.bibtex.BibEntryWriter;
import org.jabref.logic.bibtex.FieldPreferences;
import org.jabref.logic.bibtex.FieldWriter;
import org.jabref.logic.bibtex.InvalidFieldValueException;
import org.jabref.logic.exporter.BibWriter;
import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.importer.ParserResult;
Expand Down Expand Up @@ -161,7 +160,7 @@ private void highlightField(Field field, String searchPattern) {
private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences) throws IOException {
try (StringWriter writer = new StringWriter()) {
BibWriter bibWriter = new BibWriter(writer, "\n"); // JavaFX works with LF only
FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences);
FieldWriter fieldWriter = new FieldWriter(fieldPreferences);
BibEntryWriter bibEntryWriter = new BibEntryWriter(fieldWriter, entryTypesManager);
bibEntryWriter.write(entry, bibWriter, type, true);
fieldPositions = bibEntryWriter.getFieldPositions();
Expand Down Expand Up @@ -276,95 +275,101 @@ private void storeSource(BibEntry outOfFocusEntry, String text) {
}

BibtexParser bibtexParser = new BibtexParser(importFormatPreferences, fileMonitor);
ParserResult parserResult;
try {
ParserResult parserResult = bibtexParser.parse(Reader.of(text));
BibDatabase database = parserResult.getDatabase();

if (database.getEntryCount() > 1) {
LOGGER.error("More than one entry found.");
// We use the error dialog as the notification is hidden
dialogService.showWarningDialogAndWait(
Localization.lang("Problem with parsing entry"),
Localization.lang("Parsing failed because more than one entry was found. Please check your BibTeX syntax.")
);
return;
}

if (!database.hasEntries()) {
if (parserResult.hasWarnings()) {
LOGGER.warn("Could not store entry", parserResult.warnings());
String errors = parserResult.getErrorMessage();
dialogService.showErrorDialogAndWait(errors);
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", errors)));
return;
} else {
LOGGER.warn("No entries found.");
String errors = Localization.lang("No entries available");
dialogService.showErrorDialogAndWait(errors);
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", errors)));
return;
}
}
parserResult = bibtexParser.parse(Reader.of(text));
} catch (IOException ex) {
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", ex.getMessage())));
LOGGER.debug("Incorrect source", ex);
return;
}
BibDatabase database = parserResult.getDatabase();

if (database.getEntryCount() > 1) {
LOGGER.error("More than one entry found.");
// We use the error dialog as the notification is hidden
dialogService.showWarningDialogAndWait(
Localization.lang("Problem with parsing entry"),
Localization.lang("Parsing failed because more than one entry was found. Please check your BibTeX syntax.")
);
return;
}

if (!database.hasEntries()) {
if (parserResult.hasWarnings()) {
LOGGER.warn("Failed to parse Bib(La)TeX", parserResult.warnings());
LOGGER.warn("Could not store entry: {}", parserResult.warnings());
String errors = parserResult.getErrorMessage();
dialogService.showErrorDialogAndWait(errors);
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", errors)));
return;
} else {
LOGGER.warn("No entries found.");
String errors = Localization.lang("No entries available");
dialogService.showErrorDialogAndWait(errors);
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", errors)));
return;
}
}

NamedCompoundEdit compound = new NamedCompoundEdit(Localization.lang("source edit"));
BibEntry newEntry = database.getEntries().getFirst();
newEntry.getCitationKey()
.ifPresentOrElse(
outOfFocusEntry::setCitationKey,
() -> outOfFocusEntry.clearCiteKey());

// First, remove fields that the user has removed.
for (Map.Entry<Field, String> field : outOfFocusEntry.getFieldMap().entrySet()) {
Field fieldName = field.getKey();
String fieldValue = field.getValue();

if (!newEntry.hasField(fieldName)) {
compound.addEdit(new UndoableFieldChange(outOfFocusEntry, fieldName, fieldValue, null));
outOfFocusEntry.clearField(fieldName);
}
if (parserResult.hasWarnings()) {
LOGGER.warn("Failed to parse Bib(La)TeX: {}", parserResult.warnings());
String errors = parserResult.getErrorMessage();
dialogService.showErrorDialogAndWait(errors);
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", errors)));
}

NamedCompoundEdit compound = new NamedCompoundEdit(Localization.lang("source edit"));
BibEntry newEntry = database.getEntries().getFirst();
newEntry.getCitationKey()
.ifPresentOrElse(
outOfFocusEntry::setCitationKey,
outOfFocusEntry::clearCiteKey);

// First, remove fields that the user has removed.
for (Map.Entry<Field, String> field : outOfFocusEntry.getFieldMap().entrySet()) {
Field fieldName = field.getKey();
String fieldValue = field.getValue();

if (!newEntry.hasField(fieldName)) {
compound.addEdit(new UndoableFieldChange(outOfFocusEntry, fieldName, fieldValue, null));
outOfFocusEntry.clearField(fieldName);
}
}

// Then set all fields that have been set by the user.
for (Map.Entry<Field, String> field : newEntry.getFieldMap().entrySet()) {
Field fieldName = field.getKey();
String oldValue = outOfFocusEntry.getField(fieldName).orElse(null);
String newValue = field.getValue();
if (!Objects.equals(oldValue, newValue)) {
// Test if the field is legally set.
new FieldWriter(fieldPreferences).write(fieldName, newValue);

compound.addEdit(new UndoableFieldChange(outOfFocusEntry, fieldName, oldValue, newValue));
outOfFocusEntry.setField(fieldName, newValue);
// Then set all fields that have been set by the user.
for (Map.Entry<Field, String> field : newEntry.getFieldMap().entrySet()) {
Field fieldName = field.getKey();
String oldValue = outOfFocusEntry.getField(fieldName).orElse(null);
String newValue = field.getValue();
if (!Objects.equals(oldValue, newValue)) {
// Test if the field is legally set.
List<String> errors = FieldWriter.checkBalancedBraces(newValue);
if (!errors.isEmpty()) {
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", errors)));
return;
}
}

// See if the user has changed the entry type:
if (!Objects.equals(newEntry.getType(), outOfFocusEntry.getType())) {
compound.addEdit(new UndoableChangeType(outOfFocusEntry, outOfFocusEntry.getType(), newEntry.getType()));
outOfFocusEntry.setType(newEntry.getType());
}
compound.end();
undoManager.addEdit(compound);

ObservableList<BibEntry> selectedEntries = stateManager.getSelectedEntries();
if (selectedEntries == null || selectedEntries.isEmpty()) {
stateManager.activeTabProperty().get().ifPresent(libraryTab ->
libraryTab.getMainTable().clearAndSelect(outOfFocusEntry)
);
compound.addEdit(new UndoableFieldChange(outOfFocusEntry, fieldName, oldValue, newValue));
outOfFocusEntry.setField(fieldName, newValue);
}
}

validationMessage.setValue(null);
} catch (InvalidFieldValueException | IOException ex) {
validationMessage.setValue(ValidationMessage.error(Localization.lang("Failed to parse Bib(La)TeX: %0", ex.getMessage())));
LOGGER.debug("Incorrect source", ex);
// See if the user has changed the entry type:
if (!Objects.equals(newEntry.getType(), outOfFocusEntry.getType())) {
compound.addEdit(new UndoableChangeType(outOfFocusEntry, outOfFocusEntry.getType(), newEntry.getType()));
outOfFocusEntry.setType(newEntry.getType());
}
compound.end();
undoManager.addEdit(compound);

ObservableList<BibEntry> selectedEntries = stateManager.getSelectedEntries();
if (selectedEntries == null || selectedEntries.isEmpty()) {
stateManager.activeTabProperty().get().ifPresent(libraryTab ->
libraryTab.getMainTable().clearAndSelect(outOfFocusEntry)
);
}

validationMessage.setValue(null);
}

private void listenForSaveKeybinding(KeyEvent event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ private void jumpToEntry(CitationRelationItem entry) {
private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldPreferences fieldPreferences, BibEntryTypesManager entryTypesManager) throws IOException {
StringWriter writer = new StringWriter();
BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE);
FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldPreferences);
FieldWriter fieldWriter = new FieldWriter(fieldPreferences);
new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, type);
return writer.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public boolean hasDuplicate(BibEntry entry) {
public String getSourceString(BibEntry entry) {
StringWriter writer = new StringWriter();
BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE);
FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(preferences.getFieldPreferences());
FieldWriter fieldWriter = new FieldWriter(preferences.getFieldPreferences());
try {
// Force reformatting so the displayed BibTeX is consistently formatted
new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, selectedDb.getValue().getMode(), true);
Expand Down
15 changes: 5 additions & 10 deletions jablib/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,10 @@ private void writeField(BibEntry entry, BibWriter out, Field field, int indent)
if (value.isPresent() && !value.get().trim().isEmpty()) {
out.write(" ");
out.write(getFormattedFieldName(field, indent));
try {
int start = out.getCurrentPosition();
out.write(fieldWriter.write(field, value.get()));
int end = out.getCurrentPosition();
fieldPositions.put(field, new Range(start, end));
} catch (InvalidFieldValueException ex) {
LOGGER.warn("Invalid field value {} of field {} of entry {}", value.get(), field, entry.getCitationKey().orElse(""), ex);
throw new IOException("Error in field '" + field + " of entry " + entry.getCitationKey().orElse("") + "': " + ex.getMessage(), ex);
}
int start = out.getCurrentPosition();
out.write(fieldWriter.write(field, value.get()));
int end = out.getCurrentPosition();
fieldPositions.put(field, new Range(start, end));
out.writeLine(",");
}
}
Expand All @@ -200,7 +195,7 @@ static int getLengthOfLongestFieldName(BibEntry entry) {
@ADR(49)
static String getFormattedFieldName(Field field, int indent) {
String fieldName = field.getName();
return fieldName + StringUtil.repeatSpaces(indent - fieldName.length()) + " = ";
return fieldName + " ".repeat(indent - fieldName.length()) + " = ";
}

public Map<Field, Range> getFieldPositions() {
Expand Down
Loading
Loading