From aa42c16a8b9e7d051ef34ec0b4e8760af56e9a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lenhard?= Date: Thu, 14 Jul 2016 14:14:41 +0200 Subject: [PATCH] Preserve user comments in bib file (#1471) * Add failing test with preceding comment * Add failing parser test * Improve test naming * Reuse Globals.ENCODING_PREFIX in test * Check explicitly for encoding line and purge it * Add changelog entry * Write out user comments also for modified entries * Add test to check preservation of ENCODING_PREFIX inside an entry * Make BibEntryWriter more robust when dealing with faulty parsed serializations * Add test with user comment in file * Add test that changes and reformats an entry with a user comment * Add test with user comment before String * Preserve newlines also when used with bibtex strings * Add test for serialization of epilog * Fix string detection in test * In case of change, only remove trailing whitespaces between user comments and entry * Remove unused variable * Remove unused import * Reformat changelog entry * Move comment detection logic to BibtexString * Compute user comment in string only once * Move user comment computation to BibEntry * Add test for comment in the same line as BibEntry * Remove unnecessary epilog test * Remove redundant test bib file * Remove redundant asserts * Elevate registry actions * Delete stuff * Revert "Elevate registry actions" This reverts commit b4b288a87f7f2a606b025fd8b3d3b00b150346fb. * Revert "Delete stuff" This reverts commit 67a48859b5b2973f01bb41ffddce802e8825f3ac. * Use optional in assert * Remove duplicate test * Remove unnecessary asserts * Remove unused import of Optional --- CHANGELOG.md | 1 + .../sf/jabref/exporter/BibDatabaseWriter.java | 10 + .../importer/fileformat/BibtexParser.java | 33 ++- .../jabref/logic/bibtex/BibEntryWriter.java | 24 ++- .../net/sf/jabref/model/entry/BibEntry.java | 25 +++ .../sf/jabref/model/entry/BibtexString.java | 24 +++ .../exporter/BibDatabaseWriterTest.java | 61 ++++++ .../importer/fileformat/BibtexParserTest.java | 202 ++++++++++++------ .../logic/bibtex/BibEntryWriterTest.java | 124 ++++++----- .../bibWithUserCommentAndEntryChange.bib | 41 ++++ .../resources/testbib/bibWithUserComments.bib | 43 ++++ src/test/resources/testbib/complex.bib | 2 + 12 files changed, 438 insertions(+), 152 deletions(-) create mode 100644 src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib create mode 100644 src/test/resources/testbib/bibWithUserComments.bib diff --git a/CHANGELOG.md b/CHANGELOG.md index 102ab8ddf08..3dd3c72536d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# ## [Unreleased] ### Changed +- [#1026](https://github.com/JabRef/jabref/issues/1026) JabRef does no longer delete user comments outside of BibTeX entries and strings ### Fixed diff --git a/src/main/java/net/sf/jabref/exporter/BibDatabaseWriter.java b/src/main/java/net/sf/jabref/exporter/BibDatabaseWriter.java index 244da0b7f7c..18ddd90a3e3 100644 --- a/src/main/java/net/sf/jabref/exporter/BibDatabaseWriter.java +++ b/src/main/java/net/sf/jabref/exporter/BibDatabaseWriter.java @@ -317,6 +317,8 @@ private void writeString(Writer fw, BibtexString bs, Map r return; } + writeUserCommentsForString(bs, fw); + if(isFirstStringInType) { fw.write(Globals.NEWLINE); } @@ -354,6 +356,14 @@ private void writeString(Writer fw, BibtexString bs, Map r fw.write("}" + Globals.NEWLINE); } + private void writeUserCommentsForString(BibtexString string, Writer out) throws IOException { + String userComments = string.getUserComments(); + + if(!userComments.isEmpty()) { + out.write(userComments + Globals.NEWLINE); + } + } + /** * Write all strings in alphabetical order, modified to produce a safe (for * BibTeX) order of the strings if they reference each other. diff --git a/src/main/java/net/sf/jabref/importer/fileformat/BibtexParser.java b/src/main/java/net/sf/jabref/importer/fileformat/BibtexParser.java index 788abd77069..78da8c78419 100644 --- a/src/main/java/net/sf/jabref/importer/fileformat/BibtexParser.java +++ b/src/main/java/net/sf/jabref/importer/fileformat/BibtexParser.java @@ -29,6 +29,7 @@ import java.util.Objects; import java.util.Optional; +import net.sf.jabref.Globals; import net.sf.jabref.MetaData; import net.sf.jabref.importer.ParserResult; import net.sf.jabref.logic.bibtex.FieldContentParser; @@ -323,29 +324,23 @@ private String dumpTextReadSoFarToString() { // if there is no entry found, simply return the content (necessary to parse text remaining after the last entry) if (indexOfAt == -1) { return purgeEOFCharacters(result); - } else { - - //skip all text except newlines and whitespaces before first @. This is necessary to remove the file header - int runningIndex = indexOfAt - 1; - while (runningIndex >= 0) { - if (!Character.isWhitespace(result.charAt(runningIndex))) { + } else if(result.contains(Globals.ENCODING_PREFIX)) { + // purge the encoding line if it exists + int runningIndex = result.indexOf(Globals.ENCODING_PREFIX); + while(runningIndex < indexOfAt) { + if(result.charAt(runningIndex) == '\n') { + break; + } else if(result.charAt(runningIndex) == '\r') { + if(result.charAt(runningIndex + 1) == '\n') { + runningIndex++; + } break; } - runningIndex--; - } - - if(runningIndex > -1) { - // We have to ignore some text at the beginning - // so we view the first line break as the end of the previous text and don't store it - if(result.charAt(runningIndex + 1) == '\r') { - runningIndex++; - } - if(result.charAt(runningIndex + 1) == '\n') { - runningIndex++; - } + runningIndex++; } - return result.substring(runningIndex + 1); + } else { + return result; } } diff --git a/src/main/java/net/sf/jabref/logic/bibtex/BibEntryWriter.java b/src/main/java/net/sf/jabref/logic/bibtex/BibEntryWriter.java index 8cba85933a4..dc349209536 100644 --- a/src/main/java/net/sf/jabref/logic/bibtex/BibEntryWriter.java +++ b/src/main/java/net/sf/jabref/logic/bibtex/BibEntryWriter.java @@ -37,10 +37,10 @@ public void write(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode) t /** * Writes the given BibEntry using the given writer * - * @param entry The entry to write - * @param out The writer to use + * @param entry The entry to write + * @param out The writer to use * @param bibDatabaseMode The database mode (bibtex or biblatex) - * @param reformat Should the entry be in any case, even if no change occurred? + * @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 { // if the entry has not been modified, write it as it was @@ -48,11 +48,21 @@ public void write(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode, B out.write(entry.getParsedSerialization()); return; } + + writeUserComments(entry, out); out.write(Globals.NEWLINE); writeRequiredFieldsFirstRemainingFieldsSecond(entry, out, bibDatabaseMode); out.write(Globals.NEWLINE); } + private void writeUserComments(BibEntry entry, Writer out) throws IOException { + String userComments = entry.getUserComments(); + + if(!userComments.isEmpty()) { + out.write(userComments + Globals.NEWLINE); + } + } + public void writeWithoutPrependedNewlines(BibEntry entry, Writer out, BibDatabaseMode bibDatabaseMode) throws IOException { // if the entry has not been modified, write it as it was if (!entry.hasChanged()) { @@ -71,7 +81,7 @@ public void writeWithoutPrependedNewlines(BibEntry entry, Writer out, BibDatabas * @throws IOException */ private void writeRequiredFieldsFirstRemainingFieldsSecond(BibEntry entry, Writer out, - BibDatabaseMode bibDatabaseMode) throws IOException { + BibDatabaseMode bibDatabaseMode) throws IOException { // Write header with type and bibtex-key. TypedBibEntry typedEntry = new TypedBibEntry(entry, Optional.empty(), bibDatabaseMode); out.write('@' + typedEntry.getTypeForDisplay() + '{'); @@ -127,9 +137,9 @@ private void writeKeyField(BibEntry entry, Writer out) throws IOException { /** * Write a single field, if it has any content. * - * @param entry the entry to write - * @param out the target of the write - * @param name The field name + * @param entry the entry to write + * @param out the target of the write + * @param name The field name * @throws IOException In case of an IO error */ private void writeField(BibEntry entry, Writer out, String name, int indentation) throws IOException { diff --git a/src/main/java/net/sf/jabref/model/entry/BibEntry.java b/src/main/java/net/sf/jabref/model/entry/BibEntry.java index b8bd9b1bb38..f4c5a86c84e 100644 --- a/src/main/java/net/sf/jabref/model/entry/BibEntry.java +++ b/src/main/java/net/sf/jabref/model/entry/BibEntry.java @@ -629,6 +629,31 @@ public BibEntry withField(String field, String value) { return this; } + /* + * Returns user comments (arbitrary text before the entry), if they exist. If not, returns the empty String + */ + public String getUserComments() { + + if (parsedSerialization != null) { + + try { + // get the text before the entry + String prolog = parsedSerialization.substring(0, parsedSerialization.indexOf('@')); + + // delete trailing whitespaces (between entry and text) + prolog = prolog.replaceFirst("\\s+$", ""); + + // if there is any non whitespace text, write it + if (prolog.length() > 0) { + return prolog; + } + } catch (StringIndexOutOfBoundsException ignore) { + // if this occurs a broken parsed serialization has been set, so just do nothing + } + } + return ""; + } + public Set getFieldAsWords(String field) { String fieldName = toLowerCase(field); Set storedList = fieldsAsWords.get(fieldName); diff --git a/src/main/java/net/sf/jabref/model/entry/BibtexString.java b/src/main/java/net/sf/jabref/model/entry/BibtexString.java index 95eb37c3dcd..125161e2eb4 100644 --- a/src/main/java/net/sf/jabref/model/entry/BibtexString.java +++ b/src/main/java/net/sf/jabref/model/entry/BibtexString.java @@ -152,4 +152,28 @@ public String getParsedSerialization() { public boolean hasChanged(){ return hasChanged; } + + /* + * Returns user comments (arbitrary text before the string) if there are any. If not returns the empty string + */ + 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; + } + } catch(StringIndexOutOfBoundsException ignore) { + // if this occurs a broken parsed serialization has been set, so just do nothing + } + } + + return ""; + } } diff --git a/src/test/java/net/sf/jabref/exporter/BibDatabaseWriterTest.java b/src/test/java/net/sf/jabref/exporter/BibDatabaseWriterTest.java index a737d33db30..57fef45bc27 100644 --- a/src/test/java/net/sf/jabref/exporter/BibDatabaseWriterTest.java +++ b/src/test/java/net/sf/jabref/exporter/BibDatabaseWriterTest.java @@ -291,6 +291,66 @@ public void roundtrip() throws IOException { } } + @Test + public void roundtripWithUserComment() throws IOException { + Path testBibtexFile = Paths.get("src/test/resources/testbib/bibWithUserComments.bib"); + Charset encoding = StandardCharsets.UTF_8; + ParserResult result = BibtexParser.parse(ImportFormat.getReader(testBibtexFile, encoding)); + + SavePreferences preferences = new SavePreferences().withEncoding(encoding).withSaveInOriginalOrder(true); + BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData(), + new Defaults(BibDatabaseMode.BIBTEX)); + + databaseWriter.writePartOfDatabase(stringWriter, context, result.getDatabase().getEntries(), preferences); + try (Scanner scanner = new Scanner(testBibtexFile,encoding.name())) { + assertEquals(scanner.useDelimiter("\\A").next(), stringWriter.toString()); + } + } + + @Test + public void roundtripWithUserCommentAndEntryChange() throws IOException { + Path testBibtexFile = Paths.get("src/test/resources/testbib/bibWithUserComments.bib"); + Charset encoding = StandardCharsets.UTF_8; + ParserResult result = BibtexParser.parse(ImportFormat.getReader(testBibtexFile, encoding)); + + BibEntry entry = result.getDatabase().getEntryByKey("1137631").get(); + entry.setField("author", "Mr. Author"); + + SavePreferences preferences = new SavePreferences().withEncoding(encoding).withSaveInOriginalOrder(true); + BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData(), + new Defaults(BibDatabaseMode.BIBTEX)); + + databaseWriter.writePartOfDatabase(stringWriter, context, result.getDatabase().getEntries(), preferences); + + try (Scanner scanner = new Scanner(Paths.get("src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib"),encoding.name())) { + assertEquals(scanner.useDelimiter("\\A").next(), stringWriter.toString()); + } + } + + @Test + public void roundtripWithUserCommentBeforeStringAndChange() throws IOException { + Path testBibtexFile = Paths.get("src/test/resources/testbib/complex.bib"); + Charset encoding = StandardCharsets.UTF_8; + ParserResult result = BibtexParser.parse(ImportFormat.getReader(testBibtexFile, encoding)); + + BibtexString string = result.getDatabase().getStringValues().iterator().next(); + if(string.getContent().isEmpty()) { + // do nothing + } else { + string.setContent("my first string"); + } + + SavePreferences preferences = new SavePreferences().withEncoding(encoding).withSaveInOriginalOrder(true); + BibDatabaseContext context = new BibDatabaseContext(result.getDatabase(), result.getMetaData(), + new Defaults(BibDatabaseMode.BIBTEX)); + + databaseWriter.writePartOfDatabase(stringWriter, context, result.getDatabase().getEntries(), preferences); + + try (Scanner scanner = new Scanner(testBibtexFile,encoding.name())) { + assertEquals(scanner.useDelimiter("\\A").next(), stringWriter.toString()); + } + } + @Test public void writeSavedSerializationOfEntryIfUnchanged() throws IOException { BibEntry entry = new BibEntry(); @@ -543,4 +603,5 @@ public void writeEntriesInOriginalOrderWhenNoSaveOrderConfigIsSetInMetadata() th + Globals.NEWLINE , stringWriter.toString()); } + } diff --git a/src/test/java/net/sf/jabref/importer/fileformat/BibtexParserTest.java b/src/test/java/net/sf/jabref/importer/fileformat/BibtexParserTest.java index fc2e8a76fed..adc87ca2342 100644 --- a/src/test/java/net/sf/jabref/importer/fileformat/BibtexParserTest.java +++ b/src/test/java/net/sf/jabref/importer/fileformat/BibtexParserTest.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import net.sf.jabref.Globals; import net.sf.jabref.JabRefPreferences; @@ -312,10 +313,10 @@ public void parseRecognizesMultipleEntries() throws IOException { ParserResult result = BibtexParser .parse(new StringReader( "@article{canh05," - + " author = {Crowston, K. and Annabi, H.},\n" - + " title = {Title A}}\n" - + "@inProceedings{foo," - + " author={Norton Bar}}")); + + " author = {Crowston, K. and Annabi, H.},\n" + + " title = {Title A}}\n" + + "@inProceedings{foo," + + " author={Norton Bar}}")); List parsed = result.getDatabase().getEntries(); List expected = new ArrayList<>(); @@ -430,28 +431,28 @@ public void parseCombinesMultipleKeywordsFields() throws IOException { public void parseRecognizesHeaderButIgnoresEncoding() throws IOException { ParserResult result = BibtexParser.parse(new StringReader( "This file was created with JabRef 2.1 beta 2." - + "\n" - + "Encoding: Cp1252" - + "\n" - + "" - + "\n" - + "@INPROCEEDINGS{CroAnnHow05," - + "\n" - + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," - + "\n" - + " title = {Effective work practices for floss development: A model and propositions}," - + "\n" - + " booktitle = {Hawaii International Conference On System Sciences (HICSS)}," - + "\n" - + " year = {2005}," - + "\n" - + " owner = {oezbek}," - + "\n" - + " timestamp = {2006.05.29}," - + "\n" - + " url = {http://james.howison.name/publications.html}" - + "\n" - + "}))")); + + "\n" + + "Encoding: Cp1252" + + "\n" + + "" + + "\n" + + "@INPROCEEDINGS{CroAnnHow05," + + "\n" + + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," + + "\n" + + " title = {Effective work practices for floss development: A model and propositions}," + + "\n" + + " booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + + "\n" + + " year = {2005}," + + "\n" + + " owner = {oezbek}," + + "\n" + + " timestamp = {2006.05.29}," + + "\n" + + " url = {http://james.howison.name/publications.html}" + + "\n" + + "}))")); assertEquals(Globals.prefs.getDefaultEncoding(), result.getMetaData().getEncoding()); Collection c = result.getDatabase().getEntries(); @@ -593,20 +594,20 @@ public void parseReturnsEmptyListIfNoEntryRecognized() throws IOException { ParserResult result = BibtexParser .parse(new StringReader( " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," - + "\n" - + " title = {Effective work practices for floss development: A model and propositions}," - + "\n" - + " booktitle = {Hawaii International Conference On System Sciences (HICSS)}," - + "\n" - + " year = {2005}," - + "\n" - + " owner = {oezbek}," - + "\n" - + " timestamp = {2006.05.29}," - + "\n" - + " url = {http://james.howison.name/publications.html}" - + "\n" - + "}))")); + + "\n" + + " title = {Effective work practices for floss development: A model and propositions}," + + "\n" + + " booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + + "\n" + + " year = {2005}," + + "\n" + + " owner = {oezbek}," + + "\n" + + " timestamp = {2006.05.29}," + + "\n" + + " url = {http://james.howison.name/publications.html}" + + "\n" + + "}))")); Collection c = result.getDatabase().getEntries(); assertEquals(0, c.size()); } @@ -952,16 +953,16 @@ public void parseRecognizesMultipleStrings() throws IOException { public void parseRecognizesStringAndEntry() throws IOException { ParserResult result = BibtexParser.parse(new StringReader( - "" - + "@string{bourdieu = {Bourdieu, Pierre}}" - + "@book{bourdieu-2002-questions-sociologie, " - + " Address = {Paris}," - + " Author = bourdieu," - + " Isbn = 2707318256," - + " Publisher = {Minuit}," - + " Title = {Questions de sociologie}," - + " Year = 2002" - + "}")); + "" + + "@string{bourdieu = {Bourdieu, Pierre}}" + + "@book{bourdieu-2002-questions-sociologie, " + + " Address = {Paris}," + + " Author = bourdieu," + + " Isbn = 2707318256," + + " Publisher = {Minuit}," + + " Title = {Questions de sociologie}," + + " Year = 2002" + + "}")); assertEquals(1, result.getDatabase().getStringCount()); BibtexString s = result.getDatabase().getStringValues().iterator().next(); @@ -1253,10 +1254,10 @@ public void parseSavesNewlinesBeforeEntryInParsedSerialization() throws IOExcept } @Test - public void parseSavesOnlyRealNewlinesBeforeEntryInParsedSerialization() throws IOException { + public void parseRemovesEncodingLineInParsedSerialization() throws IOException { String testEntry = "@article{test,author={Ed von Test}}"; ParserResult result = BibtexParser.parse( - new StringReader("%Encoding: no" + Globals.NEWLINE + Globals.NEWLINE + Globals.NEWLINE + testEntry)); + new StringReader(Globals.ENCODING_PREFIX + Globals.NEWLINE + Globals.NEWLINE + Globals.NEWLINE + testEntry)); Collection c = result.getDatabase().getEntries(); assertEquals(1, c.size()); @@ -1378,7 +1379,7 @@ public void integrationTestSaveOrderConfig() throws IOException { Optional saveOrderConfig = result.getMetaData().getSaveOrderConfig(); assertEquals(new SaveOrderConfig(false, new SaveOrderConfig.SortCriterion("author", false), - new SaveOrderConfig.SortCriterion("year", true), new SaveOrderConfig.SortCriterion("abstract", false)), + new SaveOrderConfig.SortCriterion("year", true), new SaveOrderConfig.SortCriterion("abstract", false)), saveOrderConfig.get()); } @@ -1387,8 +1388,8 @@ public void integrationTestCustomKeyPattern() throws IOException { ParserResult result = BibtexParser .parse(new StringReader( "@comment{jabref-meta: keypattern_article:articleTest;}" - + Globals.NEWLINE - + "@comment{jabref-meta: keypatterndefault:test;}")); + + Globals.NEWLINE + + "@comment{jabref-meta: keypatterndefault:test;}")); AbstractLabelPattern labelPattern = result.getMetaData().getLabelPattern(); @@ -1412,17 +1413,17 @@ public void integrationTestBiblatexMode() throws IOException { public void integrationTestGroupTree() throws IOException, ParseException { ParserResult result = BibtexParser.parse(new StringReader( "@comment{jabref-meta: groupsversion:3;}" - + Globals.NEWLINE + - "@comment{jabref-meta: groupstree:" - + Globals.NEWLINE - + "0 AllEntriesGroup:;" - + Globals.NEWLINE - + "1 KeywordGroup:Fréchet\\;0\\;keywords\\;FrechetSpace\\;0\\;1\\;;" - + Globals.NEWLINE - + "1 KeywordGroup:Invariant theory\\;0\\;keywords\\;GIT\\;0\\;0\\;;" - + Globals.NEWLINE - + "1 ExplicitGroup:TestGroup\\;0\\;Key1\\;Key2\\;;" - + "}")); + + Globals.NEWLINE + + "@comment{jabref-meta: groupstree:" + + Globals.NEWLINE + + "0 AllEntriesGroup:;" + + Globals.NEWLINE + + "1 KeywordGroup:Fréchet\\;0\\;keywords\\;FrechetSpace\\;0\\;1\\;;" + + Globals.NEWLINE + + "1 KeywordGroup:Invariant theory\\;0\\;keywords\\;GIT\\;0\\;0\\;;" + + Globals.NEWLINE + + "1 ExplicitGroup:TestGroup\\;0\\;Key1\\;Key2\\;;" + + "}")); GroupTreeNode root = result.getMetaData().getGroups(); @@ -1434,7 +1435,7 @@ public void integrationTestGroupTree() throws IOException, ParseException { assertEquals( new KeywordGroup("Invariant theory", "keywords", "GIT", false, false, GroupHierarchyType.INDEPENDENT), root.getChildren().get(1).getGroup()); - assertEquals(Arrays.asList("Key1", "Key2"), ((ExplicitGroup)root.getChildren().get(2).getGroup()).getLegacyEntryKeys()); + assertEquals(Arrays.asList("Key1", "Key2"), ((ExplicitGroup) root.getChildren().get(2).getGroup()).getLegacyEntryKeys()); } @Test @@ -1457,7 +1458,7 @@ public void integrationTestFileDirectories() throws IOException { ParserResult result = BibtexParser .parse(new StringReader( "@comment{jabref-meta: fileDirectory:\\\\Literature\\\\;}" - + "@comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\\\Documents;}")); + + "@comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\\\Documents;}")); assertEquals("\\Literature\\", result.getMetaData().getDefaultFileDirectory().get()); assertEquals("D:\\Documents", result.getMetaData().getUserFileDirectory("defaultOwner-user").get()); @@ -1486,4 +1487,69 @@ public void parseReturnsEntriesInSameOrder() throws IOException { assertEquals(expected, result.getDatabase().getEntries()); } + + @Test + public void parsePrecedingComment() throws IOException { + // @formatter:off + String bibtexEntry = "% Some random comment that should stay here" + Globals.NEWLINE + + "@Article{test," + Globals.NEWLINE + + " Author = {Foo Bar}," + Globals.NEWLINE + + " Journal = {International Journal of Something}," + Globals.NEWLINE + + " Note = {some note}," + Globals.NEWLINE + + " Number = {1}" + Globals.NEWLINE + + "}"; + // @formatter:on + + // read in bibtex string + ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); + + Collection entries = result.getDatabase().getEntries(); + assertEquals(1, entries.size()); + + BibEntry entry = entries.iterator().next(); + assertEquals("test", entry.getCiteKey()); + assertEquals(5, entry.getFieldNames().size()); + Set fields = entry.getFieldNames(); + assertTrue(fields.contains("author")); + assertEquals("Foo Bar", entry.getField("author")); + assertEquals(bibtexEntry, entry.getParsedSerialization()); + } + + @Test + public void parseCommentAndEntryInOneLine() throws IOException { + // @formatter:off + String bibtexEntry = "Some random comment that should stay here @Article{test," + Globals.NEWLINE + + " Author = {Foo Bar}," + Globals.NEWLINE + + " Journal = {International Journal of Something}," + Globals.NEWLINE + + " Note = {some note}," + Globals.NEWLINE + + " Number = {1}" + Globals.NEWLINE + + "}"; + // @formatter:on + + // read in bibtex string + ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); + + Collection entries = result.getDatabase().getEntries(); + assertEquals(1, entries.size()); + + BibEntry entry = entries.iterator().next(); + assertEquals("test", entry.getCiteKey()); + assertEquals(5, entry.getFieldNames().size()); + Set fields = entry.getFieldNames(); + assertTrue(fields.contains("author")); + assertEquals("Foo Bar", entry.getField("author")); + assertEquals(bibtexEntry, entry.getParsedSerialization()); + } + + @Test + public void preserveEncodingPrefixInsideEntry() { + List parsed = BibtexParser.fromString("@article{test,author={" + Globals.ENCODING_PREFIX + "}}"); + + BibEntry expected = new BibEntry(); + expected.setType("article"); + expected.setCiteKey("test"); + expected.setField("author", Globals.ENCODING_PREFIX); + assertEquals(Collections.singletonList(expected), parsed); + } + } diff --git a/src/test/java/net/sf/jabref/logic/bibtex/BibEntryWriterTest.java b/src/test/java/net/sf/jabref/logic/bibtex/BibEntryWriterTest.java index 3721ae7650e..be74d915a9f 100644 --- a/src/test/java/net/sf/jabref/logic/bibtex/BibEntryWriterTest.java +++ b/src/test/java/net/sf/jabref/logic/bibtex/BibEntryWriterTest.java @@ -83,16 +83,8 @@ public void roundTripTest() throws IOException { // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(5, entry.getFieldNames().size()); - Set fields = entry.getFieldNames(); - assertTrue(fields.contains("author")); - assertEquals("Foo Bar", entry.getField("author")); //write out bibtex string StringWriter stringWriter = new StringWriter(); @@ -115,16 +107,8 @@ public void roundTripWithPrependingNewlines() throws IOException { // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(5, entry.getFieldNames().size()); - Set fields = entry.getFieldNames(); - assertTrue(fields.contains("author")); - assertEquals("Foo Bar", entry.getField("author")); //write out bibtex string StringWriter stringWriter = new StringWriter(); @@ -147,21 +131,12 @@ public void roundTripWithModification() throws IOException { // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(5, entry.getFieldNames().size()); // Modify entry entry.setField("author", "BlaBla"); - Set fields = entry.getFieldNames(); - assertTrue(fields.contains("author")); - assertEquals("BlaBla", entry.getField("author")); - // write out bibtex string StringWriter stringWriter = new StringWriter(); writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); @@ -192,24 +167,14 @@ public void roundTripWithCamelCasingInTheOriginalEntryAndResultInLowerCase() thr // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(6, entry.getFieldNames().size()); // modify entry entry.setField("author", "BlaBla"); - Set fields = entry.getFieldNames(); - assertTrue(fields.contains("author")); - assertEquals("BlaBla", entry.getField("author")); - //write out bibtex string StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); String actual = stringWriter.toString(); @@ -238,16 +203,8 @@ public void roundTripWithAppendedNewlines() throws IOException { // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(5, entry.getFieldNames().size()); - Set fields = entry.getFieldNames(); - assertTrue(fields.contains("author")); - assertEquals("Foo Bar", entry.getField("author")); //write out bibtex string StringWriter stringWriter = new StringWriter(); @@ -279,16 +236,8 @@ public void multipleWritesWithoutModification() throws IOException { private String testSingleWrite(String bibtexEntry) throws IOException { // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(5, entry.getFieldNames().size()); - Set fields = entry.getFieldNames(); - assertTrue(fields.contains("author")); - assertEquals("Foo Bar", entry.getField("author")); //write out bibtex string StringWriter stringWriter = new StringWriter(); @@ -311,16 +260,13 @@ public void monthFieldSpecialSyntax() throws IOException { // read in bibtex string ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); - Collection entries = result.getDatabase().getEntries(); - assertEquals(1, entries.size()); - BibEntry entry = entries.iterator().next(); - assertEquals("test", entry.getCiteKey()); - assertEquals(4, entry.getFieldNames().size()); + + // modify month field Set fields = entry.getFieldNames(); assertTrue(fields.contains("month")); - assertEquals("#mar#", entry.getField("month")); + assertEquals("#mar#", entry.getFieldOptional("month").get()); //write out bibtex string StringWriter stringWriter = new StringWriter(); @@ -351,7 +297,6 @@ public void addFieldWithLongerLength() throws IOException { //write out bibtex string StringWriter stringWriter = new StringWriter(); - writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); String actual = stringWriter.toString(); @@ -403,4 +348,67 @@ public void trimFieldContents() throws IOException { assertEquals(expected, actual); } + + @Test + public void roundTripWithPrecedingCommentTest() throws IOException { + // @formatter:off + String bibtexEntry = "% Some random comment that should stay here" + Globals.NEWLINE + + "@Article{test," + Globals.NEWLINE + + " Author = {Foo Bar}," + Globals.NEWLINE + + " Journal = {International Journal of Something}," + Globals.NEWLINE + + " Note = {some note}," + Globals.NEWLINE + + " Number = {1}" + Globals.NEWLINE + + "}"; + // @formatter:on + + // read in bibtex string + ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); + Collection entries = result.getDatabase().getEntries(); + BibEntry entry = entries.iterator().next(); + + //write out bibtex string + StringWriter stringWriter = new StringWriter(); + writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + String actual = stringWriter.toString(); + + assertEquals(bibtexEntry, actual); + } + + @Test + public void roundTripWithPrecedingCommentAndModificationTest() throws IOException { + // @formatter:off + String bibtexEntry = "% Some random comment that should stay here" + Globals.NEWLINE + + "@Article{test," + Globals.NEWLINE + + " Author = {Foo Bar}," + Globals.NEWLINE + + " Journal = {International Journal of Something}," + Globals.NEWLINE + + " Note = {some note}," + Globals.NEWLINE + + " Number = {1}" + Globals.NEWLINE + + "}"; + // @formatter:on + + // read in bibtex string + ParserResult result = BibtexParser.parse(new StringReader(bibtexEntry)); + Collection entries = result.getDatabase().getEntries(); + BibEntry entry = entries.iterator().next(); + + // change the entry + entry.setField("author", "John Doe"); + + //write out bibtex string + StringWriter stringWriter = new StringWriter(); + writer.write(entry, stringWriter, BibDatabaseMode.BIBTEX); + String actual = stringWriter.toString(); + // @formatter:off + String expected = "% Some random comment that should stay here" + Globals.NEWLINE + Globals.NEWLINE + + "@Article{test," + Globals.NEWLINE + + " author = {John Doe}," + Globals.NEWLINE + + " journal = {International Journal of Something}," + Globals.NEWLINE + + " number = {1}," + Globals.NEWLINE + + " note = {some note}," + Globals.NEWLINE + + "}" + Globals.NEWLINE; + // @formatter:on + + assertEquals(expected, actual); + } + } diff --git a/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib b/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib new file mode 100644 index 00000000000..ea17f6aaae5 --- /dev/null +++ b/src/test/resources/testbib/bibWithUserCommentAndEntryChange.bib @@ -0,0 +1,41 @@ +% Encoding: UTF-8 + +@Preamble{preamble} + +@String{firstString = {my first string}} +@String{secondString = {}} + +@ARTICLE{1102917, + author = {E. Bardram}, + title = {The trouble with login: on usability and computer security in ubiquitous + computing}, + journal = {Personal Ubiquitous Comput.}, + year = {2005}, + volume = {9}, + pages = {357--367}, + number = {6}, + address = {London, UK}, + bdsk-url-1 = {http://dx.doi.org/10.1007/s00779-005-0347-6}, + doi = {http://dx.doi.org/10.1007/s00779-005-0347-6}, + issn = {1617-4909}, + publisher = {Springer-Verlag} +} + +This is some arbitrary user comment that should be preserved + +@InProceedings{1137631, + author = {Mr. Author}, + title = {Extending XP practices to support security requirements engineering}, + booktitle = {SESS '06: Proceedings of the 2006 international workshop on Software engineering for secure systems}, + year = {2006}, + pages = {11--18}, + address = {New York, NY, USA}, + publisher = {ACM}, + bdsk-url-1 = {http://doi.acm.org/10.1145/1137627.1137631}, + doi = {http://doi.acm.org/10.1145/1137627.1137631}, + file = {:/Volumes/iDisk/Freie Universität Berlin/Semester 9/Softwareprozesse/p11-bostrom.pdf:PDF}, + isbn = {1-59593-411-1}, + location = {Shanghai, China}, +} + +@Comment{jabref-meta: databaseType:bibtex;} diff --git a/src/test/resources/testbib/bibWithUserComments.bib b/src/test/resources/testbib/bibWithUserComments.bib new file mode 100644 index 00000000000..8148c3b6d63 --- /dev/null +++ b/src/test/resources/testbib/bibWithUserComments.bib @@ -0,0 +1,43 @@ +% Encoding: UTF-8 + +@Preamble{preamble} + +@String{firstString = {my first string}} +@String{secondString = {}} + +@ARTICLE{1102917, + author = {E. Bardram}, + title = {The trouble with login: on usability and computer security in ubiquitous + computing}, + journal = {Personal Ubiquitous Comput.}, + year = {2005}, + volume = {9}, + pages = {357--367}, + number = {6}, + address = {London, UK}, + bdsk-url-1 = {http://dx.doi.org/10.1007/s00779-005-0347-6}, + doi = {http://dx.doi.org/10.1007/s00779-005-0347-6}, + issn = {1617-4909}, + publisher = {Springer-Verlag} +} + +This is some arbitrary user comment that should be preserved + +@INPROCEEDINGS{1137631, + author = {Gustav Bostr\"{o}m and Jaana W\"{a}yrynen and Marine Bod\'{e}n and + Konstantin Beznosov and Philippe Kruchten}, + title = {Extending XP practices to support security requirements engineering}, + booktitle = {SESS '06: Proceedings of the 2006 international workshop on Software + engineering for secure systems}, + year = {2006}, + pages = {11--18}, + address = {New York, NY, USA}, + publisher = {ACM}, + bdsk-url-1 = {http://doi.acm.org/10.1145/1137627.1137631}, + doi = {http://doi.acm.org/10.1145/1137627.1137631}, + file = {:/Volumes/iDisk/Freie Universität Berlin/Semester 9/Softwareprozesse/p11-bostrom.pdf:PDF}, + isbn = {1-59593-411-1}, + location = {Shanghai, China} +} + +@Comment{jabref-meta: databaseType:bibtex;} diff --git a/src/test/resources/testbib/complex.bib b/src/test/resources/testbib/complex.bib index 09ecb967c41..ee64be35dc5 100644 --- a/src/test/resources/testbib/complex.bib +++ b/src/test/resources/testbib/complex.bib @@ -2,6 +2,8 @@ @Preamble{preamble} +This is some arbitrary user comment that should be preserved + @String{firstString = {my first string}} @String{secondString = {}}