From ac738c062ad88e10c0ea8ec1056fafbd98131dfd Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 11 Nov 2021 00:48:17 +0100 Subject: [PATCH 01/10] Remove "% Encoding: UTF8" --- CHANGELOG.md | 1 + .../org/jabref/cli/ArgumentProcessor.java | 5 +- .../org/jabref/gui/SendAsEMailAction.java | 7 +- .../org/jabref/gui/entryeditor/SourceTab.java | 9 +- .../gui/exporter/SaveDatabaseAction.java | 7 +- .../autosaveandbackup/BackupManager.java | 8 +- .../jabref/logic/bibtex/BibEntryWriter.java | 42 ++--- .../jabref/logic/crawler/StudyRepository.java | 10 +- .../logic/exporter/BibDatabaseWriter.java | 26 ++-- .../logic/exporter/BibtexDatabaseWriter.java | 43 +++--- .../exporter/EmbeddedBibFilePdfExporter.java | 7 +- .../importer/fileformat/BibtexParser.java | 9 +- .../logic/bibtex/BibEntryWriterTest.java | 134 ++++++++-------- .../exporter/BibtexDatabaseWriterTest.java | 144 +++++++++++------- .../resources/testbib/articleWithMonths.bib | 1 - .../resources/testbib/unknownMetaData.bib | 2 - 16 files changed, 253 insertions(+), 202 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a60c25b248a..ae43c6a71fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We improved the Drag and Drop behavior in the "Customize Entry Types" Dialog [#6338](https://github.com/JabRef/jabref/issues/6338) - When determining the URL of an ArXiV eprint, the URL now points to the version [#8149](https://github.com/JabRef/jabref/pull/8149) - We Included all standard fields with citation key when exporting to Old OpenOffice/LibreOffice Calc Format [#8176](https://github.com/JabRef/jabref/pull/8176) +- In case the database is encoded with `UTF8`, the `% Encoding` marker is not written any more - We present options to manually enter an article or return to the New Entry menu when the fetcher DOI fails to find an entry for an ID [#7870](https://github.com/JabRef/jabref/issues/7870) ### Fixed diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 5cfc3e81cf0..e36bce36ca8 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -10,6 +10,7 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.Vector; import java.util.prefs.BackingStoreException; @@ -531,8 +532,10 @@ private void saveDatabase(BibDatabase newBase, String subName) { GeneralPreferences generalPreferences = preferencesService.getGeneralPreferences(); SavePreferences savePreferences = preferencesService.getSavePreferences(); AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), generalPreferences.getDefaultEncoding()); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, generalPreferences, savePreferences, Globals.entryTypesManager); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, Globals.entryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); + fileWriter.write(stringJoiner.toString()); // Show just a warning message if encoding did not work for all characters: if (fileWriter.hasEncodingProblems()) { diff --git a/src/main/java/org/jabref/gui/SendAsEMailAction.java b/src/main/java/org/jabref/gui/SendAsEMailAction.java index 7ea52d621f9..30df8410bd2 100644 --- a/src/main/java/org/jabref/gui/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/SendAsEMailAction.java @@ -2,11 +2,11 @@ import java.awt.Desktop; import java.io.IOException; -import java.io.StringWriter; import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.actions.ActionHelper; @@ -16,6 +16,7 @@ import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -71,7 +72,7 @@ private String sendEmail() throws Exception { return Localization.lang("This operation requires one or more entries to be selected."); } - StringWriter rawEntries = new StringWriter(); + StringJoiner rawEntries = new StringJoiner(OS.NEWLINE); BibDatabaseContext databaseContext = stateManager.getActiveDatabase().get(); List entries = stateManager.getSelectedEntries(); @@ -104,7 +105,7 @@ private String sendEmail() throws Exception { } } - String mailTo = "?Body=".concat(rawEntries.getBuffer().toString()); + String mailTo = "?Body=".concat(rawEntries.toString()); mailTo = mailTo.concat("&Subject="); mailTo = mailTo.concat(preferencesService.getExternalApplicationsPreferences().getEmailSubject()); for (String path : attachments) { diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 4f8e4d7fcc9..18a8948aece 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -2,10 +2,10 @@ import java.io.IOException; import java.io.StringReader; -import java.io.StringWriter; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.StringJoiner; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,6 +43,7 @@ import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.search.SearchQuery; +import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -128,11 +129,11 @@ private void highlightSearchPattern() { } private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldWriterPreferences fieldWriterPreferences) throws IOException { - StringWriter stringWriter = new StringWriter(200); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); - new BibEntryWriter(fieldWriter, Globals.entryTypesManager).writeWithoutPrependedNewlines(entry, stringWriter, type); + new BibEntryWriter(fieldWriter, Globals.entryTypesManager).writeWithoutPrependedNewlines(entry, stringJoiner, type); - return stringWriter.getBuffer().toString(); + return stringJoiner.toString(); } /* Work around for different input methods. diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index a85f5acfd66..f074ff51f07 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -7,6 +7,7 @@ import java.nio.file.Path; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.stream.Collectors; import javafx.scene.control.ButtonBar; @@ -31,6 +32,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.shared.prefs.SharedDatabasePreferences; +import org.jabref.logic.util.OS; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.ChangePropagation; @@ -229,7 +231,8 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, SavePreferences savePreferences = this.preferences.getSavePreferences() .withSaveType(saveType); try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, savePreferences.shouldMakeBackup())) { - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, generalPreferences, savePreferences, entryTypesManager); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); if (selectedOnly) { databaseWriter.savePartOfDatabase(libraryTab.getBibDatabaseContext(), libraryTab.getSelectedEntries()); @@ -237,6 +240,8 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, databaseWriter.saveDatabase(libraryTab.getBibDatabaseContext()); } + fileWriter.write(stringJoiner.toString()); + libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); if (fileWriter.hasEncodingProblems()) { diff --git a/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java index 0c063d26400..dab6f6b1de7 100644 --- a/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java @@ -1,6 +1,7 @@ package org.jabref.logic.autosaveandbackup; import java.io.IOException; +import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -8,6 +9,7 @@ import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; @@ -17,6 +19,7 @@ import org.jabref.logic.exporter.SavePreferences; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.logic.util.DelayTaskThrottler; +import org.jabref.logic.util.OS; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.BibDatabaseContextChangedEvent; @@ -135,8 +138,11 @@ private void performBackup(Path backupPath) { GeneralPreferences generalPreferences = preferences.getGeneralPreferences(); SavePreferences savePreferences = preferences.getSavePreferences() .withMakeBackup(false); - new BibtexDatabaseWriter(new AtomicFileWriter(backupPath, charset), generalPreferences, savePreferences, entryTypesManager) + Writer writer = new AtomicFileWriter(backupPath, charset); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager) .saveDatabase(bibDatabaseContext); + writer.write(stringJoiner.toString()); } catch (IOException e) { logIfCritical(backupPath, e); } diff --git a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java index e5e24ef8278..9feb8426c48 100644 --- a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java +++ b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.StringWriter; -import java.io.Writer; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; @@ -11,6 +10,7 @@ import java.util.Optional; import java.util.Set; import java.util.SortedSet; +import java.util.StringJoiner; import java.util.TreeSet; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -37,15 +37,14 @@ public BibEntryWriter(FieldWriter fieldWriter, BibEntryTypesManager entryTypesMa } public String serializeAll(List entries, BibDatabaseMode databaseMode) throws IOException { - StringWriter writer = new StringWriter(); - + StringJoiner writer = new StringJoiner(OS.NEWLINE); for (BibEntry entry : entries) { write(entry, writer, databaseMode); } return writer.toString(); } - public void write(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode) throws IOException { + public void write(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode) throws IOException { write(entry, out, bibDatabaseMode, false); } @@ -57,31 +56,29 @@ public void write(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode) t * @param bibDatabaseMode The database mode (bibtex or biblatex) * @param reformat Should the entry be in any case, even if no change occurred? */ - public void write(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode, Boolean reformat) throws IOException { + public void write(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode, Boolean reformat) throws IOException { // if the entry has not been modified, write it as it was if (!reformat && !entry.hasChanged()) { - out.write(entry.getParsedSerialization()); + out.add(entry.getParsedSerialization()); return; } writeUserComments(entry, out); - out.write(OS.NEWLINE); writeRequiredFieldsFirstRemainingFieldsSecond(entry, out, bibDatabaseMode); - out.write(OS.NEWLINE); } - private void writeUserComments(BibEntry entry, Writer out) throws IOException { + private void writeUserComments(BibEntry entry, StringJoiner out) throws IOException { String userComments = entry.getUserComments(); if (!userComments.isEmpty()) { - out.write(userComments + OS.NEWLINE); + out.add(userComments); } } - public void writeWithoutPrependedNewlines(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode) throws IOException { + public void writeWithoutPrependedNewlines(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode) throws IOException { // if the entry has not been modified, write it as it was if (!entry.hasChanged()) { - out.write(entry.getParsedSerialization().trim()); + out.add(entry.getParsedSerialization().trim()); return; } @@ -91,13 +88,14 @@ public void writeWithoutPrependedNewlines(BibEntry entry, Writer out, BibDatabas /** * Writes fields in the order of requiredFields, optionalFields and other fields, but does not sort the fields. */ - private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Writer out, + private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode) throws IOException { // Write header with type and bibtex-key TypedBibEntry typedEntry = new TypedBibEntry(entry, bibDatabaseMode); - out.write('@' + typedEntry.getTypeForDisplay() + '{'); + StringWriter writer = new StringWriter(); + writer.write('@' + typedEntry.getTypeForDisplay() + '{'); - writeKeyField(entry, out); + writeKeyField(entry, writer); Set written = new HashSet<>(); written.add(InternalField.KEY_FIELD); @@ -114,7 +112,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Write .collect(Collectors.toList()); for (Field field : requiredFields) { - writeField(entry, out, field, indentation); + writeField(entry, writer, field, indentation); } // Then optional fields @@ -126,7 +124,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Write .collect(Collectors.toList()); for (Field field : optionalFields) { - writeField(entry, out, field, indentation); + writeField(entry, writer, field, indentation); } written.addAll(requiredFields); @@ -139,14 +137,16 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Write .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Field::getName)))); for (Field field : remainingFields) { - writeField(entry, out, field, indentation); + writeField(entry, writer, field, indentation); } // Finally, end the entry. - out.write('}'); + writer.write('}'); + writer.write(OS.NEWLINE); + out.add(writer.toString()); } - private void writeKeyField(BibEntry entry, Writer out) throws IOException { + private void writeKeyField(BibEntry entry, StringWriter out) throws IOException { String keyField = StringUtil.shaveString(entry.getCitationKey().orElse("")); out.write(keyField + ',' + OS.NEWLINE); } @@ -159,7 +159,7 @@ private void writeKeyField(BibEntry entry, Writer out) throws IOException { * @param field the field * @throws IOException In case of an IO error */ - private void writeField(BibEntry entry, Writer out, Field field, int indentation) throws IOException { + private void writeField(BibEntry entry, StringWriter out, Field field, int indentation) throws IOException { Optional value = entry.getField(field); // only write field if is is not empty // field.ifPresent does not work as an IOException may be thrown diff --git a/src/main/java/org/jabref/logic/crawler/StudyRepository.java b/src/main/java/org/jabref/logic/crawler/StudyRepository.java index f67a501042d..59842cfda5d 100644 --- a/src/main/java/org/jabref/logic/crawler/StudyRepository.java +++ b/src/main/java/org/jabref/logic/crawler/StudyRepository.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.StringJoiner; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -25,6 +26,7 @@ import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; @@ -416,12 +418,10 @@ private void writeResultToFile(Path pathToFile, BibDatabase entries) throws IOEx Files.createFile(pathToFile); } try (Writer fileWriter = new FileWriter(pathToFile.toFile())) { - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, generalPreferences, savePreferences, bibEntryTypesManager); - databaseWriter.saveDatabase(new BibDatabaseContext(entries)); - } - try (AtomicFileWriter fileWriter = new AtomicFileWriter(pathToFile, generalPreferences.getDefaultEncoding(), savePreferences.shouldMakeBackup())) { - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, generalPreferences, savePreferences, bibEntryTypesManager); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, bibEntryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(entries)); + fileWriter.write(stringJoiner.toString()); } catch (UnsupportedCharsetException ex) { throw new SaveException(Localization.lang("Character encoding '%0' is not supported.", generalPreferences.getDefaultEncoding().displayName()), ex); } catch (IOException ex) { diff --git a/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java b/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java index b8d91298e97..e580dc95657 100644 --- a/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java +++ b/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java @@ -1,7 +1,6 @@ package org.jabref.logic.exporter; import java.io.IOException; -import java.io.Writer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -13,6 +12,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,14 +47,14 @@ public abstract class BibDatabaseWriter { private static final Pattern REFERENCE_PATTERN = Pattern.compile("(#[A-Za-z]+#)"); // Used to detect string references in strings - protected final Writer writer; + protected final StringJoiner blockJoiner; protected final GeneralPreferences generalPreferences; protected final SavePreferences savePreferences; protected final List saveActionsFieldChanges = new ArrayList<>(); protected final BibEntryTypesManager entryTypesManager; - public BibDatabaseWriter(Writer writer, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { - this.writer = Objects.requireNonNull(writer); + public BibDatabaseWriter(StringJoiner blockJoiner, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { + this.blockJoiner = Objects.requireNonNull(blockJoiner); this.generalPreferences = generalPreferences; this.savePreferences = savePreferences; this.entryTypesManager = entryTypesManager; @@ -179,7 +179,7 @@ public void savePartOfDatabase(BibDatabaseContext bibDatabaseContext, List remaining, int maxKeyLength) + protected void writeString(BibtexString bibtexString, Map remaining, int maxKeyLength) throws IOException { // First remove this from the "remaining" list so it can't cause problem with circular refs: remaining.remove(bibtexString.getName()); @@ -300,14 +296,14 @@ protected void writeString(BibtexString bibtexString, boolean isFirstString, Map // If the label we found exists as a key in the "remaining" Map, we go on and write it now: if (remaining.containsKey(label)) { BibtexString referred = remaining.get(label); - writeString(referred, isFirstString, remaining, maxKeyLength); + writeString(referred, remaining, maxKeyLength); } } - writeString(bibtexString, isFirstString, maxKeyLength); + writeString(bibtexString, maxKeyLength); } - protected abstract void writeString(BibtexString bibtexString, boolean isFirstString, int maxKeyLength) + protected abstract void writeString(BibtexString bibtexString, int maxKeyLength) throws IOException; protected void writeEntryTypeDefinitions(Set types) throws IOException { diff --git a/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java b/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java index 09a8c331ac5..ce095c9deb2 100644 --- a/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java +++ b/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java @@ -1,9 +1,11 @@ package org.jabref.logic.exporter; import java.io.IOException; -import java.io.Writer; +import java.io.StringWriter; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Map; +import java.util.StringJoiner; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; @@ -27,22 +29,20 @@ public class BibtexDatabaseWriter extends BibDatabaseWriter { private static final String COMMENT_PREFIX = "@Comment"; private static final String PREAMBLE_PREFIX = "@Preamble"; - public BibtexDatabaseWriter(Writer writer, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { - super(writer, generalPreferences, savePreferences, entryTypesManager); + public BibtexDatabaseWriter(StringJoiner blockJoiner, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { + super(blockJoiner, generalPreferences, savePreferences, entryTypesManager); } @Override protected void writeEpilogue(String epilogue) throws IOException { if (!StringUtil.isNullOrEmpty(epilogue)) { - writer.write(OS.NEWLINE); - writer.write(epilogue); - writer.write(OS.NEWLINE); + blockJoiner.add(epilogue); } } @Override protected void writeMetaDataItem(Map.Entry metaItem) throws IOException { - writer.write(OS.NEWLINE); + StringWriter writer = new StringWriter(); writer.write(COMMENT_PREFIX + "{"); writer.write(MetaData.META_FLAG); writer.write(metaItem.getKey()); @@ -50,36 +50,35 @@ protected void writeMetaDataItem(Map.Entry metaItem) throws IOEx writer.write(metaItem.getValue()); writer.write("}"); writer.write(OS.NEWLINE); + blockJoiner.add(writer.toString()); } @Override protected void writePreamble(String preamble) throws IOException { if (!StringUtil.isNullOrEmpty(preamble)) { - writer.write(OS.NEWLINE); + StringWriter writer = new StringWriter(); writer.write(PREAMBLE_PREFIX + "{"); writer.write(preamble); writer.write('}' + OS.NEWLINE); + blockJoiner.add(writer.toString()); } } @Override - protected void writeString(BibtexString bibtexString, boolean isFirstString, int maxKeyLength) throws IOException { + protected void writeString(BibtexString bibtexString, int maxKeyLength) throws IOException { // If the string has not been modified, write it back as it was if (!savePreferences.shouldReformatFile() && !bibtexString.hasChanged()) { - writer.write(bibtexString.getParsedSerialization()); + blockJoiner.add(bibtexString.getParsedSerialization()); return; } // Write user comments String userComments = bibtexString.getUserComments(); if (!userComments.isEmpty()) { - writer.write(userComments + OS.NEWLINE); - } - - if (isFirstString) { - writer.write(OS.NEWLINE); + blockJoiner.add(userComments + OS.NEWLINE); } + StringWriter writer = new StringWriter(); writer.write(STRING_PREFIX + "{" + bibtexString.getName() + StringUtil .repeatSpaces(maxKeyLength - bibtexString.getName().length()) + " = "); if (bibtexString.getContent().isEmpty()) { @@ -96,41 +95,47 @@ protected void writeString(BibtexString bibtexString, boolean isFirstString, int } writer.write("}" + OS.NEWLINE); + blockJoiner.add(writer.toString()); } @Override protected void writeEntryTypeDefinition(BibEntryType customType) throws IOException { - writer.write(OS.NEWLINE); + StringWriter writer = new StringWriter(); writer.write(COMMENT_PREFIX + "{"); writer.write(BibEntryTypesManager.serialize(customType)); writer.write("}"); writer.write(OS.NEWLINE); + blockJoiner.add(writer.toString()); } @Override - protected void writePrelogue(BibDatabaseContext bibDatabaseContext, Charset encoding) throws IOException { - if (encoding == null) { + protected void writeProlog(BibDatabaseContext bibDatabaseContext, Charset encoding) throws IOException { + if ((encoding == null) || (encoding == StandardCharsets.UTF_8)) { return; } // Writes the file encoding information. + StringWriter writer = new StringWriter(); writer.write("% "); writer.write(SavePreferences.ENCODING_PREFIX + encoding); writer.write(OS.NEWLINE); + blockJoiner.add(writer.toString()); } @Override protected void writeDatabaseID(String sharedDatabaseID) throws IOException { + StringWriter writer = new StringWriter(); writer.write("% " + DATABASE_ID_PREFIX + " " + sharedDatabaseID + OS.NEWLINE); + blockJoiner.add(writer.toString()); } @Override protected void writeEntry(BibEntry entry, BibDatabaseMode mode) throws IOException { BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(savePreferences.getFieldWriterPreferences()), entryTypesManager); - bibtexEntryWriter.write(entry, writer, mode, savePreferences.shouldReformatFile()); + bibtexEntryWriter.write(entry, blockJoiner, mode, savePreferences.shouldReformatFile()); } } diff --git a/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java b/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java index e4735cfc2c2..5693879e62c 100644 --- a/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java +++ b/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java @@ -3,7 +3,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -11,10 +10,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.StringJoiner; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.util.OS; import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; @@ -101,12 +102,12 @@ private void embedBibTex(String bibTeX, Path file, Charset encoding) throws IOEx } private String getBibString(List entries) throws IOException { - StringWriter stringWriter = new StringWriter(200); + StringJoiner stringWriter = new StringJoiner(OS.NEWLINE); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); BibEntryWriter bibEntryWriter = new BibEntryWriter(fieldWriter, bibEntryTypesManager); for (BibEntry entry : entries) { bibEntryWriter.writeWithoutPrependedNewlines(entry, stringWriter, bibDatabaseMode); } - return stringWriter.getBuffer().toString(); + return stringWriter.toString(); } } diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index eb348459668..c9dc5d90022 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -149,7 +149,6 @@ private void initializeParserResult() { } private void parseDatabaseID() throws IOException { - while (!eof) { skipWhitespace(); char c = (char) read(); @@ -345,7 +344,13 @@ private String purge(String context, String stringToPurge) { } runningIndex++; } - return context.substring(runningIndex + 1); + // strip empty lines + while ((runningIndex < indexOfAt) && + (context.charAt(runningIndex) == '\r' || + context.charAt(runningIndex) == '\n')) { + runningIndex++; + } + return context.substring(runningIndex); } private String getPureTextFromFile() { diff --git a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java index 31a496a8a81..292d6508310 100644 --- a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java +++ b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java @@ -2,11 +2,11 @@ import java.io.IOException; import java.io.StringReader; -import java.io.StringWriter; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.StringJoiner; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; @@ -47,7 +47,7 @@ void setUpWriter() { @Test void testSerialization() throws IOException { - StringWriter stringWriter = new StringWriter(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); BibEntry entry = new BibEntry(StandardEntryType.Article); // set a required field @@ -57,9 +57,9 @@ void testSerialization() throws IOException { entry.setField(StandardField.NUMBER, "1"); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + @@ -83,10 +83,10 @@ void writeOtherTypeTest() throws Exception { entry.setField(StandardField.COMMENT, "testentry"); entry.setCitationKey("test"); - // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + // write out bibtex stringJoiner + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(expected, actual); } @@ -97,20 +97,20 @@ void writeEntryWithFile() throws Exception { LinkedFile file = new LinkedFile("test", Path.of("/home/uers/test.pdf"), "PDF"); entry.addFile(file); - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); assertEquals(OS.NEWLINE + "@Article{," + OS.NEWLINE + " file = {test:/home/uers/test.pdf:PDF}," + OS.NEWLINE - + "}" + OS.NEWLINE, stringWriter.toString()); + + "}" + OS.NEWLINE, stringJoiner.toString()); } @Test void writeEntryWithOrField() throws Exception { - StringWriter stringWriter = new StringWriter(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); BibEntry entry = new BibEntry(StandardEntryType.InBook); // set an required OR field (author/editor) @@ -120,9 +120,9 @@ void writeEntryWithOrField() throws Exception { entry.setField(StandardField.NUMBER, "1"); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@InBook{," + OS.NEWLINE + @@ -138,7 +138,7 @@ void writeEntryWithOrField() throws Exception { @Test void writeEntryWithOrFieldBothFieldsPresent() throws Exception { - StringWriter stringWriter = new StringWriter(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); BibEntry entry = new BibEntry(StandardEntryType.InBook); // set an required OR field with both fields(author/editor) @@ -149,9 +149,9 @@ void writeEntryWithOrFieldBothFieldsPresent() throws Exception { entry.setField(StandardField.NUMBER, "1"); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@InBook{," + OS.NEWLINE + @@ -178,9 +178,9 @@ void writeReallyUnknownTypeTest() throws Exception { entry.setCitationKey("test"); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(expected, actual); } @@ -202,9 +202,9 @@ void roundTripTest() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(bibtexEntry, actual); } @@ -226,9 +226,9 @@ void roundTripWithPrependingNewlines() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(bibtexEntry, actual); } @@ -253,9 +253,9 @@ void roundTripWithModification() throws IOException { entry.setField(StandardField.AUTHOR, "BlaBla"); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -289,9 +289,9 @@ void roundTripWithCamelCasingInTheOriginalEntryAndResultInLowerCase() throws IOE entry.setField(StandardField.AUTHOR, "BlaBla"); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -326,9 +326,9 @@ void testEntryTypeChange() throws IOException { entry.setType(StandardEntryType.InProceedings); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); // @formatter:off String expectedNewEntry = OS.NEWLINE + "@InProceedings{test," + OS.NEWLINE + @@ -359,9 +359,9 @@ void roundTripWithAppendedNewlines() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); // Only one appending newline is written by the writer, the rest by FileActions. So, these should be removed here. assertEquals(bibtexEntry.substring(0, bibtexEntry.length() - 1), actual); @@ -392,9 +392,9 @@ private String testSingleWrite(String bibtexEntry) throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(bibtexEntry, actual); return actual; @@ -421,9 +421,9 @@ void monthFieldSpecialSyntax() throws IOException { assertEquals("#mar#", entry.getField(StandardField.MONTH).get()); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(bibtexEntry, actual); } @@ -435,14 +435,14 @@ void constantMonthApril() throws Exception { // enable writing entry.setChanged(true); - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); assertEquals(OS.NEWLINE + "@Misc{," + OS.NEWLINE + " month = apr," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -452,14 +452,14 @@ void monthApril() throws Exception { // enable writing entry.setChanged(true); - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); assertEquals(OS.NEWLINE + "@Misc{," + OS.NEWLINE + " month = {apr}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -482,9 +482,9 @@ void addFieldWithLongerLength() throws IOException { entry.setField(StandardField.HOWPUBLISHED, "asdf"); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -500,15 +500,15 @@ void addFieldWithLongerLength() throws IOException { @Test void doNotWriteEmptyFields() throws IOException { - StringWriter stringWriter = new StringWriter(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); BibEntry entry = new BibEntry(StandardEntryType.Article); entry.setField(StandardField.AUTHOR, " "); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + String actual = stringJoiner.toString(); String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + @@ -519,12 +519,12 @@ void doNotWriteEmptyFields() throws IOException { @Test void writeThrowsErrorIfFieldContainsUnbalancedBraces() { - StringWriter stringWriter = new StringWriter(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); BibEntry entry = new BibEntry(StandardEntryType.Article); entry.setField(StandardField.NOTE, "some text with unbalanced { braces"); - assertThrows(IOException.class, () -> writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX)); + assertThrows(IOException.class, () -> writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX)); } @Test @@ -545,9 +545,9 @@ void roundTripWithPrecedingCommentTest() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); assertEquals(bibtexEntry, actual); } @@ -573,9 +573,9 @@ void roundTripWithPrecedingCommentAndModificationTest() throws IOException { entry.setField(StandardField.AUTHOR, "John Doe"); // write out bibtex string - StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); - String actual = stringWriter.toString(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + String actual = stringJoiner.toString(); // @formatter:off String expected = "% Some random comment that should stay here" + OS.NEWLINE + OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -591,7 +591,7 @@ void roundTripWithPrecedingCommentAndModificationTest() throws IOException { @Test void alphabeticSerialization() throws IOException { - StringWriter stringWriter = new StringWriter(); + StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); BibEntry entry = new BibEntry(StandardEntryType.Article); // required fields @@ -606,9 +606,9 @@ void alphabeticSerialization() throws IOException { entry.setField(StandardField.YEAR, "2019"); entry.setField(StandardField.CHAPTER, "chapter"); - writer.write(entry, stringWriter, BibDatabaseMode.BIBLATEX); + writer.write(entry, stringJoiner, BibDatabaseMode.BIBLATEX); - String actual = stringWriter.toString(); + String actual = stringJoiner.toString(); // @formatter:off String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + diff --git a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java index a48da10a0e7..70e518552b7 100644 --- a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java +++ b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.StringReader; -import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -11,6 +10,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.StringJoiner; import org.jabref.logic.citationkeypattern.AbstractCitationKeyPattern; import org.jabref.logic.citationkeypattern.DatabaseCitationKeyPattern; @@ -60,7 +60,7 @@ class BibtexDatabaseWriterTest { - private StringWriter stringWriter; + private StringJoiner stringJoiner; private BibtexDatabaseWriter databaseWriter; private BibDatabase database; private MetaData metaData; @@ -73,14 +73,14 @@ class BibtexDatabaseWriterTest { @BeforeEach void setUp() { - stringWriter = new StringWriter(); + stringJoiner = new StringJoiner(OS.NEWLINE); generalPreferences = mock(GeneralPreferences.class); when(generalPreferences.getDefaultEncoding()).thenReturn(null); savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); entryTypesManager = new BibEntryTypesManager(); - databaseWriter = new BibtexDatabaseWriter(stringWriter, generalPreferences, savePreferences, entryTypesManager); + databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); database = new BibDatabase(); metaData = new MetaData(); @@ -104,7 +104,7 @@ void writeEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("% Encoding: US-ASCII" + OS.NEWLINE, stringWriter.toString()); + assertEquals("% Encoding: US-ASCII" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -113,7 +113,7 @@ void writePreamble() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Preamble{Test preamble}" + OS.NEWLINE, stringWriter.toString()); + assertEquals(OS.NEWLINE + "@Preamble{Test preamble}" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -124,7 +124,7 @@ void writePreambleAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "@Preamble{Test preamble}" + OS.NEWLINE, stringWriter.toString()); + "@Preamble{Test preamble}" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -139,7 +139,7 @@ void writeEntry() throws Exception { OS.NEWLINE + "@Article{," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -155,7 +155,7 @@ void writeEncodingAndEntry() throws Exception { "% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + "@Article{," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -164,7 +164,7 @@ void writeEpilogue() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "Test epilog" + OS.NEWLINE, stringWriter.toString()); + assertEquals(OS.NEWLINE + "Test epilog" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -175,7 +175,7 @@ void writeEpilogueAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "Test epilog" + OS.NEWLINE, stringWriter.toString()); + "Test epilog" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -187,7 +187,7 @@ void writeMetadata() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals(OS.NEWLINE + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -201,7 +201,7 @@ void writeMetadataAndEncoding() throws Exception { assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, stringWriter.toString()); + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -217,7 +217,7 @@ void writeGroups() throws Exception { + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE - + "}" + OS.NEWLINE, stringWriter.toString()); + + "}" + OS.NEWLINE, stringJoiner.toString()); // @formatter:on } @@ -238,7 +238,7 @@ void writeGroupsAndEncoding() throws Exception { + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE - + "}" + OS.NEWLINE, stringWriter.toString()); + + "}" + OS.NEWLINE, stringJoiner.toString()); // @formatter:on } @@ -248,7 +248,7 @@ void writeString() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); + assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -259,7 +259,17 @@ void writeStringAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); + "@String{name = {content}}" + OS.NEWLINE, stringJoiner.toString()); + } + + @Test + void doNotWriteUtf8StringAndEncoding() throws Exception { + when(generalPreferences.getDefaultEncoding()).thenReturn(StandardCharsets.UTF_8); + database.addString(new BibtexString("name", "content")); + + databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); + + assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -291,7 +301,7 @@ void writeEntryWithCustomizedTypeAlsoWritesTypeDeclaration() throws Exception { + "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: customizedtype: req[title;author;date] opt[year;month;publisher]}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -317,14 +327,13 @@ void writeCustomizedTypesInAlphabeticalOrder() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Arrays.asList(entry, otherEntry)); assertEquals( - OS.NEWLINE - + "@Customizedtype{," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + "@Customizedtype{," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + "@Othercustomizedtype{," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: customizedtype: req[title] opt[]}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: othercustomizedtype: req[title] opt[]}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -338,7 +347,34 @@ void roundtripWithArticleMonths() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); + } + + @Test + void roundtripUtf8EncodingHeaderRemoved() throws Exception { + when(generalPreferences.getDefaultEncoding()).thenReturn(StandardCharsets.UTF_8); + // @formatter:off + String bibtexEntry = OS.NEWLINE + "% Encoding: UTF8" + OS.NEWLINE + + OS.NEWLINE + + "@Article{," + OS.NEWLINE + + " author = {Foo Bar}," + OS.NEWLINE + + " journal = {International Journal of Something}," + OS.NEWLINE + + " note = {some note}," + OS.NEWLINE + + " number = {1}," + OS.NEWLINE + + "}" + OS.NEWLINE; + // @formatter:on + ParserResult result = new BibtexParser(importFormatPreferences, fileMonitor).parse(new StringReader(bibtexEntry)); + BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); + databaseWriter.saveDatabase(context); + // @formatter:off + String expected = "@Article{," + OS.NEWLINE + + " author = {Foo Bar}," + OS.NEWLINE + + " journal = {International Journal of Something}," + OS.NEWLINE + + " note = {some note}," + OS.NEWLINE + + " number = {1}," + OS.NEWLINE + + "}" + OS.NEWLINE; + // @formatter:on + assertEquals(expected, stringJoiner.toString()); } @Test @@ -352,7 +388,7 @@ void roundtripWithComplexBib() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); } @Test @@ -366,7 +402,7 @@ void roundtripWithUserComment() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); } @Test @@ -383,7 +419,7 @@ void roundtripWithUserCommentAndEntryChange() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(Path.of("src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib"), encoding), stringWriter.toString()); + assertEquals(Files.readString(Path.of("src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib"), encoding), stringJoiner.toString()); } @Test @@ -403,7 +439,7 @@ void roundtripWithUserCommentBeforeStringAndChange() throws Exception { databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); } @Test @@ -417,7 +453,7 @@ void roundtripWithUnknownMetaData() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); } @Test @@ -431,7 +467,7 @@ void writeSavedSerializationOfEntryIfUnchanged() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.singletonList(entry)); - assertEquals("presaved serialization", stringWriter.toString()); + assertEquals("presaved serialization", stringJoiner.toString()); } @Test @@ -450,7 +486,7 @@ void reformatEntryIfAskedToDoSo() throws Exception { OS.NEWLINE + "@Article{," + OS.NEWLINE + " author = {Mr. author}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -461,7 +497,7 @@ void writeSavedSerializationOfStringIfUnchanged() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("serialization", stringWriter.toString()); + assertEquals("serialization", stringJoiner.toString()); } @Test @@ -473,7 +509,7 @@ void reformatStringIfAskedToDoSo() throws Exception { when(savePreferences.shouldReformatFile()).thenReturn(true); databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); + assertEquals(OS.NEWLINE + "@String{name = {content}}" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -495,7 +531,7 @@ void writeSaveActions() throws Exception { + "journal[title_case]" + OS.NEWLINE + "title[lower_case]" + OS.NEWLINE + ";}" - + OS.NEWLINE, stringWriter.toString()); + + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -510,7 +546,7 @@ void writeSaveOrderConfig() throws Exception { assertEquals(OS.NEWLINE + "@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}" - + OS.NEWLINE, stringWriter.toString()); + + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -524,7 +560,7 @@ void writeCustomKeyPattern() throws Exception { assertEquals(OS.NEWLINE + "@Comment{jabref-meta: keypattern_article:articleTest;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -534,7 +570,7 @@ void writeBiblatexMode() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals(OS.NEWLINE + "@Comment{jabref-meta: databaseType:biblatex;}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -544,7 +580,7 @@ void writeProtectedFlag() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals(OS.NEWLINE + "@Comment{jabref-meta: protectedFlag:true;}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -557,7 +593,7 @@ void writeFileDirectories() throws Exception { assertEquals(OS.NEWLINE + "@Comment{jabref-meta: fileDirectory:\\\\Literature\\\\;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\\\Documents;}" - + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectoryLatex-defaultOwner-user:D:\\\\Latex;}" + OS.NEWLINE, stringWriter.toString()); + + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectoryLatex-defaultOwner-user:D:\\\\Latex;}" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -589,9 +625,7 @@ void writeEntriesSorted() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, database.getEntries()); - assertEquals( - OS.NEWLINE + - "@Article{," + OS.NEWLINE + + assertEquals("@Article{," + OS.NEWLINE + " author = {A}," + OS.NEWLINE + " year = {2010}," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + @@ -605,7 +639,7 @@ void writeEntriesSorted() throws Exception { "}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -632,9 +666,7 @@ void writeEntriesInOriginalOrderWhenNoSaveOrderConfigIsSetInMetadata() throws Ex when(savePreferences.shouldSaveInOriginalOrder()).thenReturn(false); databaseWriter.savePartOfDatabase(bibtexContext, database.getEntries()); - assertEquals( - OS.NEWLINE + - "@Article{," + OS.NEWLINE + + assertEquals("Article{," + OS.NEWLINE + " author = {A}," + OS.NEWLINE + " year = {2010}," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + @@ -647,7 +679,7 @@ void writeEntriesInOriginalOrderWhenNoSaveOrderConfigIsSetInMetadata() throws Ex " year = {2000}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -662,7 +694,7 @@ void trimFieldContents() throws IOException { OS.NEWLINE + "@Article{," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -679,7 +711,7 @@ void newlineAtEndOfAbstractFieldIsDeleted() throws Exception { OS.NEWLINE + "@Article{," + OS.NEWLINE + " abstract = {" + text + "}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringWriter.toString()); + stringJoiner.toString()); } @Test @@ -695,7 +727,7 @@ void roundtripWithContentSelectorsAndUmlauts() throws Exception { databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); - assertEquals(fileContent, stringWriter.toString()); + assertEquals(fileContent, stringJoiner.toString()); } @Test @@ -727,8 +759,8 @@ void saveAlsoSavesSecondModification() throws Exception { entry.setField(StandardField.AUTHOR, "Test"); // write a second time - stringWriter = new StringWriter(); - databaseWriter = new BibtexDatabaseWriter(stringWriter, generalPreferences, savePreferences, entryTypesManager); + stringJoiner = new StringJoiner(OS.NEWLINE); + databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); assertEquals(OS.NEWLINE + @@ -739,7 +771,7 @@ void saveAlsoSavesSecondModification() throws Exception { " number = {1}," + OS.NEWLINE + "}" + OS.NEWLINE + "" + OS.NEWLINE + - "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE, stringWriter.toString()); + "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE, stringJoiner.toString()); } @Test @@ -766,12 +798,10 @@ void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchanged() throws Exception { entry.setChanged(false); // write entry - stringWriter = new StringWriter(); - databaseWriter = new BibtexDatabaseWriter(stringWriter, generalPreferences, savePreferences, entryTypesManager); BibDatabaseContext context = new BibDatabaseContext(firstParse.getDatabase(), firstParse.getMetaData()); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); - assertEquals(bibtexEntry, stringWriter.toString()); + assertEquals(bibtexEntry, stringJoiner.toString()); } @Test @@ -804,11 +834,11 @@ void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchangedEvenInThePrecenseOfSav entry.setChanged(false); // write a second time - stringWriter = new StringWriter(); - databaseWriter = new BibtexDatabaseWriter(stringWriter, generalPreferences, savePreferences, entryTypesManager); + stringJoiner = new StringJoiner(OS.NEWLINE); + databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); // returns tu original entry, not to the last saved one - assertEquals(bibtexEntry, stringWriter.toString()); + assertEquals(bibtexEntry, stringJoiner.toString()); } } diff --git a/src/test/resources/testbib/articleWithMonths.bib b/src/test/resources/testbib/articleWithMonths.bib index 6898d754ade..0664116e9c9 100644 --- a/src/test/resources/testbib/articleWithMonths.bib +++ b/src/test/resources/testbib/articleWithMonths.bib @@ -1,4 +1,3 @@ -% Encoding: UTF-8 @Article{constant, month = apr } diff --git a/src/test/resources/testbib/unknownMetaData.bib b/src/test/resources/testbib/unknownMetaData.bib index d32a1b9a382..69ed04d5466 100644 --- a/src/test/resources/testbib/unknownMetaData.bib +++ b/src/test/resources/testbib/unknownMetaData.bib @@ -1,5 +1,3 @@ -% Encoding: UTF-8 - @Article{author2017title, author = {Author}, title = {Title}, From 4ed85cf49ebf93b8268a0a7f573837827b3b6b88 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 11 Nov 2021 23:57:15 +0100 Subject: [PATCH 02/10] Introduce BibWriter --- CHANGELOG.md | 4 +- .../org/jabref/benchmarks/Benchmarks.java | 5 +- .../org/jabref/cli/ArgumentProcessor.java | 7 +- .../org/jabref/gui/SendAsEMailAction.java | 8 +- .../org/jabref/gui/entryeditor/SourceTab.java | 11 +- .../gui/exporter/SaveDatabaseAction.java | 8 +- .../autosaveandbackup/BackupManager.java | 7 +- .../jabref/logic/bibtex/BibEntryWriter.java | 66 ++++---- .../jabref/logic/crawler/StudyRepository.java | 8 +- .../logic/exporter/BibDatabaseWriter.java | 17 +- .../org/jabref/logic/exporter/BibWriter.java | 68 ++++++++ .../logic/exporter/BibtexDatabaseWriter.java | 85 +++++----- .../exporter/EmbeddedBibFilePdfExporter.java | 7 +- .../importer/fileformat/BibtexParser.java | 48 +++++- .../jabref/model/database/BibDatabase.java | 16 ++ .../jabref/preferences/NewLineSeparator.java | 2 +- .../logic/bibtex/BibEntryWriterTest.java | 153 ++++++------------ .../exporter/BibtexDatabaseWriterTest.java | 114 ++++++------- .../resources/testbib/bibWithUserComments.bib | 2 - src/test/resources/testbib/complex.bib | 2 - 20 files changed, 349 insertions(+), 289 deletions(-) create mode 100644 src/main/java/org/jabref/logic/exporter/BibWriter.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ae43c6a71fb..eadeffe897e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,9 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We improved the Drag and Drop behavior in the "Customize Entry Types" Dialog [#6338](https://github.com/JabRef/jabref/issues/6338) - When determining the URL of an ArXiV eprint, the URL now points to the version [#8149](https://github.com/JabRef/jabref/pull/8149) - We Included all standard fields with citation key when exporting to Old OpenOffice/LibreOffice Calc Format [#8176](https://github.com/JabRef/jabref/pull/8176) -- In case the database is encoded with `UTF8`, the `% Encoding` marker is not written any more +- In case the database is encoded with `UTF8`, the `% Encoding` marker is not written anymore +- The written `.bib` file has the same line endings [#390](https://github.com/koppor/jabref/issues/390) +- The written file always has a final line break - We present options to manually enter an article or return to the New Entry menu when the fetcher DOI fails to find an entry for an ID [#7870](https://github.com/JabRef/jabref/issues/7870) ### Fixed diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index 46ba83b2f89..b0073114ac5 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import org.jabref.gui.Globals; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SavePreferences; import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter; @@ -17,6 +18,7 @@ import org.jabref.logic.layout.format.HTMLChars; import org.jabref.logic.layout.format.LatexToUnicodeFormatter; import org.jabref.logic.search.SearchQuery; +import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -77,7 +79,8 @@ public void init() throws Exception { private StringWriter getOutputWriter() throws IOException { StringWriter outputWriter = new StringWriter(); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(outputWriter, mock(GeneralPreferences.class), mock(SavePreferences.class), new BibEntryTypesManager()); + BibWriter bibWriter = new BibWriter(outputWriter, OS.NEWLINE); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, mock(GeneralPreferences.class), mock(SavePreferences.class), new BibEntryTypesManager()); databaseWriter.savePartOfDatabase(new BibDatabaseContext(database, new MetaData()), database.getEntries()); return outputWriter; } diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index e36bce36ca8..74b9c84d3c2 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -10,7 +10,6 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; -import java.util.StringJoiner; import java.util.Vector; import java.util.prefs.BackingStoreException; @@ -23,6 +22,7 @@ import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibDatabaseWriter; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; import org.jabref.logic.exporter.Exporter; @@ -532,10 +532,9 @@ private void saveDatabase(BibDatabase newBase, String subName) { GeneralPreferences generalPreferences = preferencesService.getGeneralPreferences(); SavePreferences savePreferences = preferencesService.getSavePreferences(); AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), generalPreferences.getDefaultEncoding()); - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, Globals.entryTypesManager); + BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, Globals.entryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); - fileWriter.write(stringJoiner.toString()); // Show just a warning message if encoding did not work for all characters: if (fileWriter.hasEncodingProblems()) { diff --git a/src/main/java/org/jabref/gui/SendAsEMailAction.java b/src/main/java/org/jabref/gui/SendAsEMailAction.java index 30df8410bd2..6ef1150cbe6 100644 --- a/src/main/java/org/jabref/gui/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/SendAsEMailAction.java @@ -2,11 +2,11 @@ import java.awt.Desktop; import java.io.IOException; +import java.io.StringWriter; import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.StringJoiner; import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.actions.ActionHelper; @@ -15,6 +15,7 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.OS; import org.jabref.logic.util.io.FileUtil; @@ -72,7 +73,8 @@ private String sendEmail() throws Exception { return Localization.lang("This operation requires one or more entries to be selected."); } - StringJoiner rawEntries = new StringJoiner(OS.NEWLINE); + StringWriter rawEntries = new StringWriter(); + BibWriter bibWriter = new BibWriter(rawEntries, OS.NEWLINE); BibDatabaseContext databaseContext = stateManager.getActiveDatabase().get(); List entries = stateManager.getSelectedEntries(); @@ -81,7 +83,7 @@ private String sendEmail() throws Exception { for (BibEntry entry : entries) { try { - bibtexEntryWriter.write(entry, rawEntries, databaseContext.getMode()); + bibtexEntryWriter.write(entry, bibWriter, databaseContext.getMode()); } catch (IOException e) { LOGGER.warn("Problem creating BibTeX file for mailing.", e); } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 18a8948aece..71b742190bf 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -2,10 +2,10 @@ import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.StringJoiner; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,6 +38,7 @@ import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.bibtex.FieldWriterPreferences; import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; @@ -129,11 +130,11 @@ private void highlightSearchPattern() { } private String getSourceString(BibEntry entry, BibDatabaseMode type, FieldWriterPreferences fieldWriterPreferences) throws IOException { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); + StringWriter writer = new StringWriter(); + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); - new BibEntryWriter(fieldWriter, Globals.entryTypesManager).writeWithoutPrependedNewlines(entry, stringJoiner, type); - - return stringJoiner.toString(); + new BibEntryWriter(fieldWriter, Globals.entryTypesManager).write(entry, bibWriter, type); + return writer.toString(); } /* Work around for different input methods. diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index f074ff51f07..8b38bfbcca2 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -7,7 +7,6 @@ import java.nio.file.Path; import java.util.Optional; import java.util.Set; -import java.util.StringJoiner; import java.util.stream.Collectors; import javafx.scene.control.ButtonBar; @@ -25,6 +24,7 @@ import org.jabref.logic.autosaveandbackup.AutosaveManager; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SaveException; import org.jabref.logic.exporter.SavePreferences; @@ -231,8 +231,8 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, SavePreferences savePreferences = this.preferences.getSavePreferences() .withSaveType(saveType); try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, savePreferences.shouldMakeBackup())) { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); + BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); if (selectedOnly) { databaseWriter.savePartOfDatabase(libraryTab.getBibDatabaseContext(), libraryTab.getSelectedEntries()); @@ -240,8 +240,6 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, databaseWriter.saveDatabase(libraryTab.getBibDatabaseContext()); } - fileWriter.write(stringJoiner.toString()); - libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); if (fileWriter.hasEncodingProblems()) { diff --git a/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java index dab6f6b1de7..7ec9727a3af 100644 --- a/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java @@ -9,12 +9,12 @@ import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.StringJoiner; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SavePreferences; import org.jabref.logic.util.CoarseChangeFilter; @@ -139,10 +139,9 @@ private void performBackup(Path backupPath) { SavePreferences savePreferences = preferences.getSavePreferences() .withMakeBackup(false); Writer writer = new AtomicFileWriter(backupPath, charset); - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager) + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); + new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager) .saveDatabase(bibDatabaseContext); - writer.write(stringJoiner.toString()); } catch (IOException e) { logIfCritical(backupPath, e); } diff --git a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java index 9feb8426c48..677e0cf746b 100644 --- a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java +++ b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java @@ -10,12 +10,12 @@ import java.util.Optional; import java.util.Set; import java.util.SortedSet; -import java.util.StringJoiner; import java.util.TreeSet; import java.util.function.Predicate; import java.util.stream.Collectors; import org.jabref.logic.TypedBibEntry; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; @@ -37,14 +37,15 @@ public BibEntryWriter(FieldWriter fieldWriter, BibEntryTypesManager entryTypesMa } public String serializeAll(List entries, BibDatabaseMode databaseMode) throws IOException { - StringJoiner writer = new StringJoiner(OS.NEWLINE); + StringWriter writer = new StringWriter(); + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); for (BibEntry entry : entries) { - write(entry, writer, databaseMode); + write(entry, bibWriter, databaseMode); } return writer.toString(); } - public void write(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode) throws IOException { + public void write(BibEntry entry, BibWriter out, BibDatabaseMode bibDatabaseMode) throws IOException { write(entry, out, bibDatabaseMode, false); } @@ -56,46 +57,37 @@ public void write(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseM * @param bibDatabaseMode The database mode (bibtex or biblatex) * @param reformat Should the entry be in any case, even if no change occurred? */ - public void write(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode, Boolean reformat) throws IOException { + public void write(BibEntry entry, BibWriter out, BibDatabaseMode bibDatabaseMode, Boolean reformat) throws IOException { // if the entry has not been modified, write it as it was if (!reformat && !entry.hasChanged()) { - out.add(entry.getParsedSerialization()); + out.write(entry.getParsedSerialization()); + out.finishBlock(); return; } writeUserComments(entry, out); writeRequiredFieldsFirstRemainingFieldsSecond(entry, out, bibDatabaseMode); + out.finishBlock(); } - private void writeUserComments(BibEntry entry, StringJoiner out) throws IOException { + private void writeUserComments(BibEntry entry, BibWriter out) throws IOException { String userComments = entry.getUserComments(); if (!userComments.isEmpty()) { - out.add(userComments); + out.write(userComments); } } - public void writeWithoutPrependedNewlines(BibEntry entry, StringJoiner out, BibDatabaseMode bibDatabaseMode) throws IOException { - // if the entry has not been modified, write it as it was - if (!entry.hasChanged()) { - out.add(entry.getParsedSerialization().trim()); - return; - } - - writeRequiredFieldsFirstRemainingFieldsSecond(entry, out, bibDatabaseMode); - } - /** * Writes fields in the order of requiredFields, optionalFields and other fields, but does not sort the fields. */ - private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, StringJoiner out, + private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, BibWriter out, BibDatabaseMode bibDatabaseMode) throws IOException { // Write header with type and bibtex-key TypedBibEntry typedEntry = new TypedBibEntry(entry, bibDatabaseMode); - StringWriter writer = new StringWriter(); - writer.write('@' + typedEntry.getTypeForDisplay() + '{'); + out.write('@' + typedEntry.getTypeForDisplay() + '{'); - writeKeyField(entry, writer); + writeKeyField(entry, out); Set written = new HashSet<>(); written.add(InternalField.KEY_FIELD); @@ -112,7 +104,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Strin .collect(Collectors.toList()); for (Field field : requiredFields) { - writeField(entry, writer, field, indentation); + writeField(entry, out, field, indentation); } // Then optional fields @@ -124,7 +116,7 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Strin .collect(Collectors.toList()); for (Field field : optionalFields) { - writeField(entry, writer, field, indentation); + writeField(entry, out, field, indentation); } written.addAll(requiredFields); @@ -137,18 +129,16 @@ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Strin .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Field::getName)))); for (Field field : remainingFields) { - writeField(entry, writer, field, indentation); + writeField(entry, out, field, indentation); } // Finally, end the entry. - writer.write('}'); - writer.write(OS.NEWLINE); - out.add(writer.toString()); + out.writeLine("}"); } - private void writeKeyField(BibEntry entry, StringWriter out) throws IOException { + private void writeKeyField(BibEntry entry, BibWriter out) throws IOException { String keyField = StringUtil.shaveString(entry.getCitationKey().orElse("")); - out.write(keyField + ',' + OS.NEWLINE); + out.writeLine(keyField + ','); } /** @@ -159,9 +149,9 @@ private void writeKeyField(BibEntry entry, StringWriter out) throws IOException * @param field the field * @throws IOException In case of an IO error */ - private void writeField(BibEntry entry, StringWriter out, Field field, int indentation) throws IOException { + private void writeField(BibEntry entry, BibWriter out, Field field, int indentation) throws IOException { Optional value = entry.getField(field); - // only write field if is is not empty + // only write field if it is not empty // field.ifPresent does not work as an IOException may be thrown if (value.isPresent() && !value.get().trim().isEmpty()) { out.write(" " + getFormattedFieldName(field, indentation)); @@ -170,7 +160,7 @@ private void writeField(BibEntry entry, StringWriter out, Field field, int inden } catch (InvalidFieldValueException ex) { throw new IOException("Error in field '" + field + " of entry " + entry.getCitationKey().orElse("") + "': " + ex.getMessage(), ex); } - out.write(',' + OS.NEWLINE); + out.writeLine(","); } } @@ -187,19 +177,17 @@ private int getLengthOfLongestFieldName(BibEntry entry) { /** * Get display version of a entry field. *

- * BibTeX is case-insensitive therefore there is no difference between: - * howpublished, HOWPUBLISHED, HowPublished, etc. + * BibTeX is case-insensitive therefore there is no difference between: howpublished, HOWPUBLISHED, HowPublished, etc. *

- * The was a long discussion about how JabRef should write the fields. - * See https://github.com/JabRef/jabref/issues/116 + * There was a long discussion about how JabRef should write the fields. See https://github.com/JabRef/jabref/issues/116 *

* The team decided to do the biblatex way and use lower case for the field names. * * @param field The name of the field. * @return The display version of the field name. */ - private String getFormattedFieldName(Field field, int intendation) { + private String getFormattedFieldName(Field field, int intention) { String fieldName = field.getName(); - return fieldName.toLowerCase(Locale.ROOT) + StringUtil.repeatSpaces(intendation - fieldName.length()) + " = "; + return fieldName.toLowerCase(Locale.ROOT) + StringUtil.repeatSpaces(intention - fieldName.length()) + " = "; } } diff --git a/src/main/java/org/jabref/logic/crawler/StudyRepository.java b/src/main/java/org/jabref/logic/crawler/StudyRepository.java index 59842cfda5d..e876d94f0fa 100644 --- a/src/main/java/org/jabref/logic/crawler/StudyRepository.java +++ b/src/main/java/org/jabref/logic/crawler/StudyRepository.java @@ -10,13 +10,12 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.List; -import java.util.StringJoiner; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.database.DatabaseMerger; -import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SaveException; import org.jabref.logic.exporter.SavePreferences; @@ -418,10 +417,9 @@ private void writeResultToFile(Path pathToFile, BibDatabase entries) throws IOEx Files.createFile(pathToFile); } try (Writer fileWriter = new FileWriter(pathToFile.toFile())) { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, bibEntryTypesManager); + BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, bibEntryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(entries)); - fileWriter.write(stringJoiner.toString()); } catch (UnsupportedCharsetException ex) { throw new SaveException(Localization.lang("Character encoding '%0' is not supported.", generalPreferences.getDefaultEncoding().displayName()), ex); } catch (IOException ex) { diff --git a/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java b/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java index e580dc95657..f62215b43ae 100644 --- a/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java +++ b/src/main/java/org/jabref/logic/exporter/BibDatabaseWriter.java @@ -12,7 +12,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.StringJoiner; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,14 +46,14 @@ public abstract class BibDatabaseWriter { private static final Pattern REFERENCE_PATTERN = Pattern.compile("(#[A-Za-z]+#)"); // Used to detect string references in strings - protected final StringJoiner blockJoiner; + protected final BibWriter bibWriter; protected final GeneralPreferences generalPreferences; protected final SavePreferences savePreferences; protected final List saveActionsFieldChanges = new ArrayList<>(); protected final BibEntryTypesManager entryTypesManager; - public BibDatabaseWriter(StringJoiner blockJoiner, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { - this.blockJoiner = Objects.requireNonNull(blockJoiner); + public BibDatabaseWriter(BibWriter bibWriter, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { + this.bibWriter = Objects.requireNonNull(bibWriter); this.generalPreferences = generalPreferences; this.savePreferences = savePreferences; this.entryTypesManager = entryTypesManager; @@ -174,14 +173,13 @@ public void savePartOfDatabase(BibDatabaseContext bibDatabaseContext, List typesToWrite = new TreeSet<>(); - // Some file formats write something at the start of the file (like the encoding) if (savePreferences.getSaveType() != SavePreferences.DatabaseSaveType.PLAIN_BIBTEX) { writeProlog(bibDatabaseContext, generalPreferences.getDefaultEncoding()); } + bibWriter.finishBlock(); + // Write preamble if there is one. writePreamble(bibDatabaseContext.getDatabase().getPreamble().orElse("")); @@ -197,6 +195,9 @@ public void savePartOfDatabase(BibDatabaseContext bibDatabaseContext, List typesToWrite = new TreeSet<>(); + for (BibEntry entry : sortedEntries) { // Check if we must write the type definition for this // entry, as well. Our criterion is that all non-standard @@ -275,6 +276,8 @@ private void writeStrings(BibDatabase database) throws IOException { } } } + + bibWriter.finishBlock(); } protected void writeString(BibtexString bibtexString, Map remaining, int maxKeyLength) diff --git a/src/main/java/org/jabref/logic/exporter/BibWriter.java b/src/main/java/org/jabref/logic/exporter/BibWriter.java new file mode 100644 index 00000000000..1e3e364ff60 --- /dev/null +++ b/src/main/java/org/jabref/logic/exporter/BibWriter.java @@ -0,0 +1,68 @@ +package org.jabref.logic.exporter; + +import java.io.IOException; +import java.io.Writer; + +/** + * Class to write to a .bib file. Used by {@link BibtexDatabaseWriter} + */ +public class BibWriter { + + private final String newline; + private final Writer writer; + + private boolean precedingNewLineRequired = false; + private boolean somethingWasWritten = false; + private boolean lastWriteWasNewline = false; + + /** + * @param newline the string used for a line break + */ + public BibWriter(Writer writer, String newline) { + this.writer = writer; + this.newline = newline; + } + + /** + * Writes the given string. The newlines of the given string are converted to the newline set for this clas + */ + public void write(String string) throws IOException { + if (precedingNewLineRequired) { + writer.write(newline); + precedingNewLineRequired = false; + } + string = string.replace("\r\n", "\n").replace("\n", newline); + writer.write(string); + lastWriteWasNewline = string.endsWith(newline); + somethingWasWritten = true; + } + + /** + * Writes the given string and finishes it with a line break + */ + public void writeLine(String string) throws IOException { + this.write(string); + this.finishLine(); + } + + /** + * Finishes a line + */ + public void finishLine() throws IOException { + this.write(newline); + } + + /** + * Finishes a block + */ + public void finishBlock() throws IOException { + if (!somethingWasWritten) { + return; + } + if (!lastWriteWasNewline) { + this.finishLine(); + } + this.somethingWasWritten = false; + this.precedingNewLineRequired = true; + } +} diff --git a/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java b/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java index ce095c9deb2..252bb535a76 100644 --- a/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java +++ b/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java @@ -1,16 +1,14 @@ package org.jabref.logic.exporter; import java.io.IOException; -import java.io.StringWriter; +import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Map; -import java.util.StringJoiner; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; @@ -29,38 +27,40 @@ public class BibtexDatabaseWriter extends BibDatabaseWriter { private static final String COMMENT_PREFIX = "@Comment"; private static final String PREAMBLE_PREFIX = "@Preamble"; - public BibtexDatabaseWriter(StringJoiner blockJoiner, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { - super(blockJoiner, generalPreferences, savePreferences, entryTypesManager); + public BibtexDatabaseWriter(BibWriter bibWriter, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { + super(bibWriter, generalPreferences, savePreferences, entryTypesManager); + } + + public BibtexDatabaseWriter(Writer writer, String newline, GeneralPreferences generalPreferences, SavePreferences savePreferences, BibEntryTypesManager entryTypesManager) { + super(new BibWriter(writer, newline), generalPreferences, savePreferences, entryTypesManager); } @Override protected void writeEpilogue(String epilogue) throws IOException { if (!StringUtil.isNullOrEmpty(epilogue)) { - blockJoiner.add(epilogue); + bibWriter.write(epilogue); + bibWriter.finishBlock(); } } @Override protected void writeMetaDataItem(Map.Entry metaItem) throws IOException { - StringWriter writer = new StringWriter(); - writer.write(COMMENT_PREFIX + "{"); - writer.write(MetaData.META_FLAG); - writer.write(metaItem.getKey()); - writer.write(":"); - writer.write(metaItem.getValue()); - writer.write("}"); - writer.write(OS.NEWLINE); - blockJoiner.add(writer.toString()); + bibWriter.write(COMMENT_PREFIX + "{"); + bibWriter.write(MetaData.META_FLAG); + bibWriter.write(metaItem.getKey()); + bibWriter.write(":"); + bibWriter.write(metaItem.getValue()); + bibWriter.write("}"); + bibWriter.finishBlock(); } @Override protected void writePreamble(String preamble) throws IOException { if (!StringUtil.isNullOrEmpty(preamble)) { - StringWriter writer = new StringWriter(); - writer.write(PREAMBLE_PREFIX + "{"); - writer.write(preamble); - writer.write('}' + OS.NEWLINE); - blockJoiner.add(writer.toString()); + bibWriter.write(PREAMBLE_PREFIX + "{"); + bibWriter.write(preamble); + bibWriter.writeLine("}"); + bibWriter.finishBlock(); } } @@ -68,44 +68,41 @@ protected void writePreamble(String preamble) throws IOException { protected void writeString(BibtexString bibtexString, int maxKeyLength) throws IOException { // If the string has not been modified, write it back as it was if (!savePreferences.shouldReformatFile() && !bibtexString.hasChanged()) { - blockJoiner.add(bibtexString.getParsedSerialization()); + bibWriter.write(bibtexString.getParsedSerialization()); return; } // Write user comments String userComments = bibtexString.getUserComments(); if (!userComments.isEmpty()) { - blockJoiner.add(userComments + OS.NEWLINE); + bibWriter.writeLine(userComments); } - StringWriter writer = new StringWriter(); - writer.write(STRING_PREFIX + "{" + bibtexString.getName() + StringUtil + bibWriter.write(STRING_PREFIX + "{" + bibtexString.getName() + StringUtil .repeatSpaces(maxKeyLength - bibtexString.getName().length()) + " = "); if (bibtexString.getContent().isEmpty()) { - writer.write("{}"); + bibWriter.write("{}"); } else { try { String formatted = new FieldWriter(savePreferences.getFieldWriterPreferences()) .write(InternalField.BIBTEX_STRING, bibtexString.getContent() ); - writer.write(formatted); + bibWriter.write(formatted); } catch (InvalidFieldValueException ex) { throw new IOException(ex); } } - writer.write("}" + OS.NEWLINE); - blockJoiner.add(writer.toString()); + bibWriter.writeLine("}"); + bibWriter.finishBlock(); } @Override protected void writeEntryTypeDefinition(BibEntryType customType) throws IOException { - StringWriter writer = new StringWriter(); - writer.write(COMMENT_PREFIX + "{"); - writer.write(BibEntryTypesManager.serialize(customType)); - writer.write("}"); - writer.write(OS.NEWLINE); - blockJoiner.add(writer.toString()); + bibWriter.write(COMMENT_PREFIX + "{"); + bibWriter.write(BibEntryTypesManager.serialize(customType)); + bibWriter.writeLine("}"); + bibWriter.finishBlock(); } @Override @@ -115,27 +112,21 @@ protected void writeProlog(BibDatabaseContext bibDatabaseContext, Charset encodi } // Writes the file encoding information. - StringWriter writer = new StringWriter(); - writer.write("% "); - writer.write(SavePreferences.ENCODING_PREFIX + encoding); - writer.write(OS.NEWLINE); - blockJoiner.add(writer.toString()); + bibWriter.write("% "); + bibWriter.writeLine(SavePreferences.ENCODING_PREFIX + encoding); } @Override protected void writeDatabaseID(String sharedDatabaseID) throws IOException { - StringWriter writer = new StringWriter(); - writer.write("% " + - DATABASE_ID_PREFIX + - " " + - sharedDatabaseID + - OS.NEWLINE); - blockJoiner.add(writer.toString()); + bibWriter.write("% "); + bibWriter.write(DATABASE_ID_PREFIX); + bibWriter.write(" "); + bibWriter.writeLine(sharedDatabaseID); } @Override protected void writeEntry(BibEntry entry, BibDatabaseMode mode) throws IOException { BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(savePreferences.getFieldWriterPreferences()), entryTypesManager); - bibtexEntryWriter.write(entry, blockJoiner, mode, savePreferences.shouldReformatFile()); + bibtexEntryWriter.write(entry, bibWriter, mode, savePreferences.shouldReformatFile()); } } diff --git a/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java b/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java index 5693879e62c..ea8782474c9 100644 --- a/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java +++ b/src/main/java/org/jabref/logic/exporter/EmbeddedBibFilePdfExporter.java @@ -3,6 +3,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -10,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.StringJoiner; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; @@ -102,11 +102,12 @@ private void embedBibTex(String bibTeX, Path file, Charset encoding) throws IOEx } private String getBibString(List entries) throws IOException { - StringJoiner stringWriter = new StringJoiner(OS.NEWLINE); + StringWriter stringWriter = new StringWriter(); + BibWriter bibWriter = new BibWriter(stringWriter, OS.NEWLINE); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(fieldWriterPreferences); BibEntryWriter bibEntryWriter = new BibEntryWriter(fieldWriter, bibEntryTypesManager); for (BibEntry entry : entries) { - bibEntryWriter.writeWithoutPrependedNewlines(entry, stringWriter, bibDatabaseMode); + bibEntryWriter.write(entry, bibWriter, bibDatabaseMode); } return stringWriter.toString(); } diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index c9dc5d90022..ce28718dabb 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -6,6 +6,7 @@ import java.io.InputStreamReader; import java.io.PushbackReader; import java.io.Reader; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Deque; @@ -29,6 +30,7 @@ import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.util.MetaDataParser; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.KeyCollisionException; import org.jabref.model.entry.BibEntry; @@ -71,6 +73,7 @@ public class BibtexParser implements Parser { private PushbackReader pushbackReader; private BibDatabase database; private Set entryTypes; + private String newLine = OS.NEWLINE; private boolean eof; private int line = 1; private ParserResult parserResult; @@ -132,7 +135,9 @@ public ParserResult parse(Reader in) throws IOException { Objects.requireNonNull(in); pushbackReader = new PushbackReader(in, BibtexParser.LOOKAHEAD); - // Bibtex related contents. + determineNewLine(); + + // BibTeX related contents. initializeParserResult(); parseDatabaseID(); @@ -142,6 +147,25 @@ public ParserResult parse(Reader in) throws IOException { return parseFileContent(); } + private void determineNewLine() throws IOException { + StringWriter stringWriter = new StringWriter(BibtexParser.LOOKAHEAD); + int i = 0; + int currentChar; + do { + currentChar = pushbackReader.read(); + stringWriter.append((char) currentChar); + i++; + } while ((i(); // To store custom entry types parsed. @@ -184,7 +208,7 @@ private ParserResult parseFileContent() throws IOException { if ("preamble".equals(entryType)) { database.setPreamble(parsePreamble()); - // Consume new line which signals end of preamble + // Consume a new line which separates the preamble from the next part (if the file was written with JabRef) skipOneNewline(); // the preamble is saved verbatim anyways, so the text read so far can be dropped dumpTextReadSoFarToString(); @@ -236,8 +260,21 @@ private void parseAndAddEntry(String type) { // store comments collected without type definition entry.setCommentsBeforeEntry( commentsAndEntryTypeDefinition.substring(0, commentsAndEntryTypeDefinition.lastIndexOf('@'))); + // store complete parsed serialization (comments, type definition + type contents) - entry.setParsedSerialization(commentsAndEntryTypeDefinition + dumpTextReadSoFarToString()); + + String parsedSerialization = commentsAndEntryTypeDefinition + dumpTextReadSoFarToString(); + // normalize new lines + parsedSerialization = parsedSerialization.replace("\r\n", "\n"); + // strip newlines from beginning and and (we need to keep whitespaces, thus trim() cannot be used) + while (parsedSerialization.startsWith("\n")) { + parsedSerialization = parsedSerialization.substring(1); + } + while (parsedSerialization.endsWith("\n")) { + parsedSerialization = parsedSerialization.substring(0, parsedSerialization.length()-1); + } + parsedSerialization = parsedSerialization.replace("\n", newLine); + entry.setParsedSerialization(parsedSerialization); database.insertEntry(entry); } catch (IOException ex) { @@ -505,7 +542,10 @@ private BibtexString parseString() throws IOException { private String parsePreamble() throws IOException { skipWhitespace(); - return parseBracketedText(); + String result = parseBracketedText(); + // also "include" the newline in the preamble + skipOneNewline(); + return result; } private BibEntry parseEntry(String entryType) throws IOException { diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 4349e371db9..53ee6c12f49 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -21,6 +21,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import org.jabref.logic.util.OS; import org.jabref.model.database.event.EntriesAddedEvent; import org.jabref.model.database.event.EntriesRemovedEvent; import org.jabref.model.entry.BibEntry; @@ -59,8 +60,16 @@ public class BibDatabase { // All file contents below the last entry in the file private String epilog = ""; + private String sharedDatabaseID; + private String newLine = OS.NEWLINE; + + public BibDatabase(List entries, String newLine) { + this(entries); + this.newLine = newLine; + } + public BibDatabase(List entries) { this(); insertEntries(entries); @@ -622,4 +631,11 @@ public long getNumberOfCitationKeyOccurrences(String key) { public boolean isDuplicateCitationKeyExisting(String key) { return getNumberOfCitationKeyOccurrences(key) > 1; } + + /** + * Returns the string used to indicate a linebreak + */ + public String getNewLine() { + return newLine; + } } diff --git a/src/main/java/org/jabref/preferences/NewLineSeparator.java b/src/main/java/org/jabref/preferences/NewLineSeparator.java index 6870cfc50b9..3a089bf331d 100644 --- a/src/main/java/org/jabref/preferences/NewLineSeparator.java +++ b/src/main/java/org/jabref/preferences/NewLineSeparator.java @@ -1,7 +1,7 @@ package org.jabref.preferences; /** - * An enum which contains the possible NewLineSeperators + * An enum which contains the possible NewLineSeparators * Possible are CR ("\n"), LF ("\r") and the windows standard CR/LF. */ public enum NewLineSeparator { diff --git a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java index 292d6508310..4176bcf0278 100644 --- a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java +++ b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java @@ -2,12 +2,13 @@ import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.StringJoiner; +import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; @@ -35,20 +36,20 @@ class BibEntryWriterTest { private static ImportFormatPreferences importFormatPreferences; - private BibEntryWriter writer; + private StringWriter stringWriter = new StringWriter(); + private BibWriter bibWriter = new BibWriter(stringWriter, OS.NEWLINE); + private BibEntryWriter bibEntryWriter; private final FileUpdateMonitor fileMonitor = new DummyFileUpdateMonitor(); @BeforeEach void setUpWriter() { importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); FieldWriterPreferences fieldWriterPreferences = mock(FieldWriterPreferences.class, Answers.RETURNS_DEEP_STUBS); - writer = new BibEntryWriter(new FieldWriter(fieldWriterPreferences), new BibEntryTypesManager()); + bibEntryWriter = new BibEntryWriter(new FieldWriter(fieldWriterPreferences), new BibEntryTypesManager()); } @Test void testSerialization() throws IOException { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibEntry entry = new BibEntry(StandardEntryType.Article); // set a required field entry.setField(StandardField.AUTHOR, "Foo Bar"); @@ -57,9 +58,7 @@ void testSerialization() throws IOException { entry.setField(StandardField.NUMBER, "1"); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + @@ -70,7 +69,7 @@ void testSerialization() throws IOException { "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test @@ -83,12 +82,8 @@ void writeOtherTypeTest() throws Exception { entry.setField(StandardField.COMMENT, "testentry"); entry.setCitationKey("test"); - // write out bibtex stringJoiner - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); - - assertEquals(expected, actual); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); + assertEquals(expected, stringWriter.toString()); } @Test @@ -97,21 +92,18 @@ void writeEntryWithFile() throws Exception { LinkedFile file = new LinkedFile("test", Path.of("/home/uers/test.pdf"), "PDF"); entry.addFile(file); - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); assertEquals(OS.NEWLINE + "@Article{," + OS.NEWLINE + " file = {test:/home/uers/test.pdf:PDF}," + OS.NEWLINE - + "}" + OS.NEWLINE, stringJoiner.toString()); + + "}" + OS.NEWLINE, stringWriter.toString()); } @Test void writeEntryWithOrField() throws Exception { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibEntry entry = new BibEntry(StandardEntryType.InBook); // set an required OR field (author/editor) entry.setField(StandardField.EDITOR, "Foo Bar"); @@ -120,9 +112,7 @@ void writeEntryWithOrField() throws Exception { entry.setField(StandardField.NUMBER, "1"); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expected = OS.NEWLINE + "@InBook{," + OS.NEWLINE + @@ -133,13 +123,11 @@ void writeEntryWithOrField() throws Exception { "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test void writeEntryWithOrFieldBothFieldsPresent() throws Exception { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibEntry entry = new BibEntry(StandardEntryType.InBook); // set an required OR field with both fields(author/editor) entry.setField(StandardField.AUTHOR, "Foo Thor"); @@ -149,9 +137,7 @@ void writeEntryWithOrFieldBothFieldsPresent() throws Exception { entry.setField(StandardField.NUMBER, "1"); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expected = OS.NEWLINE + "@InBook{," + OS.NEWLINE + @@ -163,7 +149,7 @@ void writeEntryWithOrFieldBothFieldsPresent() throws Exception { "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test @@ -176,13 +162,9 @@ void writeReallyUnknownTypeTest() throws Exception { entry.setType(new UnknownEntryType("ReallyUnknownType")); entry.setField(StandardField.COMMENT, "testentry"); entry.setCitationKey("test"); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); - - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test @@ -202,11 +184,9 @@ void roundTripTest() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(bibtexEntry, actual); + assertEquals(bibtexEntry, stringWriter.toString()); } @Test @@ -226,11 +206,9 @@ void roundTripWithPrependingNewlines() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(bibtexEntry, actual); + assertEquals(bibtexEntry, stringWriter.toString()); } @Test @@ -253,9 +231,7 @@ void roundTripWithModification() throws IOException { entry.setField(StandardField.AUTHOR, "BlaBla"); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -265,7 +241,7 @@ void roundTripWithModification() throws IOException { " number = {1}," + OS.NEWLINE + "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, bibWriter.toString()); } @Test @@ -289,9 +265,7 @@ void roundTripWithCamelCasingInTheOriginalEntryAndResultInLowerCase() throws IOE entry.setField(StandardField.AUTHOR, "BlaBla"); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -302,7 +276,7 @@ void roundTripWithCamelCasingInTheOriginalEntryAndResultInLowerCase() throws IOE " howpublished = {asdf}," + OS.NEWLINE + "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test @@ -326,9 +300,7 @@ void testEntryTypeChange() throws IOException { entry.setType(StandardEntryType.InProceedings); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expectedNewEntry = OS.NEWLINE + "@InProceedings{test," + OS.NEWLINE + @@ -339,7 +311,7 @@ void testEntryTypeChange() throws IOException { " journal = {International Journal of Something}," + OS.NEWLINE + "}" + OS.NEWLINE; // @formatter:on - assertEquals(expectedNewEntry, actual); + assertEquals(expectedNewEntry, stringWriter.toString()); } @Test @@ -359,9 +331,8 @@ void roundTripWithAppendedNewlines() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); + String actual = bibWriter.toString(); // Only one appending newline is written by the writer, the rest by FileActions. So, these should be removed here. assertEquals(bibtexEntry.substring(0, bibtexEntry.length() - 1), actual); @@ -392,10 +363,11 @@ private String testSingleWrite(String bibtexEntry) throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + StringWriter writer = new StringWriter(); + BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); + String actual = writer.toString(); assertEquals(bibtexEntry, actual); return actual; } @@ -421,11 +393,9 @@ void monthFieldSpecialSyntax() throws IOException { assertEquals("#mar#", entry.getField(StandardField.MONTH).get()); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(bibtexEntry, actual); + assertEquals(bibtexEntry, stringWriter.toString()); } @Test @@ -435,14 +405,13 @@ void constantMonthApril() throws Exception { // enable writing entry.setChanged(true); - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); assertEquals(OS.NEWLINE + "@Misc{," + OS.NEWLINE + " month = apr," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -452,14 +421,13 @@ void monthApril() throws Exception { // enable writing entry.setChanged(true); - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); assertEquals(OS.NEWLINE + "@Misc{," + OS.NEWLINE + " month = {apr}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -482,9 +450,7 @@ void addFieldWithLongerLength() throws IOException { entry.setField(StandardField.HOWPUBLISHED, "asdf"); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -495,36 +461,30 @@ void addFieldWithLongerLength() throws IOException { " howpublished = {asdf}," + OS.NEWLINE + "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test void doNotWriteEmptyFields() throws IOException { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibEntry entry = new BibEntry(StandardEntryType.Article); entry.setField(StandardField.AUTHOR, " "); entry.setField(StandardField.NOTE, "some note"); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + "}" + OS.NEWLINE; - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test void writeThrowsErrorIfFieldContainsUnbalancedBraces() { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibEntry entry = new BibEntry(StandardEntryType.Article); entry.setField(StandardField.NOTE, "some text with unbalanced { braces"); - assertThrows(IOException.class, () -> writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX)); + assertThrows(IOException.class, () -> bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX)); } @Test @@ -545,11 +505,9 @@ void roundTripWithPrecedingCommentTest() throws IOException { BibEntry entry = entries.iterator().next(); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(bibtexEntry, actual); + assertEquals(bibtexEntry, stringWriter.toString()); } @Test @@ -573,9 +531,8 @@ void roundTripWithPrecedingCommentAndModificationTest() throws IOException { entry.setField(StandardField.AUTHOR, "John Doe"); // write out bibtex string - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBTEX); - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); + // @formatter:off String expected = "% Some random comment that should stay here" + OS.NEWLINE + OS.NEWLINE + "@Article{test," + OS.NEWLINE + @@ -586,13 +543,11 @@ void roundTripWithPrecedingCommentAndModificationTest() throws IOException { "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test void alphabeticSerialization() throws IOException { - StringJoiner stringJoiner = new StringJoiner(OS.NEWLINE); - BibEntry entry = new BibEntry(StandardEntryType.Article); // required fields entry.setField(StandardField.AUTHOR, "Foo Bar"); @@ -606,9 +561,7 @@ void alphabeticSerialization() throws IOException { entry.setField(StandardField.YEAR, "2019"); entry.setField(StandardField.CHAPTER, "chapter"); - writer.write(entry, stringJoiner, BibDatabaseMode.BIBLATEX); - - String actual = stringJoiner.toString(); + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBLATEX); // @formatter:off String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + @@ -623,7 +576,7 @@ void alphabeticSerialization() throws IOException { "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, actual); + assertEquals(expected, stringWriter.toString()); } @Test @@ -656,7 +609,7 @@ void testSerializeAll() throws IOException { entry2.setField(StandardField.YEAR, "2020"); entry2.setField(StandardField.CHAPTER, "chapter"); - String output = writer.serializeAll(List.of(entry1, entry2), BibDatabaseMode.BIBLATEX); + String output = bibEntryWriter.serializeAll(List.of(entry1, entry2), BibDatabaseMode.BIBLATEX); // @formatter:off String expected1 = OS.NEWLINE + "@Article{," + OS.NEWLINE + diff --git a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java index 70e518552b7..5c94576bf5c 100644 --- a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java +++ b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -10,7 +11,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.StringJoiner; import org.jabref.logic.citationkeypattern.AbstractCitationKeyPattern; import org.jabref.logic.citationkeypattern.DatabaseCitationKeyPattern; @@ -60,7 +60,6 @@ class BibtexDatabaseWriterTest { - private StringJoiner stringJoiner; private BibtexDatabaseWriter databaseWriter; private BibDatabase database; private MetaData metaData; @@ -70,17 +69,20 @@ class BibtexDatabaseWriterTest { private GeneralPreferences generalPreferences; private SavePreferences savePreferences; private BibEntryTypesManager entryTypesManager; + private StringWriter stringWriter; + private BibWriter bibWriter; @BeforeEach void setUp() { - stringJoiner = new StringJoiner(OS.NEWLINE); generalPreferences = mock(GeneralPreferences.class); when(generalPreferences.getDefaultEncoding()).thenReturn(null); savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); entryTypesManager = new BibEntryTypesManager(); - databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); + stringWriter = new StringWriter(); + bibWriter = new BibWriter(stringWriter, OS.NEWLINE); + databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); database = new BibDatabase(); metaData = new MetaData(); @@ -104,7 +106,7 @@ void writeEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("% Encoding: US-ASCII" + OS.NEWLINE, stringJoiner.toString()); + assertEquals("% Encoding: US-ASCII" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -113,7 +115,7 @@ void writePreamble() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Preamble{Test preamble}" + OS.NEWLINE, stringJoiner.toString()); + assertEquals(OS.NEWLINE + "@Preamble{Test preamble}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -124,7 +126,7 @@ void writePreambleAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "@Preamble{Test preamble}" + OS.NEWLINE, stringJoiner.toString()); + "@Preamble{Test preamble}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -135,11 +137,9 @@ void writeEntry() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.singletonList(entry)); - assertEquals( - OS.NEWLINE + - "@Article{," + OS.NEWLINE + "}" + assertEquals("@Article{," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -155,7 +155,7 @@ void writeEncodingAndEntry() throws Exception { "% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + "@Article{," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -164,7 +164,7 @@ void writeEpilogue() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "Test epilog" + OS.NEWLINE, stringJoiner.toString()); + assertEquals(OS.NEWLINE + "Test epilog" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -175,7 +175,7 @@ void writeEpilogueAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "Test epilog" + OS.NEWLINE, stringJoiner.toString()); + "Test epilog" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -187,7 +187,7 @@ void writeMetadata() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals(OS.NEWLINE + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -201,7 +201,7 @@ void writeMetadataAndEncoding() throws Exception { assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, stringJoiner.toString()); + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -217,7 +217,7 @@ void writeGroups() throws Exception { + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE - + "}" + OS.NEWLINE, stringJoiner.toString()); + + "}" + OS.NEWLINE, stringWriter.toString()); // @formatter:on } @@ -238,7 +238,7 @@ void writeGroupsAndEncoding() throws Exception { + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE - + "}" + OS.NEWLINE, stringJoiner.toString()); + + "}" + OS.NEWLINE, stringWriter.toString()); // @formatter:on } @@ -248,7 +248,7 @@ void writeString() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringJoiner.toString()); + assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringWriter.toString()); } @Test @@ -259,7 +259,7 @@ void writeStringAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals("% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE + - "@String{name = {content}}" + OS.NEWLINE, stringJoiner.toString()); + "@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -269,7 +269,7 @@ void doNotWriteUtf8StringAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringJoiner.toString()); + assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringWriter.toString()); } @Test @@ -301,7 +301,7 @@ void writeEntryWithCustomizedTypeAlsoWritesTypeDeclaration() throws Exception { + "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: customizedtype: req[title;author;date] opt[year;month;publisher]}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -333,7 +333,7 @@ void writeCustomizedTypesInAlphabeticalOrder() throws Exception { + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: customizedtype: req[title] opt[]}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: othercustomizedtype: req[title] opt[]}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -347,7 +347,7 @@ void roundtripWithArticleMonths() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); } @Test @@ -374,7 +374,7 @@ void roundtripUtf8EncodingHeaderRemoved() throws Exception { " number = {1}," + OS.NEWLINE + "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, stringJoiner.toString()); + assertEquals(expected, stringWriter.toString()); } @Test @@ -388,7 +388,7 @@ void roundtripWithComplexBib() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); } @Test @@ -402,7 +402,7 @@ void roundtripWithUserComment() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); } @Test @@ -419,7 +419,7 @@ void roundtripWithUserCommentAndEntryChange() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(Path.of("src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib"), encoding), stringJoiner.toString()); + assertEquals(Files.readString(Path.of("src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib"), encoding), stringWriter.toString()); } @Test @@ -439,7 +439,7 @@ void roundtripWithUserCommentBeforeStringAndChange() throws Exception { databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); } @Test @@ -453,7 +453,7 @@ void roundtripWithUnknownMetaData() throws Exception { BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); - assertEquals(Files.readString(testBibtexFile, encoding), stringJoiner.toString()); + assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); } @Test @@ -467,7 +467,7 @@ void writeSavedSerializationOfEntryIfUnchanged() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.singletonList(entry)); - assertEquals("presaved serialization", stringJoiner.toString()); + assertEquals("presaved serialization", stringWriter.toString()); } @Test @@ -486,7 +486,7 @@ void reformatEntryIfAskedToDoSo() throws Exception { OS.NEWLINE + "@Article{," + OS.NEWLINE + " author = {Mr. author}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -497,7 +497,7 @@ void writeSavedSerializationOfStringIfUnchanged() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("serialization", stringJoiner.toString()); + assertEquals("serialization" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -509,7 +509,7 @@ void reformatStringIfAskedToDoSo() throws Exception { when(savePreferences.shouldReformatFile()).thenReturn(true); databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@String{name = {content}}" + OS.NEWLINE, stringJoiner.toString()); + assertEquals(OS.NEWLINE + "@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -531,7 +531,7 @@ void writeSaveActions() throws Exception { + "journal[title_case]" + OS.NEWLINE + "title[lower_case]" + OS.NEWLINE + ";}" - + OS.NEWLINE, stringJoiner.toString()); + + OS.NEWLINE, stringWriter.toString()); } @Test @@ -546,7 +546,7 @@ void writeSaveOrderConfig() throws Exception { assertEquals(OS.NEWLINE + "@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}" - + OS.NEWLINE, stringJoiner.toString()); + + OS.NEWLINE, stringWriter.toString()); } @Test @@ -560,7 +560,7 @@ void writeCustomKeyPattern() throws Exception { assertEquals(OS.NEWLINE + "@Comment{jabref-meta: keypattern_article:articleTest;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -570,7 +570,7 @@ void writeBiblatexMode() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals(OS.NEWLINE + "@Comment{jabref-meta: databaseType:biblatex;}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -580,7 +580,7 @@ void writeProtectedFlag() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); assertEquals(OS.NEWLINE + "@Comment{jabref-meta: protectedFlag:true;}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -593,7 +593,7 @@ void writeFileDirectories() throws Exception { assertEquals(OS.NEWLINE + "@Comment{jabref-meta: fileDirectory:\\\\Literature\\\\;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\\\Documents;}" - + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectoryLatex-defaultOwner-user:D:\\\\Latex;}" + OS.NEWLINE, stringJoiner.toString()); + + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectoryLatex-defaultOwner-user:D:\\\\Latex;}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -639,7 +639,7 @@ void writeEntriesSorted() throws Exception { "}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -666,7 +666,7 @@ void writeEntriesInOriginalOrderWhenNoSaveOrderConfigIsSetInMetadata() throws Ex when(savePreferences.shouldSaveInOriginalOrder()).thenReturn(false); databaseWriter.savePartOfDatabase(bibtexContext, database.getEntries()); - assertEquals("Article{," + OS.NEWLINE + + assertEquals("@Article{," + OS.NEWLINE + " author = {A}," + OS.NEWLINE + " year = {2010}," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + @@ -679,7 +679,7 @@ void writeEntriesInOriginalOrderWhenNoSaveOrderConfigIsSetInMetadata() throws Ex " year = {2000}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -694,7 +694,7 @@ void trimFieldContents() throws IOException { OS.NEWLINE + "@Article{," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -711,7 +711,7 @@ void newlineAtEndOfAbstractFieldIsDeleted() throws Exception { OS.NEWLINE + "@Article{," + OS.NEWLINE + " abstract = {" + text + "}," + OS.NEWLINE + "}" + OS.NEWLINE, - stringJoiner.toString()); + stringWriter.toString()); } @Test @@ -727,7 +727,7 @@ void roundtripWithContentSelectorsAndUmlauts() throws Exception { databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); - assertEquals(fileContent, stringJoiner.toString()); + assertEquals(fileContent, stringWriter.toString()); } @Test @@ -759,8 +759,9 @@ void saveAlsoSavesSecondModification() throws Exception { entry.setField(StandardField.AUTHOR, "Test"); // write a second time - stringJoiner = new StringJoiner(OS.NEWLINE); - databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); + stringWriter = new StringWriter(); + bibWriter = new BibWriter(stringWriter, OS.NEWLINE); + databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); assertEquals(OS.NEWLINE + @@ -771,7 +772,7 @@ void saveAlsoSavesSecondModification() throws Exception { " number = {1}," + OS.NEWLINE + "}" + OS.NEWLINE + "" + OS.NEWLINE + - "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE, stringJoiner.toString()); + "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -801,18 +802,18 @@ void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchanged() throws Exception { BibDatabaseContext context = new BibDatabaseContext(firstParse.getDatabase(), firstParse.getMetaData()); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); - assertEquals(bibtexEntry, stringJoiner.toString()); + assertEquals(bibtexEntry, stringWriter.toString()); } @Test - void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchangedEvenInThePrecenseOfSavedModifications() throws Exception { + void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchangedEvenInThePresenceOfSavedModifications() throws Exception { // @formatter:off - String bibtexEntry = OS.NEWLINE + "@Article{test," + OS.NEWLINE + + String bibtexEntry = "@Article{test," + OS.NEWLINE + " Author = {Foo Bar}," + OS.NEWLINE + " Journal = {International Journal of Something}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + " Number = {1}," + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on // read in bibtex string @@ -834,11 +835,12 @@ void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchangedEvenInThePrecenseOfSav entry.setChanged(false); // write a second time - stringJoiner = new StringJoiner(OS.NEWLINE); - databaseWriter = new BibtexDatabaseWriter(stringJoiner, generalPreferences, savePreferences, entryTypesManager); + stringWriter = new StringWriter(); + bibWriter = new BibWriter(stringWriter, OS.NEWLINE); + databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); // returns tu original entry, not to the last saved one - assertEquals(bibtexEntry, stringJoiner.toString()); + assertEquals(bibtexEntry, stringWriter.toString()); } } diff --git a/src/test/resources/testbib/bibWithUserComments.bib b/src/test/resources/testbib/bibWithUserComments.bib index 90685b10eac..fbe2d05c7b4 100644 --- a/src/test/resources/testbib/bibWithUserComments.bib +++ b/src/test/resources/testbib/bibWithUserComments.bib @@ -1,5 +1,3 @@ -% Encoding: UTF-8 - @Preamble{preamble} @String{firstString = {my first string}} diff --git a/src/test/resources/testbib/complex.bib b/src/test/resources/testbib/complex.bib index a46342912bf..8fb2ba87f74 100644 --- a/src/test/resources/testbib/complex.bib +++ b/src/test/resources/testbib/complex.bib @@ -1,5 +1,3 @@ -% Encoding: UTF-8 - @Preamble{preamble} This is some arbitrary user comment that should be preserved From 9c076f1c4df7c00f9d96794e329b562c9ab9b074 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 12 Nov 2021 23:08:41 +0100 Subject: [PATCH 03/10] Fixes for the results of the block functionality --- .../jabref/logic/bibtex/BibEntryWriter.java | 2 + .../org/jabref/logic/exporter/BibWriter.java | 24 ++-- .../logic/exporter/BibtexDatabaseWriter.java | 1 - .../importer/fileformat/BibtexParser.java | 18 ++- .../java/org/jabref/model/entry/BibEntry.java | 5 +- .../org/jabref/model/entry/BibtexString.java | 10 +- .../logic/bibtex/BibEntryWriterTest.java | 89 ++++++++----- .../exporter/BibtexDatabaseWriterTest.java | 117 +++++++++++++----- .../importer/fileformat/BibtexParserTest.java | 18 +-- .../bibWithUserCommentAndEntryChange.bib | 2 - 10 files changed, 177 insertions(+), 109 deletions(-) diff --git a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java index 677e0cf746b..ef93f2878cb 100644 --- a/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java +++ b/src/main/java/org/jabref/logic/bibtex/BibEntryWriter.java @@ -75,6 +75,8 @@ private void writeUserComments(BibEntry entry, BibWriter out) throws IOException if (!userComments.isEmpty()) { out.write(userComments); + // ensure that a line break appears after the comment + out.finishLine(); } } diff --git a/src/main/java/org/jabref/logic/exporter/BibWriter.java b/src/main/java/org/jabref/logic/exporter/BibWriter.java index 1e3e364ff60..837530fa33a 100644 --- a/src/main/java/org/jabref/logic/exporter/BibWriter.java +++ b/src/main/java/org/jabref/logic/exporter/BibWriter.java @@ -26,16 +26,16 @@ public BibWriter(Writer writer, String newline) { /** * Writes the given string. The newlines of the given string are converted to the newline set for this clas */ - public void write(String string) throws IOException { - if (precedingNewLineRequired) { - writer.write(newline); - precedingNewLineRequired = false; - } - string = string.replace("\r\n", "\n").replace("\n", newline); - writer.write(string); - lastWriteWasNewline = string.endsWith(newline); - somethingWasWritten = true; - } + public void write(String string) throws IOException { + if (precedingNewLineRequired) { + writer.write(newline); + precedingNewLineRequired = false; + } + string = string.replace("\r\n", "\n").replace("\n", newline); + writer.write(string); + lastWriteWasNewline = string.endsWith(newline); + somethingWasWritten = true; + } /** * Writes the given string and finishes it with a line break @@ -49,7 +49,9 @@ public void writeLine(String string) throws IOException { * Finishes a line */ public void finishLine() throws IOException { - this.write(newline); + if (!this.lastWriteWasNewline) { + this.write(newline); + } } /** diff --git a/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java b/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java index 252bb535a76..84227448fe9 100644 --- a/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java +++ b/src/main/java/org/jabref/logic/exporter/BibtexDatabaseWriter.java @@ -94,7 +94,6 @@ protected void writeString(BibtexString bibtexString, int maxKeyLength) throws I } bibWriter.writeLine("}"); - bibWriter.finishBlock(); } @Override diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index ce28718dabb..88d2f5ea4ab 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -256,6 +256,14 @@ private void parseAndAddEntry(String type) { // this is at least `@Type` String commentsAndEntryTypeDefinition = dumpTextReadSoFarToString(); + // remove first newline + // this is appended by JabRef during writing automatically + if (commentsAndEntryTypeDefinition.startsWith("\r\n")) { + commentsAndEntryTypeDefinition = commentsAndEntryTypeDefinition.substring(2); + } else if (commentsAndEntryTypeDefinition.startsWith("\n")) { + commentsAndEntryTypeDefinition = commentsAndEntryTypeDefinition.substring(1); + } + BibEntry entry = parseEntry(type); // store comments collected without type definition entry.setCommentsBeforeEntry( @@ -264,16 +272,6 @@ private void parseAndAddEntry(String type) { // store complete parsed serialization (comments, type definition + type contents) String parsedSerialization = commentsAndEntryTypeDefinition + dumpTextReadSoFarToString(); - // normalize new lines - parsedSerialization = parsedSerialization.replace("\r\n", "\n"); - // strip newlines from beginning and and (we need to keep whitespaces, thus trim() cannot be used) - while (parsedSerialization.startsWith("\n")) { - parsedSerialization = parsedSerialization.substring(1); - } - while (parsedSerialization.endsWith("\n")) { - parsedSerialization = parsedSerialization.substring(0, parsedSerialization.length()-1); - } - parsedSerialization = parsedSerialization.replace("\n", newLine); entry.setParsedSerialization(parsedSerialization); database.insertEntry(entry); diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index e0311d8b615..28709bc1c52 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -12,7 +12,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; -import java.util.regex.Pattern; import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; @@ -57,7 +56,6 @@ public class BibEntry implements Cloneable { public static final EntryType DEFAULT_TYPE = StandardEntryType.Misc; private static final Logger LOGGER = LoggerFactory.getLogger(BibEntry.class); - private static final Pattern REMOVE_TRAILING_WHITESPACE = Pattern.compile("\\s+$"); private final SharedBibEntryData sharedBibEntryData; /** @@ -699,8 +697,7 @@ public void setParsedSerialization(String parsedSerialization) { } public void setCommentsBeforeEntry(String parsedComments) { - // delete trailing whitespaces (between entry and text) - this.commentsBeforeEntry = REMOVE_TRAILING_WHITESPACE.matcher(parsedComments).replaceFirst(""); + this.commentsBeforeEntry = parsedComments; } public boolean hasChanged() { diff --git a/src/main/java/org/jabref/model/entry/BibtexString.java b/src/main/java/org/jabref/model/entry/BibtexString.java index 84e1518cf3e..6b7bd2c289b 100644 --- a/src/main/java/org/jabref/model/entry/BibtexString.java +++ b/src/main/java/org/jabref/model/entry/BibtexString.java @@ -145,22 +145,14 @@ public boolean hasChanged() { */ public String getUserComments() { if (parsedSerialization != null) { - try { // get the text before the string String prolog = parsedSerialization.substring(0, parsedSerialization.indexOf('@')); - - // delete trailing whitespaces (between string and text) - prolog = prolog.replaceFirst("\\s+$", ""); - // if there is any non whitespace text, write it with proper line separation - if (prolog.length() > 0) { - return prolog; - } + return prolog; } catch (StringIndexOutOfBoundsException ignore) { // if this occurs a broken parsed serialization has been set, so just do nothing } } - return ""; } diff --git a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java index 4176bcf0278..1c80e3217eb 100644 --- a/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java +++ b/src/test/java/org/jabref/logic/bibtex/BibEntryWriterTest.java @@ -61,7 +61,7 @@ void testSerialization() throws IOException { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + + String expected = "@Article{," + OS.NEWLINE + " author = {Foo Bar}," + OS.NEWLINE + " journal = {International Journal of Something}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + @@ -74,7 +74,7 @@ void testSerialization() throws IOException { @Test void writeOtherTypeTest() throws Exception { - String expected = OS.NEWLINE + "@Other{test," + OS.NEWLINE + + String expected = "@Other{test," + OS.NEWLINE + " comment = {testentry}," + OS.NEWLINE + "}" + OS.NEWLINE; @@ -94,8 +94,7 @@ void writeEntryWithFile() throws Exception { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(OS.NEWLINE + - "@Article{," + assertEquals("@Article{," + OS.NEWLINE + " file = {test:/home/uers/test.pdf:PDF}," + OS.NEWLINE @@ -115,7 +114,7 @@ void writeEntryWithOrField() throws Exception { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expected = OS.NEWLINE + "@InBook{," + OS.NEWLINE + + String expected = "@InBook{," + OS.NEWLINE + " editor = {Foo Bar}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + " number = {1}," + OS.NEWLINE + @@ -140,7 +139,7 @@ void writeEntryWithOrFieldBothFieldsPresent() throws Exception { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expected = OS.NEWLINE + "@InBook{," + OS.NEWLINE + + String expected = "@InBook{," + OS.NEWLINE + " author = {Foo Thor}," + OS.NEWLINE + " editor = {Edi Bar}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + @@ -154,7 +153,7 @@ void writeEntryWithOrFieldBothFieldsPresent() throws Exception { @Test void writeReallyUnknownTypeTest() throws Exception { - String expected = OS.NEWLINE + "@Reallyunknowntype{test," + OS.NEWLINE + + String expected = "@Reallyunknowntype{test," + OS.NEWLINE + " comment = {testentry}," + OS.NEWLINE + "}" + OS.NEWLINE; @@ -175,7 +174,7 @@ void roundTripTest() throws IOException { " Journal = {International Journal of Something}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + " Number = {1}" + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on // read in bibtex string @@ -197,7 +196,7 @@ void roundTripWithPrependingNewlines() throws IOException { " Journal = {International Journal of Something}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + " Number = {1}" + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on // read in bibtex string @@ -208,13 +207,13 @@ void roundTripWithPrependingNewlines() throws IOException { // write out bibtex string bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(bibtexEntry, stringWriter.toString()); + assertEquals(bibtexEntry.substring(2), stringWriter.toString()); } @Test void roundTripWithModification() throws IOException { // @formatter:off - String bibtexEntry = OS.NEWLINE + "@Article{test," + OS.NEWLINE + + String bibtexEntry = "@Article{test," + OS.NEWLINE + " Author = {Foo Bar}," + OS.NEWLINE + " Journal = {International Journal of Something}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + @@ -234,14 +233,14 @@ void roundTripWithModification() throws IOException { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + + String expected = "@Article{test," + OS.NEWLINE + " author = {BlaBla}," + OS.NEWLINE + " journal = {International Journal of Something}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + " number = {1}," + OS.NEWLINE + "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected, bibWriter.toString()); + assertEquals(expected, stringWriter.toString()); } @Test @@ -268,7 +267,7 @@ void roundTripWithCamelCasingInTheOriginalEntryAndResultInLowerCase() throws IOE bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expected = OS.NEWLINE + "@Article{test," + OS.NEWLINE + + String expected = "@Article{test," + OS.NEWLINE + " author = {BlaBla}," + OS.NEWLINE + " journal = {International Journal of Something}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + @@ -303,7 +302,7 @@ void testEntryTypeChange() throws IOException { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expectedNewEntry = OS.NEWLINE + "@InProceedings{test," + OS.NEWLINE + + String expectedNewEntry = "@InProceedings{test," + OS.NEWLINE + " author = {BlaBla}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + " number = {1}," + OS.NEWLINE + @@ -332,10 +331,40 @@ void roundTripWithAppendedNewlines() throws IOException { // write out bibtex string bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - String actual = bibWriter.toString(); + String actual = stringWriter.toString(); + + // Only one appending newline is written by the writer + // OS.NEWLINE is used, not the given one + assertEquals(bibtexEntry.substring(0, bibtexEntry.length() - 2) + OS.NEWLINE, actual); + } + + @Test + void roundTripNormalizesNewLines() throws IOException { + // @formatter:off + String bibtexEntry = "@Article{test,\n" + + " Author = {Foo Bar},\r\n" + + " Journal = {International Journal of Something},\n" + + " Number = {1},\n" + + " Note = {some note}\r\n" + + "}\n\n"; + // @formatter:on + + // read in bibtex string + ParserResult result = new BibtexParser(importFormatPreferences, fileMonitor).parse(new StringReader(bibtexEntry)); + Collection entries = result.getDatabase().getEntries(); + BibEntry entry = entries.iterator().next(); + + // write out bibtex string + bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); + String actual = stringWriter.toString(); - // Only one appending newline is written by the writer, the rest by FileActions. So, these should be removed here. - assertEquals(bibtexEntry.substring(0, bibtexEntry.length() - 1), actual); + String expected = "@Article{test," + OS.NEWLINE + + " Author = {Foo Bar}," + OS.NEWLINE + + " Journal = {International Journal of Something}," + OS.NEWLINE + + " Number = {1}," + OS.NEWLINE + + " Note = {some note}" + OS.NEWLINE + + "}" + OS.NEWLINE; + assertEquals(expected, actual); } @Test @@ -346,7 +375,7 @@ void multipleWritesWithoutModification() throws IOException { " Journal = {International Journal of Something}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + " Number = {1}" + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on String result = testSingleWrite(bibtexEntry); @@ -379,7 +408,7 @@ void monthFieldSpecialSyntax() throws IOException { " Author = {Foo Bar}," + OS.NEWLINE + " Month = mar," + OS.NEWLINE + " Number = {1}" + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on // read in bibtex string @@ -407,8 +436,7 @@ void constantMonthApril() throws Exception { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(OS.NEWLINE + - "@Misc{," + OS.NEWLINE + + assertEquals("@Misc{," + OS.NEWLINE + " month = apr," + OS.NEWLINE + "}" + OS.NEWLINE, stringWriter.toString()); @@ -423,8 +451,7 @@ void monthApril() throws Exception { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - assertEquals(OS.NEWLINE + - "@Misc{," + OS.NEWLINE + + assertEquals("@Misc{," + OS.NEWLINE + " month = {apr}," + OS.NEWLINE + "}" + OS.NEWLINE, stringWriter.toString()); @@ -472,7 +499,7 @@ void doNotWriteEmptyFields() throws IOException { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); - String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + + String expected = "@Article{," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + "}" + OS.NEWLINE; @@ -496,7 +523,7 @@ void roundTripWithPrecedingCommentTest() throws IOException { " Journal = {International Journal of Something}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + " Number = {1}" + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on // read in bibtex string @@ -534,7 +561,7 @@ void roundTripWithPrecedingCommentAndModificationTest() throws IOException { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBTEX); // @formatter:off - String expected = "% Some random comment that should stay here" + OS.NEWLINE + OS.NEWLINE + + String expected = "% Some random comment that should stay here" + OS.NEWLINE + "@Article{test," + OS.NEWLINE + " author = {John Doe}," + OS.NEWLINE + " journal = {International Journal of Something}," + OS.NEWLINE + @@ -564,7 +591,7 @@ void alphabeticSerialization() throws IOException { bibEntryWriter.write(entry, bibWriter, BibDatabaseMode.BIBLATEX); // @formatter:off - String expected = OS.NEWLINE + "@Article{," + OS.NEWLINE + + String expected = "@Article{," + OS.NEWLINE + " author = {Foo Bar}," + OS.NEWLINE + " date = {2019-10-16}," + OS.NEWLINE + " journaltitle = {International Journal of Something}," + OS.NEWLINE + @@ -612,7 +639,7 @@ void testSerializeAll() throws IOException { String output = bibEntryWriter.serializeAll(List.of(entry1, entry2), BibDatabaseMode.BIBLATEX); // @formatter:off - String expected1 = OS.NEWLINE + "@Article{," + OS.NEWLINE + + String expected1 = "@Article{," + OS.NEWLINE + " author = {Journal Author}," + OS.NEWLINE + " date = {2020-11-16}," + OS.NEWLINE + " journaltitle = {Journal of Words}," + OS.NEWLINE + @@ -625,7 +652,7 @@ void testSerializeAll() throws IOException { // @formatter:on // @formatter:off - String expected2 = OS.NEWLINE + "@Book{," + OS.NEWLINE + + String expected2 = "@Book{," + OS.NEWLINE + " author = {John Book}," + OS.NEWLINE + " date = {2017-12-20}," + OS.NEWLINE + " title = {Entry Title}," + OS.NEWLINE + @@ -637,7 +664,7 @@ void testSerializeAll() throws IOException { "}" + OS.NEWLINE; // @formatter:on - assertEquals(expected1 + expected2, output); + assertEquals(expected1 + OS.NEWLINE + expected2, output); } } diff --git a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java index 5c94576bf5c..33713059eb4 100644 --- a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java +++ b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java @@ -115,7 +115,7 @@ void writePreamble() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Preamble{Test preamble}" + OS.NEWLINE, stringWriter.toString()); + assertEquals("@Preamble{Test preamble}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -164,7 +164,7 @@ void writeEpilogue() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "Test epilog" + OS.NEWLINE, stringWriter.toString()); + assertEquals("Test epilog" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -186,7 +186,7 @@ void writeMetadata() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, + assertEquals("@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, stringWriter.toString()); } @@ -213,8 +213,7 @@ void writeGroups() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); // @formatter:off - assertEquals(OS.NEWLINE - + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + assertEquals("@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE + "}" + OS.NEWLINE, stringWriter.toString()); @@ -248,7 +247,7 @@ void writeString() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringWriter.toString()); + assertEquals("@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -269,7 +268,7 @@ void doNotWriteUtf8StringAndEncoding() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals("@String{name = {content}}" + OS.NEWLINE + OS.NEWLINE, stringWriter.toString()); + assertEquals("@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -295,9 +294,7 @@ void writeEntryWithCustomizedTypeAlsoWritesTypeDeclaration() throws Exception { databaseWriter.saveDatabase(bibtexContext); - assertEquals( - OS.NEWLINE + - "@Customizedtype{," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + assertEquals("@Customizedtype{," + OS.NEWLINE + "}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: databaseType:bibtex;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-entrytype: customizedtype: req[title;author;date] opt[year;month;publisher]}" + OS.NEWLINE, @@ -405,6 +402,64 @@ void roundtripWithUserComment() throws Exception { assertEquals(Files.readString(testBibtexFile, encoding), stringWriter.toString()); } + @Test + void roundtripWithOneUserCommentAndEntryChange() throws Exception { + String bibEntry = "@Comment this in an unbracketed comment that should be preserved as well\n" + + "\n" + + "This is some arbitrary user comment that should be preserved\n" + + "\n" + + "@InProceedings{1137631,\n" + + " author = {Mr. Author},\n" + + "}\n"; + + // read in bibtex string + ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + ParserResult result = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()).parse(new StringReader(bibEntry)); + + BibEntry entry = result.getDatabase().getEntryByCitationKey("1137631").get(); + entry.setField(StandardField.AUTHOR, "Mr. Author"); + + when(generalPreferences.getDefaultEncoding()).thenReturn(StandardCharsets.UTF_8); + when(savePreferences.shouldSaveInOriginalOrder()).thenReturn(true); + BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); + + // we need a new writer because "\n" instead of "OS.NEWLINE" + bibWriter = new BibWriter(stringWriter, "\n"); + databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); + databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); + assertEquals(bibEntry, stringWriter.toString()); + } + + @Test + void roundtripWithTwoEntriesAndOneUserCommentAndEntryChange() throws Exception { + String bibEntry = "@Article{test,}\n" + + "\n" + + "@Comment this in an unbracketed comment that should be preserved as well\n" + + "\n" + + "This is some arbitrary user comment that should be preserved\n" + + "\n" + + "@InProceedings{1137631,\n" + + " author = {Mr. Author},\n" + + "}\n"; + + // read in bibtex string + ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + ParserResult result = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()).parse(new StringReader(bibEntry)); + + BibEntry entry = result.getDatabase().getEntryByCitationKey("1137631").get(); + entry.setField(StandardField.AUTHOR, "Mr. Author"); + + when(generalPreferences.getDefaultEncoding()).thenReturn(StandardCharsets.UTF_8); + when(savePreferences.shouldSaveInOriginalOrder()).thenReturn(true); + BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData()); + + // we need a new writer because "\n" instead of "OS.NEWLINE" + bibWriter = new BibWriter(stringWriter, "\n"); + databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); + databaseWriter.savePartOfDatabase(context, result.getDatabase().getEntries()); + assertEquals(bibEntry, stringWriter.toString()); + } + @Test void roundtripWithUserCommentAndEntryChange() throws Exception { Path testBibtexFile = Path.of("src/test/resources/testbib/bibWithUserComments.bib"); @@ -467,7 +522,7 @@ void writeSavedSerializationOfEntryIfUnchanged() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.singletonList(entry)); - assertEquals("presaved serialization", stringWriter.toString()); + assertEquals("presaved serialization" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -482,9 +537,7 @@ void reformatEntryIfAskedToDoSo() throws Exception { when(savePreferences.shouldReformatFile()).thenReturn(true); databaseWriter.savePartOfDatabase(bibtexContext, Collections.singletonList(entry)); - assertEquals( - OS.NEWLINE - + "@Article{," + OS.NEWLINE + " author = {Mr. author}," + OS.NEWLINE + "}" + assertEquals("@Article{," + OS.NEWLINE + " author = {Mr. author}," + OS.NEWLINE + "}" + OS.NEWLINE, stringWriter.toString()); } @@ -509,7 +562,7 @@ void reformatStringIfAskedToDoSo() throws Exception { when(savePreferences.shouldReformatFile()).thenReturn(true); databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); + assertEquals("@String{name = {content}}" + OS.NEWLINE, stringWriter.toString()); } @Test @@ -523,9 +576,7 @@ void writeSaveActions() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals( - OS.NEWLINE + - "@Comment{jabref-meta: saveActions:enabled;" + assertEquals("@Comment{jabref-meta: saveActions:enabled;" + OS.NEWLINE + "day[upper_case]" + OS.NEWLINE + "journal[title_case]" + OS.NEWLINE @@ -544,8 +595,7 @@ void writeSaveOrderConfig() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE - + "@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}" + assertEquals("@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}" + OS.NEWLINE, stringWriter.toString()); } @@ -558,7 +608,7 @@ void writeCustomKeyPattern() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Comment{jabref-meta: keypattern_article:articleTest;}" + OS.NEWLINE + assertEquals("@Comment{jabref-meta: keypattern_article:articleTest;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: keypatterndefault:test;}" + OS.NEWLINE, stringWriter.toString()); } @@ -569,7 +619,7 @@ void writeBiblatexMode() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Comment{jabref-meta: databaseType:biblatex;}" + OS.NEWLINE, + assertEquals("@Comment{jabref-meta: databaseType:biblatex;}" + OS.NEWLINE, stringWriter.toString()); } @@ -579,7 +629,7 @@ void writeProtectedFlag() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Comment{jabref-meta: protectedFlag:true;}" + OS.NEWLINE, + assertEquals("@Comment{jabref-meta: protectedFlag:true;}" + OS.NEWLINE, stringWriter.toString()); } @@ -591,7 +641,7 @@ void writeFileDirectories() throws Exception { databaseWriter.savePartOfDatabase(bibtexContext, Collections.emptyList()); - assertEquals(OS.NEWLINE + "@Comment{jabref-meta: fileDirectory:\\\\Literature\\\\;}" + OS.NEWLINE + + assertEquals("@Comment{jabref-meta: fileDirectory:\\\\Literature\\\\;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\\\Documents;}" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: fileDirectoryLatex-defaultOwner-user:D:\\\\Latex;}" + OS.NEWLINE, stringWriter.toString()); } @@ -690,8 +740,7 @@ void trimFieldContents() throws IOException { databaseWriter.saveDatabase(bibtexContext); - assertEquals( - OS.NEWLINE + "@Article{," + OS.NEWLINE + + assertEquals("@Article{," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + "}" + OS.NEWLINE, stringWriter.toString()); @@ -707,8 +756,7 @@ void newlineAtEndOfAbstractFieldIsDeleted() throws Exception { databaseWriter.saveDatabase(bibtexContext); - assertEquals( - OS.NEWLINE + "@Article{," + OS.NEWLINE + + assertEquals("@Article{," + OS.NEWLINE + " abstract = {" + text + "}," + OS.NEWLINE + "}" + OS.NEWLINE, stringWriter.toString()); @@ -716,7 +764,9 @@ void newlineAtEndOfAbstractFieldIsDeleted() throws Exception { @Test void roundtripWithContentSelectorsAndUmlauts() throws Exception { - String fileContent = "% Encoding: UTF-8" + OS.NEWLINE + OS.NEWLINE + "@Comment{jabref-meta: selector_journal:Test {\\\\\"U}mlaut;}" + OS.NEWLINE; + String encodingHeader = "% Encoding: UTF-8" + OS.NEWLINE + OS.NEWLINE; + String commentEntry = "@Comment{jabref-meta: selector_journal:Test {\\\\\"U}mlaut;}" + OS.NEWLINE; + String fileContent = encodingHeader + commentEntry; Charset encoding = StandardCharsets.UTF_8; ParserResult firstParse = new BibtexParser(importFormatPreferences, fileMonitor).parse(new StringReader(fileContent)); @@ -727,7 +777,7 @@ void roundtripWithContentSelectorsAndUmlauts() throws Exception { databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); - assertEquals(fileContent, stringWriter.toString()); + assertEquals(commentEntry, stringWriter.toString()); } @Test @@ -764,8 +814,7 @@ void saveAlsoSavesSecondModification() throws Exception { databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); databaseWriter.savePartOfDatabase(context, firstParse.getDatabase().getEntries()); - assertEquals(OS.NEWLINE + - "@Article{test," + OS.NEWLINE + + assertEquals("@Article{test," + OS.NEWLINE + " author = {Test}," + OS.NEWLINE + " journal = {International Journal of Something}," + OS.NEWLINE + " note = {some note}," + OS.NEWLINE + @@ -778,12 +827,12 @@ void saveAlsoSavesSecondModification() throws Exception { @Test void saveReturnsToOriginalEntryWhenEntryIsFlaggedUnchanged() throws Exception { // @formatter:off - String bibtexEntry = OS.NEWLINE + "@Article{test," + OS.NEWLINE + + String bibtexEntry = "@Article{test," + OS.NEWLINE + " Author = {Foo Bar}," + OS.NEWLINE + " Journal = {International Journal of Something}," + OS.NEWLINE + " Number = {1}," + OS.NEWLINE + " Note = {some note}," + OS.NEWLINE + - "}"; + "}" + OS.NEWLINE; // @formatter:on // read in bibtex string diff --git a/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java b/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java index e0958825e03..67b9792452e 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java @@ -343,8 +343,8 @@ void parseRecognizesEntryWithAtInField() throws IOException { @Test void parseRecognizesEntryPrecedingComment() throws IOException { String comment = "@Comment{@article{myarticle,}" + OS.NEWLINE - + "@inproceedings{blabla, title={the proceedings of bl@bl@}; }" + OS.NEWLINE + "}"; - String entryWithComment = comment + OS.NEWLINE + "@article{test,author={Ed von T@st}}"; + + "@inproceedings{blabla, title={the proceedings of bl@bl@}; }" + OS.NEWLINE + "}" + OS.NEWLINE; + String entryWithComment = comment + "@article{test,author={Ed von T@st}}"; BibEntry expected = new BibEntry(StandardEntryType.Article) .withField(InternalField.KEY_FIELD, "test") .withField(StandardField.AUTHOR, "Ed von T@st"); @@ -1197,7 +1197,7 @@ void parseSavesOneNewlineAfterEntryInParsedSerialization() throws IOException { } @Test - void parseSavesNewlinesBeforeEntryInParsedSerialization() throws IOException { + void parseSavesAllButOneNewlinesBeforeEntryInParsedSerialization() throws IOException { String testEntry = "@article{test,author={Ed von Test}}"; ParserResult result = parser .parse(new StringReader(OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntry)); @@ -1206,11 +1206,12 @@ void parseSavesNewlinesBeforeEntryInParsedSerialization() throws IOException { BibEntry parsedEntry = parsedEntries.iterator().next(); assertEquals(1, parsedEntries.size()); - assertEquals(OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntry, parsedEntry.getParsedSerialization()); + // The first newline is removed, because JabRef interprets that always as block separator + assertEquals(OS.NEWLINE + OS.NEWLINE + testEntry, parsedEntry.getParsedSerialization()); } @Test - void parseRemovesEncodingLineInParsedSerialization() throws IOException { + void parseRemovesEncodingLineAndSeparatorInParsedSerialization() throws IOException { String testEntry = "@article{test,author={Ed von Test}}"; ParserResult result = parser.parse( new StringReader(SavePreferences.ENCODING_PREFIX + OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntry)); @@ -1219,7 +1220,9 @@ void parseRemovesEncodingLineInParsedSerialization() throws IOException { BibEntry parsedEntry = parsedEntries.iterator().next(); assertEquals(1, parsedEntries.size()); - assertEquals(OS.NEWLINE + OS.NEWLINE + testEntry, parsedEntry.getParsedSerialization()); + // First two newlines are removed because of removal of "Encoding" + // Third newline removed because of block functionality + assertEquals(testEntry, parsedEntry.getParsedSerialization()); } @Test @@ -1242,7 +1245,8 @@ void parseSavesNewlinesBetweenEntriesInParsedSerialization() throws IOException assertEquals(2, parsedEntries.size()); assertEquals(testEntryOne + OS.NEWLINE, first.getParsedSerialization()); - assertEquals(OS.NEWLINE + OS.NEWLINE + testEntryTwo, second.getParsedSerialization()); + // one newline is removed, because it is written by JabRef's block functionality + assertEquals(OS.NEWLINE + testEntryTwo, second.getParsedSerialization()); } @Test diff --git a/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib b/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib index 326c45842a3..17933272edd 100644 --- a/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib +++ b/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib @@ -1,5 +1,3 @@ -% Encoding: UTF-8 - @Preamble{preamble} @String{firstString = {my first string}} From d570cad6683b596ef48e5085df27bdf2e5e7f96b Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 13 Nov 2021 10:50:12 +0100 Subject: [PATCH 04/10] The written `.bib` file keeps the newline separator of the loaded `.bib` file --- CHANGELOG.md | 3 +- src/main/java/org/jabref/gui/JabRefMain.java | 5 -- .../gui/exporter/SaveDatabaseAction.java | 8 +-- .../jabref/gui/preferences/file/FileTab.fxml | 4 -- .../jabref/gui/preferences/file/FileTab.java | 9 ---- .../preferences/file/FileTabViewModel.java | 19 ------- .../logic/cleanup/FieldFormatterCleanups.java | 4 +- .../org/jabref/logic/exporter/BibWriter.java | 18 ++++--- .../importer/fileformat/BibtexParser.java | 17 ++++--- .../jabref/model/database/BibDatabase.java | 19 +++++-- .../preferences/ImportExportPreferences.java | 7 --- .../jabref/preferences/JabRefPreferences.java | 16 ------ .../jabref/preferences/NewLineSeparator.java | 51 ------------------- .../preferences/PreferencesService.java | 4 -- .../logic/bibtex/BibEntryWriterTest.java | 48 +++++++++++++++++ .../importer/fileformat/BibtexParserTest.java | 26 ++++++++++ 16 files changed, 116 insertions(+), 142 deletions(-) delete mode 100644 src/main/java/org/jabref/preferences/NewLineSeparator.java diff --git a/CHANGELOG.md b/CHANGELOG.md index eadeffe897e..24732f17f0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We Included all standard fields with citation key when exporting to Old OpenOffice/LibreOffice Calc Format [#8176](https://github.com/JabRef/jabref/pull/8176) - In case the database is encoded with `UTF8`, the `% Encoding` marker is not written anymore - The written `.bib` file has the same line endings [#390](https://github.com/koppor/jabref/issues/390) -- The written file always has a final line break +- The written `.bib` file always has a final line break +- The written `.bib` file keeps the newline separator of the loaded `.bib` file - We present options to manually enter an article or return to the New Entry menu when the fetcher DOI fails to find an entry for an ID [#7870](https://github.com/JabRef/jabref/issues/7870) ### Fixed diff --git a/src/main/java/org/jabref/gui/JabRefMain.java b/src/main/java/org/jabref/gui/JabRefMain.java index 6b3090829c9..084356f5fe3 100644 --- a/src/main/java/org/jabref/gui/JabRefMain.java +++ b/src/main/java/org/jabref/gui/JabRefMain.java @@ -25,7 +25,6 @@ import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.remote.RemotePreferences; import org.jabref.logic.remote.client.RemoteClient; -import org.jabref.logic.util.OS; import org.jabref.migrations.PreferencesMigrations; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; @@ -141,10 +140,6 @@ private static void applyPreferences(PreferencesService preferences) { // Initialize protected terms loader Globals.protectedTermsLoader = new ProtectedTermsLoader(preferences.getProtectedTermsPreferences()); - - // Override used newline character with the one stored in the preferences. - // The preferences return the system newline character sequence as default. - OS.NEWLINE = preferences.getNewLineSeparator().toString(); } private static void configureProxy(ProxyPreferences proxyPreferences) { diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 8b38bfbcca2..9757b885615 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -32,7 +32,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.shared.prefs.SharedDatabasePreferences; -import org.jabref.logic.util.OS; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.ChangePropagation; @@ -231,13 +230,14 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, SavePreferences savePreferences = this.preferences.getSavePreferences() .withSaveType(saveType); try (AtomicFileWriter fileWriter = new AtomicFileWriter(file, encoding, savePreferences.shouldMakeBackup())) { - BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); + BibDatabaseContext bibDatabaseContext = libraryTab.getBibDatabaseContext(); + BibWriter bibWriter = new BibWriter(fileWriter, bibDatabaseContext.getDatabase().getNewLineSeparator()); BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, entryTypesManager); if (selectedOnly) { - databaseWriter.savePartOfDatabase(libraryTab.getBibDatabaseContext(), libraryTab.getSelectedEntries()); + databaseWriter.savePartOfDatabase(bibDatabaseContext, libraryTab.getSelectedEntries()); } else { - databaseWriter.saveDatabase(libraryTab.getBibDatabaseContext()); + databaseWriter.saveDatabase(bibDatabaseContext); } libraryTab.registerUndoableChanges(databaseWriter.getSaveActionsFieldChanges()); diff --git a/src/main/java/org/jabref/gui/preferences/file/FileTab.fxml b/src/main/java/org/jabref/gui/preferences/file/FileTab.fxml index 742560fb2d3..8cc56872d65 100644 --- a/src/main/java/org/jabref/gui/preferences/file/FileTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/file/FileTab.fxml @@ -31,10 +31,6 @@ toggleGroup="$stringsResolveToggleGroup"/> - -