Add unescapeCsv to StringUtil
Motivation: See #3435 Modifications: Add unescapeCsv to StringUtil Result: StringUtil has the counter part of escapeCsv: unescapeCsv
This commit is contained in:
parent
a15ff32608
commit
f43dc7d551
@ -387,6 +387,67 @@ public final class StringUtil {
|
|||||||
escaped.append(DOUBLE_QUOTE) : value;
|
escaped.append(DOUBLE_QUOTE) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unescapes the specified escaped CSV field, if necessary according to
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc4180#section-2">RFC-4180</a>.
|
||||||
|
*
|
||||||
|
* @param value The escaped CSV field which will be unescaped according to
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc4180#section-2">RFC-4180</a>
|
||||||
|
* @return {@link CharSequence} the unescaped value if necessary, or the value unchanged
|
||||||
|
*/
|
||||||
|
public static CharSequence unescapeCsv(CharSequence value) {
|
||||||
|
int length = checkNotNull(value, "value").length();
|
||||||
|
if (length == 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
int last = length - 1;
|
||||||
|
boolean quoted = isDoubleQuote(value.charAt(0)) && isDoubleQuote(value.charAt(last)) && length != 1;
|
||||||
|
if (!quoted) {
|
||||||
|
validateCsvFormat(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
StringBuilder unescaped = InternalThreadLocalMap.get().stringBuilder();
|
||||||
|
for (int i = 1; i < last; i++) {
|
||||||
|
char current = value.charAt(i);
|
||||||
|
if (current == DOUBLE_QUOTE) {
|
||||||
|
if (isDoubleQuote(value.charAt(i + 1)) && (i + 1) != last) {
|
||||||
|
// Followed by a double-quote but not the last character
|
||||||
|
// Just skip the next double-quote
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
// Not followed by a double-quote or the following double-quote is the last character
|
||||||
|
throw newInvalidEscapedCsvFieldException(value, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unescaped.append(current);
|
||||||
|
}
|
||||||
|
return unescaped.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if {@code value} is a valid csv field without double-quotes.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if {@code value} needs to be encoded with double-quotes.
|
||||||
|
*/
|
||||||
|
private static void validateCsvFormat(CharSequence value) {
|
||||||
|
int length = value.length();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
switch (value.charAt(i)) {
|
||||||
|
case DOUBLE_QUOTE:
|
||||||
|
case LINE_FEED:
|
||||||
|
case CARRIAGE_RETURN:
|
||||||
|
case COMMA:
|
||||||
|
// If value contains any special character, it should be enclosed with double-quotes
|
||||||
|
throw newInvalidEscapedCsvFieldException(value, i);
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IllegalArgumentException newInvalidEscapedCsvFieldException(CharSequence value, int index) {
|
||||||
|
return new IllegalArgumentException("invalid escaped CSV field: " + value + " index: " + index);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the length of a string, {@code null} input is considered {@code 0} length.
|
* Get the length of a string, {@code null} input is considered {@code 0} length.
|
||||||
*/
|
*/
|
||||||
|
@ -321,6 +321,61 @@ public class StringUtilTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnescapeCsv() {
|
||||||
|
assertEquals("", unescapeCsv(""));
|
||||||
|
assertEquals("\"", unescapeCsv("\"\"\"\""));
|
||||||
|
assertEquals("\"\"", unescapeCsv("\"\"\"\"\"\""));
|
||||||
|
assertEquals("\"\"\"", unescapeCsv("\"\"\"\"\"\"\"\""));
|
||||||
|
assertEquals("\"netty\"", unescapeCsv("\"\"\"netty\"\"\""));
|
||||||
|
assertEquals("netty", unescapeCsv("netty"));
|
||||||
|
assertEquals("netty", unescapeCsv("\"netty\""));
|
||||||
|
assertEquals("\r", unescapeCsv("\"\r\""));
|
||||||
|
assertEquals("\n", unescapeCsv("\"\n\""));
|
||||||
|
assertEquals("hello,netty", unescapeCsv("\"hello,netty\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void unescapeCsvWithSingleQuote() {
|
||||||
|
unescapeCsv("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void unescapeCsvWithOddQuote() {
|
||||||
|
unescapeCsv("\"\"\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void unescapeCsvWithCRAndWithoutQuote() {
|
||||||
|
unescapeCsv("\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void unescapeCsvWithLFAndWithoutQuote() {
|
||||||
|
unescapeCsv("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void unescapeCsvWithCommaAndWithoutQuote() {
|
||||||
|
unescapeCsv(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void escapeCsvAndUnEscapeCsv() {
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("netty");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("hello,netty");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("hello,\"netty\"");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("\"");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv(",");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("\r");
|
||||||
|
assertEscapeCsvAndUnEscapeCsv("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEscapeCsvAndUnEscapeCsv(String value) {
|
||||||
|
assertEquals(value, unescapeCsv(StringUtil.escapeCsv(value)));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSimpleClassName() throws Exception {
|
public void testSimpleClassName() throws Exception {
|
||||||
testSimpleClassName(String.class);
|
testSimpleClassName(String.class);
|
||||||
|
Loading…
Reference in New Issue
Block a user