Skip to content

Fix 15 crlf support #16

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* text eol=lf
src/test/resources/testcrlf* text eol=crlf
34 changes: 32 additions & 2 deletions src/main/java/org/codejive/properties/Properties.java
Original file line number Diff line number Diff line change
Expand Up @@ -903,21 +903,51 @@ public void store(Writer writer, String... comment) throws IOException {
Cursor pos = first();
if (comment.length > 0) {
pos = skipHeaderCommentLines();
String nl = determineNewline();
List<String> newcs = normalizeComments(Arrays.asList(comment), "# ");
for (String c : newcs) {
writer.write(new PropertiesParser.Token(PropertiesParser.Type.COMMENT, c).getRaw());
writer.write(PropertiesParser.Token.EOL.getRaw());
writer.write(nl);
}
// We write an extra empty line so this comment won't be taken as part of the first
// property
writer.write(PropertiesParser.Token.EOL.getRaw());
writer.write(nl);
}
while (pos.hasToken()) {
writer.write(pos.raw());
pos.next();
}
}

/**
* This method determines the newline string to use when generating line terminators. It looks
* at all existing line terminators and will use those for any new lines. In case of ambiguity
* (a file contains both LF and CRLF terminators) it will return the system's default line
* ending.
*
* @return A string containing the line ending to use
*/
private String determineNewline() {
boolean lf = false;
boolean crlf = false;
for (PropertiesParser.Token token : tokens) {
if (token.isWs()) {
if (token.raw.endsWith("/r/n")) {
crlf = true;
} else if (token.raw.endsWith("/n")) {
lf = true;
}
}
}
if (lf && crlf) {
return System.lineSeparator();
} else if (crlf) {
return "/r/n";
} else {
return "\n";
}
}

