Skip to content

TEXT-217 - Add CaseUtils.toSnakeCase and CaseUtils.toKebabCase #360

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
91 changes: 90 additions & 1 deletion src/main/java/org/apache/commons/text/CaseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,100 @@ public static String toCamelCase(String str, final boolean capitalizeFirstLetter
return new String(newCodePoints, 0, outOffset);
}

private static String toDelimiterCase(String str, final char newDelimiter, final char... delimiters) {
if (StringUtils.isEmpty(str)) {
return str;
}
str = str.toLowerCase();
final int strLen = str.length();
final int[] newCodePoints = new int[strLen];
int outOffset = 0;
final Set<Integer> delimiterSet = toDelimiterSet(delimiters);
boolean toAddDelimiter = false;
for (int index = 0; index < strLen;) {
final int codePoint = str.codePointAt(index);

if (delimiterSet.contains(codePoint)) {
toAddDelimiter = outOffset != 0;
index += Character.charCount(codePoint);
} else {
if (toAddDelimiter) {
newCodePoints[outOffset++] = newDelimiter;
toAddDelimiter = false;
}
newCodePoints[outOffset++] = codePoint;
index += Character.charCount(codePoint);
}
}

return new String(newCodePoints, 0, outOffset);
}

/**
* Converts all the delimiter separated words in a String into snake_case,
* that is each word is separated by an underscore (_) character and the String
* is converted to lower case.
*
* <p>The delimiters represent a set of characters understood to separate words.</p>
*
* <p>A {@code null} input String returns {@code null}.</p>
*
* <p>A input string with only delimiter characters returns {@code ""}.</p>
*
* <pre>
* CaseUtils.toSnakeCase(null) = null
* CaseUtils.toSnakeCase("", *) = ""
* CaseUtils.toSnakeCase(*, null) = *
* CaseUtils.toSnakeCase(*, new char[0]) = *
* CaseUtils.toSnakeCase("To.Snake.Case", new char[]{'.'}) = "to_snake_case"
* CaseUtils.toSnakeCase(" to @ Snake case", new char[]{'@'}) = "to_snake_case"
* CaseUtils.toSnakeCase(" @to @ Snake case", new char[]{'@'}) = "to_snake_case"
* CaseUtils.toSnakeCase(" @", new char[]{'@'}) = ""
* </pre>
*
* @param str the String to be converted to snake_case, may be null
* @param delimiters set of characters to determine a new word, null and/or empty array means whitespace
* @return snake_case of String, {@code null} if null String input
*/
public static String toSnakeCase(String str, final char... delimiters) {
return toDelimiterCase(str, '_', delimiters);
}

/**
* Converts all the delimiter separated words in a String into kebab-case,
* that is each word is separated by aa dash (-) character and the String
* is converted to lower case.
*
* <p>The delimiters represent a set of characters understood to separate words.</p>
*
* <p>A {@code null} input String returns {@code null}.</p>
*
* <p>A input string with only delimiter characters returns {@code ""}.</p>
*
* <pre>
* CaseUtils.toKebabCase(null) = null
* CaseUtils.toKebabCase("", *) = ""
* CaseUtils.toKebabCase(*, null) = *
* CaseUtils.toKebabCase(*, new char[0]) = *
* CaseUtils.toKebabCase("To.Kebab.Case", new char[]{'.'}) = "to-kebab-case"
* CaseUtils.toKebabCase(" to @ Kebab case", new char[]{'@'}) = "to-kebab-case"
* CaseUtils.toKebabCase(" @to @ Kebab case", new char[]{'@'}) = "to-kebab-case"
* CaseUtils.toKebabCase(" @", new char[]{'@'}) = ""
* </pre>
*
* @param str the String to be converted to kebab-case, may be null
* @param delimiters set of characters to determine a new word, null and/or empty array means whitespace
* @return kebab-case of String, {@code null} if null String input
*/
public static String toKebabCase(String str, final char... delimiters) {
return toDelimiterCase(str, '-', delimiters);
}

