Skip to content
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

Substitution of ToPascalCase logic with a more general one #413

Merged
merged 2 commits into from
Sep 5, 2023
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
20 changes: 10 additions & 10 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,8 @@ public void TestXbrl()

var testFiles = new Dictionary<string, string>
{
{ "Schaltbau.xhtml", "XhtmlPeriodHtmlPeriodType" },
{ "GLEIF Annual Accounts.html", "XhtmlPeriodHtmlPeriodType" },
{ "Schaltbau.xhtml", "XhtmlHtmlType" },
{ "GLEIF Annual Accounts.html", "XhtmlHtmlType" },
};

foreach (var testFile in testFiles)
Expand Down Expand Up @@ -767,7 +767,7 @@ public void TestCustomNamespaces()
{
{ bpmnXsd, "TDefinitions" },
{ semantXsd, "TActivity" },
{ bpmndiXsd, "BPMNDiagram" },
{ bpmndiXsd, "BpmnDiagram" },
{ dcXsd, "Font" },
{ diXsd, "DiagramElement" }
};
Expand Down Expand Up @@ -1139,7 +1139,7 @@ namespace Test
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(""group-name"", Namespace="""")]
[System.ComponentModel.DesignerCategoryAttribute(""code"")]
public partial class Group_Name
public partial class GroupName
{

/// <summary>
Expand Down Expand Up @@ -1743,11 +1743,11 @@ public void EnumWithNonUniqueEntriesTest()
var durationEnumType = assembly.GetType("Test.TestEnum");
Assert.NotNull(durationEnumType);

var expectedEnumValues = new[] {"Test_Case", "Test_Case1", "Test_Case2", "Test_Case3"};
var expectedEnumValues = new[] {"TestCase", "TestCase1", "TestCase2", "TestCase3"};
var enumValues = durationEnumType.GetEnumNames().OrderBy(n => n).ToList();
Assert.Equal(expectedEnumValues, enumValues);

var mEnumValue = durationEnumType.GetMembers().First(mi => mi.Name == "Test_Case1");
var mEnumValue = durationEnumType.GetMembers().First(mi => mi.Name == "TestCase1");
var xmlEnumAttribute = mEnumValue.GetCustomAttributes<XmlEnumAttribute>().FirstOrDefault();
Assert.NotNull(xmlEnumAttribute);
Assert.Equal("test_Case", xmlEnumAttribute.Name);
Expand Down Expand Up @@ -2398,13 +2398,13 @@ public void TestArrayOfMsTypeGeneration()
var generator = new Generator
{
IntegerDataType = typeof(int),
NamespacePrefix = "Test_NS1",
NamespacePrefix = "TestNS1",
GenerateNullables = true,
CollectionType = typeof(System.Collections.Generic.List<>)
};
var contents = ConvertXml(nameof(TestArrayOfMsTypeGeneration), new[] { xsd0, xsd1 }, generator).ToArray();
var assembly = Compiler.Compile(nameof(TestForceIsNullableGeneration), contents);
var testType = assembly.GetType("Test_NS1.C_Ai");
var testType = assembly.GetType("TestNS1.CAi");
var serializer = new XmlSerializer(testType);
Assert.NotNull(serializer);
dynamic deserialized = serializer.Deserialize(new StringReader(validXml));
Expand Down Expand Up @@ -2773,10 +2773,10 @@ public void TestArrayItemAttribute()
};
var contents = ConvertXml(nameof(TestArrayItemAttribute), new[] { xsd }, generator).ToArray();
var assembly = Compiler.Compile(nameof(TestArrayItemAttribute), contents);
var applicationType = assembly.GetType("Test_Generation_Namespace.T_Application");
var applicationType = assembly.GetType("TestGenerationNamespace.TApplication");
Assert.NotNull(applicationType);
var optionList = applicationType.GetProperty("OptionList");
Assert.Equal("Test_Generation_Namespace.T_OptionList", optionList.PropertyType.FullName);
Assert.Equal("TestGenerationNamespace.TOptionList", optionList.PropertyType.FullName);
}

[Fact]
Expand Down
121 changes: 80 additions & 41 deletions XmlSchemaClassGenerator/CodeUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,55 @@ namespace XmlSchemaClassGenerator
{
public static class CodeUtilities
{
// Match non-letter followed by letter
private static readonly Regex PascalCaseRegex = new(@"[^\p{L}]\p{L}", RegexOptions.Compiled);

// Uppercases first letter and all letters following non-letters.
// Examples: testcase -> Testcase, html5element -> Html5Element, test_case -> Test_Case
public static string ToPascalCase(this string s) => string.IsNullOrEmpty(s) ? s
: char.ToUpperInvariant(s[0]) + PascalCaseRegex.Replace(s.Substring(1), m => m.Value[0] + char.ToUpperInvariant(m.Value[1]).ToString());
private static readonly Regex invalidCharsRgx = new Regex("[^_a-zA-Z0-9]");
private static readonly Regex whiteSpace = new Regex(@"(?<=\s)");
private static readonly Regex startsWithLowerCaseChar = new Regex("^[a-z]");
private static readonly Regex firstCharFollowedByUpperCasesOnly = new Regex("(?<=[A-Z])[A-Z0-9]+$");
private static readonly Regex lowerCaseNextToNumber = new Regex("(?<=[0-9])[a-z]");
private static readonly Regex upperCaseInside = new Regex("(?<=[A-Z])[A-Z]+?((?=[A-Z][a-z])|(?=[0-9]))");

// Credits: chviLadislav
// https://stackoverflow.com/questions/18627112/how-can-i-convert-text-to-pascal-case
// Example output:
// "WARD_VS_VITAL_SIGNS" "WardVsVitalSigns"
// "Who am I?" "WhoAmI"
// "I ate before you got here" "IAteBeforeYouGotHere"
// "Hello|Who|Am|I?" "HelloWhoAmI"
// "Live long and prosper" "LiveLongAndProsper"
// "Lorem ipsum dolor..." "LoremIpsumDolor"
// "CoolSP" "CoolSp"
// "AB9CD" "Ab9Cd"
// "CCCTrigger" "CccTrigger"
// "CIRC" "Circ"
// "ID_SOME" "IdSome"
// "ID_SomeOther" "IdSomeOther"
// "ID_SOMEOther" "IdSomeOther"
// "CCC_SOME_2Phases" "CccSome2Phases"
// "AlreadyGoodPascalCase" "AlreadyGoodPascalCase"
// "999 999 99 9 " "999999999"
// "1 2 3 " "123"
// "1 AB cd EFDDD 8" "1AbCdEfddd8"
// "INVALID VALUE AND _2THINGS" "InvalidValueAnd2Things"
public static string ToPascalCase(this string original)
{
// replace white spaces with undescore, then replace all invalid chars with undescore
var pascalCase = invalidCharsRgx.Replace(whiteSpace.Replace(original, "_"), "_")
// split by underscores
.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries)
// set first letter to uppercase
.Select(w => startsWithLowerCaseChar.Replace(w, m => m.Value.ToUpper()))
// replace second and all following upper case letters to lower if there is no next lower (ABC -> Abc)
.Select(w => firstCharFollowedByUpperCasesOnly.Replace(w, m => m.Value.ToLower()))
// set upper case the first lower case following a number (Ab9cd -> Ab9Cd)
.Select(w => lowerCaseNextToNumber.Replace(w, m => m.Value.ToUpper()))
// lower second and next upper case letters except the last if it follows by any lower (ABcDEf -> AbcDef)
.Select(w => upperCaseInside.Replace(w, m => m.Value.ToLower()));

return string.Concat(pascalCase);
}

public static string ToCamelCase(this string s) => string.IsNullOrEmpty(s) ? s
: char.ToLowerInvariant(s[0]) + s.Substring(1);
: char.ToLowerInvariant(s[0]) + s.Substring(1);

public static string ToBackingField(this string propertyName, string privateFieldPrefix)
=> string.Concat(privateFieldPrefix, propertyName.ToCamelCase());
Expand Down Expand Up @@ -76,37 +115,37 @@ private static Type GetIntegerDerivedType(XmlSchemaDatatype xml, GeneratorConfig
Type FromDigitRestriction(TotalDigitsRestrictionModel totalDigits
) => xml.TypeCode switch
{
XmlTypeCode.PositiveInteger or XmlTypeCode.NonNegativeInteger => totalDigits?.Value switch
{
< 3 => typeof(byte),
< 5 => typeof(ushort),
< 10 => typeof(uint),
< 20 => typeof(ulong),
< 30 => typeof(decimal),
_ => null
},
XmlTypeCode.Integer or XmlTypeCode.NegativeInteger or XmlTypeCode.NonPositiveInteger => totalDigits
?.Value switch
{
< 3 => typeof(sbyte),
< 5 => typeof(short),
< 10 => typeof(int),
< 19 => typeof(long),
< 29 => typeof(decimal),
_ => null
},
XmlTypeCode.Decimal
when restrictions.OfType<FractionDigitsRestrictionModel>().SingleOrDefault() is { IsSupported: true, Value: 0 } => totalDigits
?.Value switch
{
< 3 => typeof(sbyte),
< 5 => typeof(short),
< 10 => typeof(int),
< 19 => typeof(long),
< 29 => typeof(decimal),
_ => null
},
_ => null
XmlTypeCode.PositiveInteger or XmlTypeCode.NonNegativeInteger => totalDigits?.Value switch
{
< 3 => typeof(byte),
< 5 => typeof(ushort),
< 10 => typeof(uint),
< 20 => typeof(ulong),
< 30 => typeof(decimal),
_ => null
},
XmlTypeCode.Integer or XmlTypeCode.NegativeInteger or XmlTypeCode.NonPositiveInteger => totalDigits
?.Value switch
{
< 3 => typeof(sbyte),
< 5 => typeof(short),
< 10 => typeof(int),
< 19 => typeof(long),
< 29 => typeof(decimal),
_ => null
},
XmlTypeCode.Decimal
when restrictions.OfType<FractionDigitsRestrictionModel>().SingleOrDefault() is { IsSupported: true, Value: 0 } => totalDigits
?.Value switch
{
< 3 => typeof(sbyte),
< 5 => typeof(short),
< 10 => typeof(int),
< 19 => typeof(long),
< 29 => typeof(decimal),
_ => null
},
_ => null
};

Type FromFallback() => configuration.UseIntegerDataTypeAsFallback && configuration.IntegerDataType != null ? configuration.IntegerDataType : typeof(string);
Expand All @@ -124,8 +163,8 @@ public static Type GetEffectiveType(this XmlSchemaDatatype type, GeneratorConfig
XmlTypeCode.Time => typeof(DateTime),
XmlTypeCode.Idref => typeof(string),
XmlTypeCode.Integer or XmlTypeCode.NegativeInteger or XmlTypeCode.NonNegativeInteger or XmlTypeCode.NonPositiveInteger or XmlTypeCode.PositiveInteger => GetIntegerDerivedType(type, configuration, restrictions),
XmlTypeCode.Decimal when restrictions.OfType<FractionDigitsRestrictionModel>().SingleOrDefault() is { IsSupported: true, Value: 0 } => GetIntegerDerivedType(type, configuration, restrictions),
_ => type.ValueType,
XmlTypeCode.Decimal when restrictions.OfType<FractionDigitsRestrictionModel>().SingleOrDefault() is { IsSupported: true, Value: 0 } => GetIntegerDerivedType(type, configuration, restrictions),
_ => type.ValueType,
};

if (schemaType.IsDerivedFrom(GuidQualifiedName))
Expand Down