/// <summary> /// Convert a string to camelCase, removing any leading or trailing spaces. /// </summary> /// <param name="str">The string to convert.</param> /// <param name="wordBoundaryTest">Optional delegate for determining word boundaries.</param> /// <returns>A newly created (allocated) string.</returns> public static string ToCamelCase(this string str, IsNewWordBoundary wordBoundaryTest = null) { if (str == null) { return(null); } var chars = str.AsSpan().Trim(); if (chars.Length == 0) { // Trim got rid of any white space, so now it would be an empty string. return(string.Empty); } if (wordBoundaryTest == null) { wordBoundaryTest = _IsNewWordBoundary; } var newChars = new char[chars.Length]; // Cannot be longer since we remove characters. bool inWord = false; int src = 0; int dest = 0; // If the first character in the trimmed source string is an underscore, keep it. if (chars[0] == '_') { newChars[dest] = chars[src]; ++dest; ++src; } for (; src < chars.Length; ++src) { var isLetterOrDigit = char.IsLetterOrDigit(chars[src]); // Only call it once, we reference multiple times in the worst case scenario if (inWord && isLetterOrDigit && wordBoundaryTest(chars, src)) { // Found a Upper-lower character sequence in the source. Flag as no longer in the word. inWord = false; } if (!inWord && !isLetterOrDigit) { continue; } else if (inWord && isLetterOrDigit) { // Copy it. newChars[dest] = char.ToLower(chars[src]); ++dest; } else if (!inWord && isLetterOrDigit) { if (dest > 2) { // We are past first character or leading underscore, new word so capitalize it. newChars[dest] = char.ToUpper(chars[src]); } else if (dest == 0) { // First letter is lowercase for camel case. newChars[dest] = char.ToLower(chars[src]); } else if (newChars[0] == '_' || newChars[0] == '-') { // Leading underscore or dash, first letter in dest (dest=1 is only option here), lower-case. newChars[dest] = char.ToLower(chars[src]); } else { // dest=1, Capitalize this one, we just entered a new word. newChars[dest] = char.ToUpper(chars[src]); } ++dest; inWord = true; } else { // Not in a word, and not a letter or digit. inWord = false; } } return(new string(newChars, 0, dest)); }
/// <summary> /// Convert a string to html-id-case, removing any leading or trailing spaces. /// </summary> /// <param name="str">The string to convert.</param> /// <param name="wordBoundaryTest">Optional delegate for determining word boundaries.</param> /// <returns>A newly created (allocated) string.</returns> public static string ToKebabCase(this string str, IsNewWordBoundary wordBoundaryTest = null) => ToLowercaseSeparated(str, wordBoundaryTest, '-');
/// <summary> /// Convert a string to lower-case and separated with the given character, removing any leading and trailing spaces. /// Used by snake_case and html-id-case. /// </summary> /// <param name="str">The string to convert.</param> /// <param name="wordBoundaryTest">Optional delegate for determining word boundaries.</param> /// <param name="separator">Optional separator. Underscore for snake case, dash for html ids. /// <returns>A newly created (allocated) string.</returns> public static string ToLowercaseSeparated(this string str, IsNewWordBoundary wordBoundryTest = null, char separator = '_') { if (str == null) { return(null); } var chars = str.AsSpan().Trim(); if (chars.Length == 0) { // Trim got rid of any white space, so now it would be an empty string. return(string.Empty); } if (wordBoundryTest == null) { wordBoundryTest = _IsNewWordBoundary; } var newChars = new char[chars.Length * 2 + 1]; // Could be every other letter gets an underscore. bool inWord = false; int src = 0; int dest = 0; // If the first character in the trimmed source string is the separator, dash, or underscore, make it the separator. if (chars[0] == separator || chars[0] == '_' || chars[0] == '-') { newChars[dest] = separator; ++dest; ++src; } for (; src < chars.Length; ++src) { var isLetterOrDigit = char.IsLetterOrDigit(chars[src]); // Only call it once, we reference multiple times in the worst case scenario if (inWord && isLetterOrDigit && wordBoundryTest(chars, src)) { // Found a Upper-lower character sequence in the source. Flag as no longer in the word. inWord = false; } if (!inWord && !isLetterOrDigit) { continue; } else if (inWord && isLetterOrDigit) { // Copy it. newChars[dest] = char.ToLower(chars[src]); ++dest; } else if (!inWord && isLetterOrDigit) { if (dest > 1) { // Greater than one because otherwise we would double a leading underscore. newChars[dest] = separator; ++dest; } newChars[dest] = char.ToLower(chars[src]); ++dest; inWord = true; } else { // Not in a word, and not a letter or digit. inWord = false; } } return(new string(newChars, 0, dest)); }
/// <summary> /// Convert a string to snake_case, removing any leading or trailing spaces. /// </summary> /// <param name="str">The string to convert.</param> /// <param name="wordBoundaryTest">Optional delegate for determining word boundaries. </param> /// <returns>A newly created (allocated) string.</returns> public static string ToSnakeCase(this string str, IsNewWordBoundary wordBoundaryTest = null, char separator = '-') => ToLowercaseSeparated(str, wordBoundaryTest, '_');
/// <summary> /// Change the capitalization of a string. /// </summary> /// <param name="str">The string to change.</param> /// <param name="toCase">The capitalization style. <see cref="StringCapitalizationEnum"> for options.</param> /// <param name="wordBoundaryTest">Optional method to determine word boundaries.</param> /// <returns>The new string.</returns> public static string ChangeCapitalization(this string str, StringCapitalization toCase, IsNewWordBoundary wordBoundaryTest = null) { switch (toCase) { case StringCapitalization.KeepCase: return(str); case StringCapitalization.UpperCase: return(str.ToUpper()); case StringCapitalization.LowerCase: return(str.ToLower()); case StringCapitalization.PascalCase: return(str.ToPascalCase(wordBoundaryTest: wordBoundaryTest)); case StringCapitalization.CamelCase: return(str.ToCamelCase(wordBoundaryTest: wordBoundaryTest)); case StringCapitalization.SnakeCase: return(str.ToSnakeCase(wordBoundaryTest: wordBoundaryTest)); case StringCapitalization.HtmlIdCase: return(str.ToKebabCase(wordBoundaryTest: wordBoundaryTest)); default: //Unreachable with the enum unless someone changes it without changing this switch statement. throw new ArgumentException("INTERNAL ERROR: StringCapitalization has ENUM value not handled in the switch!"); } }