diff --git a/readme.md b/readme.md
index 6359380af..68c8954ff 100644
--- a/readme.md
+++ b/readme.md
@@ -633,10 +633,11 @@ You can combine `wordForm` argument with gender but passing this argument in whe
#### Pascalize
-`Pascalize` converts the input words to UpperCamelCase, also removing underscores and spaces:
+`Pascalize` converts the input words to UpperCamelCase, also removing underscores and spaces, with optional capability to preserve acronym casing:
```C#
-"some_title for something".Pascalize() => "SomeTitleForSomething"
+"some_title for some SKU".Pascalize() => "SomeTitleForSomeSKU"
+"NBA-Basketball MLS-baseball".Pascalize(false) => "NbaBasketballMlsBaseball"
```
@@ -648,6 +649,14 @@ You can combine `wordForm` argument with gender but passing this argument in whe
"some_title for something".Camelize() => "someTitleForSomething"
```
+#### CamelizeV2
+
+`Camelize` camelizes acronyms correctly, it is backwards incompatible with the way current Camelize works which is why introducing a new version
+
+```C#
+"HTTP IO module".Camelize() => "httpIoModule"
+```
+
#### Underscore
diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt
index 9a3753c46..85d9cb672 100644
--- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt
+++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt
@@ -765,10 +765,11 @@ namespace Humanizer
public static class InflectorExtensions
{
public static string Camelize(this string input) { }
+ public static string CamelizeV2(this string input) { }
public static string Dasherize(this string underscoredWord) { }
public static string Hyphenate(this string underscoredWord) { }
public static string Kebaberize(this string input) { }
- public static string Pascalize(this string input) { }
+ public static string Pascalize(this string input, bool preserveAcronym = true) { }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("word")]
public static string? Pluralize(this string? word, bool inputIsKnownToBeSingular = true) { }
public static string Singularize(this string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) { }
diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
index bb3920e7f..9706a56de 100644
--- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
+++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
@@ -765,10 +765,11 @@ namespace Humanizer
public static class InflectorExtensions
{
public static string Camelize(this string input) { }
+ public static string CamelizeV2(this string input) { }
public static string Dasherize(this string underscoredWord) { }
public static string Hyphenate(this string underscoredWord) { }
public static string Kebaberize(this string input) { }
- public static string Pascalize(this string input) { }
+ public static string Pascalize(this string input, bool preserveAcronym = true) { }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("word")]
public static string? Pluralize(this string? word, bool inputIsKnownToBeSingular = true) { }
public static string Singularize(this string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) { }
diff --git a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt
index 44794d215..62815357f 100644
--- a/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt
+++ b/src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt
@@ -546,10 +546,11 @@ namespace Humanizer
public static class InflectorExtensions
{
public static string Camelize(this string input) { }
+ public static string CamelizeV2(this string input) { }
public static string Dasherize(this string underscoredWord) { }
public static string Hyphenate(this string underscoredWord) { }
public static string Kebaberize(this string input) { }
- public static string Pascalize(this string input) { }
+ public static string Pascalize(this string input, bool preserveAcronym = true) { }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("word")]
public static string? Pluralize(this string? word, bool inputIsKnownToBeSingular = true) { }
public static string Singularize(this string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) { }
diff --git a/src/Humanizer.Tests/InflectorTests.cs b/src/Humanizer.Tests/InflectorTests.cs
index 3518355ec..37de1e9f8 100644
--- a/src/Humanizer.Tests/InflectorTests.cs
+++ b/src/Humanizer.Tests/InflectorTests.cs
@@ -110,9 +110,29 @@ public void Hyphenate(string input, string expectedOutput) =>
[InlineData("customer-first-name", "CustomerFirstName")]
[InlineData("_customer-first-name", "CustomerFirstName")]
[InlineData(" customer__first--name", "CustomerFirstName")]
- public void Pascalize(string input, string expectedOutput) =>
+ [InlineData("HTTP-method IO module RESTful", "HTTPMethodIOModuleRESTful")]
+ [InlineData("HTTP SSL IO module RESTful", "HTTPSSLIOModuleRESTful")]
+ public void PascalizeWithAcronymPreserved(string input, string expectedOutput) =>
Assert.Equal(expectedOutput, input.Pascalize());
+
+ [Theory]
+ [InlineData("customer", "Customer")]
+ [InlineData("CUSTOMER", "Customer")]
+ [InlineData("CUStomer", "Customer")]
+ [InlineData("customer_name", "CustomerName")]
+ [InlineData("customer_first_name", "CustomerFirstName")]
+ [InlineData("customer_first_name goes here", "CustomerFirstNameGoesHere")]
+ [InlineData("customer name", "CustomerName")]
+ [InlineData("customer name", "CustomerName")]
+ [InlineData("customer-first-name", "CustomerFirstName")]
+ [InlineData("_customer-first-name", "CustomerFirstName")]
+ [InlineData(" customer__first--name", "CustomerFirstName")]
+ [InlineData("HTTP-method IO module RESTful ", "HttpMethodIoModuleRestful")]
+ [InlineData("HTTP SSL IO module RESTful ", "HttpSslIoModuleRestful")]
+ public void PascalizeWithoutAcronymPreserved(string input, string expectedOutput) =>
+ Assert.Equal(expectedOutput, input.Pascalize(false));
+
// Same as pascalize, except first char is lowercase
[Theory]
[InlineData("customer", "customer")]
@@ -127,6 +147,22 @@ public void Pascalize(string input, string expectedOutput) =>
public void Camelize(string input, string expectedOutput) =>
Assert.Equal(expectedOutput, input.Camelize());
+
+ // Handles acronyms uniformly, preserving the case of the first letter
+ [Theory]
+ [InlineData("customer", "customer")]
+ [InlineData("CUSTOMER", "customer")]
+ [InlineData("CUStomer", "customer")]
+ [InlineData("customer_name", "customerName")]
+ [InlineData("customer_first_name", "customerFirstName")]
+ [InlineData("customer_first_name goes here", "customerFirstNameGoesHere")]
+ [InlineData("customer name", "customerName")]
+ [InlineData("customer name", "customerName")]
+ [InlineData("HTTP-method IO module RESTful ", "httpMethodIoModuleRestful")]
+ [InlineData("", "")]
+ public void CamelizeV2(string input, string expectedOutput) =>
+ Assert.Equal(expectedOutput, input.CamelizeV2());
+
//Makes an underscored lowercase string
[Theory]
[InlineData("SomeTitle", "some_title")]
diff --git a/src/Humanizer/InflectorExtensions.cs b/src/Humanizer/InflectorExtensions.cs
index b129372e8..60c4b29f7 100644
--- a/src/Humanizer/InflectorExtensions.cs
+++ b/src/Humanizer/InflectorExtensions.cs
@@ -52,12 +52,64 @@ public static string Titleize(this string input) =>
///
/// By default, pascalize converts strings to UpperCamelCase also removing underscores
+ /// For a word that are all upper case, this function by default assumes its an acronym and preserves the casing
+ /// of all letters in that word
+ /// In order to treat it as a word and not acronym, set preserveAcronym to false
///
- public static string Pascalize(this string input) =>
+ public static string Pascalize(this string input, bool preserveAcronym = true)
+ {
+ if (preserveAcronym)
+ return input.PascalizeWithAcronymPreservation();
+ else
+ return input.PascalizeWithoutAcronymPreservation();
+ }
+
+ private static string PascalizeWithAcronymPreservation(this string input) =>
Regex.Replace(input, @"(?:[ _-]+|^)([a-zA-Z])", match => match
.Groups[1]
.Value.ToUpper());
+
+ private static string PascalizeWithoutAcronymPreservation(this string input)
+ {
+ if (string.IsNullOrEmpty(input))
+ return string.Empty;
+
+ var span = input.AsSpan();
+ var sb = new StringBuilder(input.Length);
+
+ var wordStart = 0;
+ for (var index = 0; index <= span.Length; index++)
+ {
+ var isSeparator = index == span.Length || span[index] == ' ' || span[index] == '_' || span[index] == '-';
+
+ if (!isSeparator)
+ continue;
+
+ var wordSpan = span.Slice(wordStart, index - wordStart);
+
+ if (wordSpan.Length > 0)
+ {
+ // Otherwise, capitalize the first letter and append the rest
+ sb.Append(char.ToUpper(wordSpan[0]));
+ for (var k = 1; k < wordSpan.Length; k++)
+ {
+ sb.Append(char.ToLower(wordSpan[k]));
+ }
+ }
+
+ while (index < span.Length && (span[index] == ' ' || span[index] == '_' || span[index] == '-'))
+ {
+ index++;
+ }
+
+ wordStart = index;
+ }
+
+ return sb.ToString();
+ }
+
+
///
/// Same as Pascalize except that the first character is lower case
///
@@ -71,6 +123,21 @@ public static string Camelize(this string input)
: word;
}
+ ///
+ /// Adheres to Google camel case definition https://google.github.io/styleguide/jsguide.html#naming-camel-case-defined
+ /// that appropriately handles acronyms.
+ ///
+ public static string CamelizeV2(this string input)
+ {
+ var word = input.Pascalize(false);
+ return word.Length > 0
+ ? StringHumanizeExtensions.Concat(
+ char.ToLower(word[0]),
+ word.AsSpan(1))
+ : word;
+
+ }
+
///
/// Separates the input words with underscore
///