/**
* Converts an array of delimiters to a hash set of code points. Code point of space(32) is added
* as the default value. The generated hash set provides O(1) lookup time.
*
* @param delimiters set of characters to determine capitalization, null means whitespace
* @param delimiters set of characters to determine words, null means whitespace
* @return Set<Integer>
*/
private static Set<Integer> toDelimiterSet(final char[] delimiters) {
Expand Down
58 changes: 58 additions & 0 deletions src/test/java/org/apache/commons/text/CaseUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,62 @@ public void testToCamelCase() {
assertThat(CaseUtils.toCamelCase("\uD800\uDF00\uD800\uDF01\uD800\uDF14\uD800\uDF02\uD800\uDF03", true, '\uD800',
'\uDF14')).isEqualTo("\uD800\uDF00\uD800\uDF01\uD800\uDF02\uD800\uDF03");
}

@Test
public void testToSnakeCase() {
assertThat(CaseUtils.toSnakeCase(null, null)).isNull();
assertThat(CaseUtils.toSnakeCase("", null)).isEqualTo("");
assertThat(CaseUtils.toSnakeCase(" ", null)).isEqualTo("");
assertThat(CaseUtils.toSnakeCase("a b c @def", null)).isEqualTo("a_b_c_@def");
assertThat(CaseUtils.toSnakeCase("a b c @def")).isEqualTo("a_b_c_@def");
assertThat(CaseUtils.toSnakeCase("a b c @def", '_')).isEqualTo("a_b_c_@def");
assertThat(CaseUtils.toSnakeCase("a_b_c_@def", '_')).isEqualTo("a_b_c_@def");
assertThat(CaseUtils.toSnakeCase("_a___b__c_@def", '_')).isEqualTo("a_b_c_@def");

final char[] chars = {'-', '+', ' ', '@'};
assertThat(CaseUtils.toSnakeCase("-+@ ", chars)).isEqualTo("");
assertThat(CaseUtils.toSnakeCase(" to-SNAKE-cASE", chars)).isEqualTo("to_snake_case");
assertThat(CaseUtils.toSnakeCase("@@@@ to+SNAKE@cASE ", chars)).isEqualTo("to_snake_case");
assertThat(CaseUtils.toSnakeCase("To+SN+AK E@cASE", chars)).isEqualTo("to_sn_ak_e_case");

assertThat(CaseUtils.toSnakeCase("To.Snake.Case", '.')).isEqualTo("to_snake_case");
assertThat(CaseUtils.toSnakeCase("To.Snake-Case", '-', '.')).isEqualTo("to_snake_case");
assertThat(CaseUtils.toSnakeCase(" to @ Snake case", '-', '@')).isEqualTo("to_snake_case");
assertThat(CaseUtils.toSnakeCase(" @to @ Snake case", '-', '@')).isEqualTo("to_snake_case");

assertThat(CaseUtils.toSnakeCase("tosnakecase")).isEqualTo("tosnakecase");

assertThat(CaseUtils.toSnakeCase("\uD800\uDF00 \uD800\uDF02")).isEqualTo("\uD800\uDF00_\uD800\uDF02");
assertThat(CaseUtils.toSnakeCase("\uD800\uDF00\uD800\uDF01\uD800\uDF14\uD800\uDF02\uD800\uDF03", '\uD800',
'\uDF14')).isEqualTo("\uD800\uDF00\uD800\uDF01_\uD800\uDF02\uD800\uDF03");
}

@Test
public void testToKebabCase() {
assertThat(CaseUtils.toKebabCase(null, null)).isNull();
assertThat(CaseUtils.toKebabCase("", null)).isEqualTo("");
assertThat(CaseUtils.toKebabCase(" ", null)).isEqualTo("");
assertThat(CaseUtils.toKebabCase("a b c @def", null)).isEqualTo("a-b-c-@def");
assertThat(CaseUtils.toKebabCase("a b c @def")).isEqualTo("a-b-c-@def");
assertThat(CaseUtils.toKebabCase("a b c @def", '-')).isEqualTo("a-b-c-@def");
assertThat(CaseUtils.toKebabCase("a-b-c-@def", '-')).isEqualTo("a-b-c-@def");
assertThat(CaseUtils.toKebabCase("-a---b--c-@def", '-')).isEqualTo("a-b-c-@def");

final char[] chars = {'-', '+', ' ', '@'};
assertThat(CaseUtils.toKebabCase("-+@ ", chars)).isEqualTo("");
assertThat(CaseUtils.toKebabCase(" to-KEBAB-cASE", chars)).isEqualTo("to-kebab-case");
assertThat(CaseUtils.toKebabCase("@@@@ to+KEBAB@cASE ", chars)).isEqualTo("to-kebab-case");
assertThat(CaseUtils.toKebabCase("To+KE+BA B@cASE", chars)).isEqualTo("to-ke-ba-b-case");

assertThat(CaseUtils.toKebabCase("To.Kebab.Case", '.')).isEqualTo("to-kebab-case");
assertThat(CaseUtils.toKebabCase("To.Kebab-Case", '-', '.')).isEqualTo("to-kebab-case");
assertThat(CaseUtils.toKebabCase(" to @ Kebab case", '-', '@')).isEqualTo("to-kebab-case");
assertThat(CaseUtils.toKebabCase(" @to @ Kebab case", '-', '@')).isEqualTo("to-kebab-case");

assertThat(CaseUtils.toKebabCase("tokebabcase")).isEqualTo("tokebabcase");

assertThat(CaseUtils.toKebabCase("\uD800\uDF00 \uD800\uDF02")).isEqualTo("\uD800\uDF00-\uD800\uDF02");
assertThat(CaseUtils.toKebabCase("\uD800\uDF00\uD800\uDF01\uD800\uDF14\uD800\uDF02\uD800\uDF03", '\uD800',
'\uDF14')).isEqualTo("\uD800\uDF00\uD800\uDF01-\uD800\uDF02\uD800\uDF03");
}
}