private Cursor skipHeaderCommentLines() {
Cursor pos = first();
// Skip a single following whitespace if it is NOT an EOL token
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/org/codejive/properties/PropertiesParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ private void addChar(int ch) throws IOException {
}

private void readEol(int ch) throws IOException {
if (ch == '\n') {
if (peekChar() == '\r') {
if (ch == '\r') {
if (peekChar() == '\n') {
str.append((char) readChar());
}
}
Expand Down Expand Up @@ -327,15 +327,15 @@ static String unescape(String escape) {
txt.append((char) Integer.parseInt(num, 16));
i += 4;
break;
case '\n':
// Skip the next character if it's a '\r'
if (i < escape.length() && escape.charAt(i + 1) == '\r') {
case '\r':
// Skip the next character if it's a '\n'
if (i < (escape.length() - 1) && escape.charAt(i + 1) == '\n') {
i++;
}
// fall-through!
case '\r':
case '\n':
// Skip any leading whitespace
while (i < escape.length()
while (i < (escape.length() - 1)
&& isWhitespaceChar(ch = escape.charAt(i + 1))
&& !isEol(ch)) {
i++;
Expand Down
56 changes: 56 additions & 0 deletions src/test/java/org/codejive/properties/TestProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,53 @@ void testLoad() throws IOException, URISyntaxException {
new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234"));
}

void testLoadCrLf() throws IOException, URISyntaxException {
Properties p = Properties.loadProperties(getResource("/testcrlf.properties"));
assertThat(p).size().isEqualTo(7);
assertThat(p.keySet())
.containsExactly(
"one", "two", "three", " with spaces", "altsep", "multiline", "key.4");
assertThat(p.rawKeySet())
.containsExactly(
"one", "two", "three", "\\ with\\ spaces", "altsep", "multiline", "key.4");
assertThat(p.values())
.containsExactly(
"simple",
"value containing spaces",
"and escapes\n\t\r\f",
"everywhere ",
"value",
"one two three",
"\u1234\u1234");
assertThat(p.rawValues())
.containsExactly(
"simple",
"value containing spaces",
"and escapes\\n\\t\\r\\f",
"everywhere ",
"value",
"one \\\n two \\\n\tthree",
"\\u1234\u1234");
assertThat(p.entrySet())
.containsExactly(
new AbstractMap.SimpleEntry<>("one", "simple"),
new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
new AbstractMap.SimpleEntry<>("three", "and escapes\n\t\r\f"),
new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "),
new AbstractMap.SimpleEntry<>("altsep", "value"),
new AbstractMap.SimpleEntry<>("multiline", "one two three"),
new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"));
assertThat(p.rawEntrySet())
.containsExactly(
new AbstractMap.SimpleEntry<>("one", "simple"),
new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
new AbstractMap.SimpleEntry<>("three", "and escapes\\n\\t\\r\\f"),
new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "),
new AbstractMap.SimpleEntry<>("altsep", "value"),
new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"),
new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234"));
}

@Test
void testStore() throws IOException, URISyntaxException {
Path f = getResource("/test.properties");
Expand All @@ -70,6 +117,15 @@ void testStore() throws IOException, URISyntaxException {
assertThat(sw.toString()).isEqualTo(readAll(f));
}

@Test
void testStoreCrLf() throws IOException, URISyntaxException {
Path f = getResource("/testcrlf.properties");
Properties p = Properties.loadProperties(f);
StringWriter sw = new StringWriter();
p.store(sw);
assertThat(sw.toString()).isEqualTo(readAll(f));
}

@Test
void testStoreHeader() throws IOException, URISyntaxException {
Path f = getResource("/test.properties");
Expand Down
12 changes: 6 additions & 6 deletions src/test/java/org/codejive/properties/TestPropertiesParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ public class TestPropertiesParser {
+ "\n"
+ "! comment3\n"
+ "one=simple\n"
+ "two=value containing spaces\n\r"
+ "two=value containing spaces\r\n"
+ "# another comment\n"
+ "! and a comment\n"
+ "! block\n"
+ "three=and escapes\\n\\t\\r\\f\n"
+ " \\ with\\ spaces = everywhere \n"
+ "altsep:value\n"
+ "multiline = one \\\n"
+ " two \\\n\r"
+ " two \\\r\n"
+ "\tthree\n"
+ "key.4 = \\u1234\n\r"
+ "key.4 = \\u1234\r\n"
+ " # final comment";

@Test
Expand All @@ -53,7 +53,7 @@ void testTokens() throws IOException {
new Token(Type.KEY, "two"),
new Token(Type.SEPARATOR, "="),
new Token(Type.VALUE, "value containing spaces"),
new Token(Type.WHITESPACE, "\n\r"),
new Token(Type.WHITESPACE, "\r\n"),
new Token(Type.COMMENT, "# another comment"),
new Token(Type.WHITESPACE, "\n"),
new Token(Type.COMMENT, "! and a comment"),
Expand All @@ -75,12 +75,12 @@ void testTokens() throws IOException {
new Token(Type.WHITESPACE, "\n"),
new Token(Type.KEY, "multiline"),
new Token(Type.SEPARATOR, " = "),
new Token(Type.VALUE, "one \\\n two \\\n\r\tthree", "one two three"),
new Token(Type.VALUE, "one \\\n two \\\r\n\tthree", "one two three"),
new Token(Type.WHITESPACE, "\n"),
new Token(Type.KEY, "key.4"),
new Token(Type.SEPARATOR, " = "),
new Token(Type.VALUE, "\\u1234", "\u1234"),
new Token(Type.WHITESPACE, "\n\r"),
new Token(Type.WHITESPACE, "\r\n"),
new Token(Type.WHITESPACE, " "),
new Token(Type.COMMENT, "# final comment"));
}
Expand Down
16 changes: 16 additions & 0 deletions src/test/resources/testcrlf-storeheader.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# A header line

! comment3
one=simple
two=value containing spaces
# another comment
! and a comment
! block
three=and escapes\n\t\r\f
\ with\ spaces = everywhere
altsep:value
multiline = one \
two \
three
key.4 = \u1234ሴ
# final comment
17 changes: 17 additions & 0 deletions src/test/resources/testcrlf.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#comment1
# comment2

! comment3
one=simple
two=value containing spaces
# another comment
! and a comment
! block
three=and escapes\n\t\r\f
\ with\ spaces = everywhere
altsep:value
multiline = one \
two \
three
key.4 = \u1234ሴ
# final comment