From bb45e71d46efceec7692bb5ae30d9873790488e8 Mon Sep 17 00:00:00 2001 From: Michael Ganss Date: Tue, 2 Jul 2024 14:24:51 +0200 Subject: [PATCH] Add naming scheme command line option (fixes #520) Refactor syntax --- README.md | 3 ++ XmlSchemaClassGenerator.Console/Program.cs | 45 ++++++++++++------- XmlSchemaClassGenerator/CodeUtilities.cs | 8 ++++ XmlSchemaClassGenerator/NamingExtensions.cs | 3 ++ .../SubstituteNamingProvider.cs | 41 +++++++---------- XmlSchemaClassGenerator/NamingScheme.cs | 3 +- 6 files changed, 62 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 9799ad81..a0914cc2 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,9 @@ Options: default is false) --dtd, --allowDtdParse allow DTD parsing (default is false) + --ns, --namingScheme use the specified naming scheme for class and + property names (default is Pascal; can be: + Direct, Pascal, Legacy) For use from code use the [library NuGet package](https://www.nuget.org/packages/XmlSchemaClassGenerator-beta/): diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 6aee3ce0..c98c0d48 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -67,6 +67,7 @@ static int Main(string[] args) var separateNamespaceHierarchy = false; var serializeEmptyCollections = false; var allowDtdParse = false; + NamingScheme? namingScheme = null; var options = new OptionSet { { "h|help", "show this message and exit", v => showHelp = v != null }, @@ -123,20 +124,20 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l { "csm|collectionSettersMode=", @"generate a private, public, or init-only setter with or without backing field initialization for collections (default is Private; can be: {Private, Public, PublicWithoutConstructorInitialization, Init, InitWithoutConstructorInitialization})", - v => - { - collectionSettersMode = v switch - { - "pr" or "Private" => CollectionSettersMode.Private, - "pu" or "Public" => CollectionSettersMode.Public, - "puwci" or "PublicWithoutConstructorInitialization" => - CollectionSettersMode.PublicWithoutConstructorInitialization, - "in" or "Init" => CollectionSettersMode.Init, - "inwci" or "InitWithoutConstructorInitialization" => - CollectionSettersMode.InitWithoutConstructorInitialization, - _ => CollectionSettersMode.Private - }; - }}, + v => + { + collectionSettersMode = v switch + { + "pr" or "Private" => CollectionSettersMode.Private, + "pu" or "Public" => CollectionSettersMode.Public, + "puwci" or "PublicWithoutConstructorInitialization" => + CollectionSettersMode.PublicWithoutConstructorInitialization, + "in" or "Init" => CollectionSettersMode.Init, + "inwci" or "InitWithoutConstructorInitialization" => + CollectionSettersMode.InitWithoutConstructorInitialization, + _ => CollectionSettersMode.Private + }; + }}, { "ctro|codeTypeReferenceOptions=", "the default CodeTypeReferenceOptions Flags to use (default is unset; can be: {GlobalReference, GenericTypeParameter})", v => codeTypeReferenceOptions = v == null ? default : (CodeTypeReferenceOptions)Enum.Parse(typeof(CodeTypeReferenceOptions), v, false) }, { "tvpn|textValuePropertyName=", "the name of the property that holds the text value of an element (default is Value)", v => textValuePropertyName = v }, { "dst|debuggerStepThrough", "generate DebuggerStepThroughAttribute (default is enabled)", v => generateDebuggerStepThroughAttribute = v != null }, @@ -162,6 +163,18 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l { "uc|unionCommonType", "generate a common type for unions if possible (default is false)", v => unionCommonType = v != null }, { "ec|serializeEmptyCollections", "serialize empty collections (default is false)", v => serializeEmptyCollections = v != null }, { "dtd|allowDtdParse", "allows dtd parse (default is false)", v => allowDtdParse = v != null }, + { "ns|namingScheme", @"use the specified naming scheme for class and property names (default is Pascal; can be: Direct, Pascal, Legacy)", + v => + { + namingScheme = v?.ToLowerInvariant() switch + { + "direct" => NamingScheme.Direct, + "pascal" => NamingScheme.PascalCase, + "legacy" => NamingScheme.LegacyPascalCase, + _ => NamingScheme.PascalCase, + }; + } + } }; var globsAndUris = options.Parse(args); @@ -222,7 +235,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l DateTimeWithTimeZone = dateTimeWithTimeZone, EntityFramework = entityFramework, GenerateInterfaces = interfaces, - NamingScheme = pascal ? NamingScheme.PascalCase : NamingScheme.Direct, + NamingScheme = namingScheme != null ? namingScheme.Value : (pascal ? NamingScheme.PascalCase : NamingScheme.Direct), AssemblyVisible = assembly, CollectionType = collectionType, CollectionImplementationType = collectionImplementationType, @@ -250,7 +263,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l MapUnionToWidestCommonType = unionCommonType, SeparateNamespaceHierarchy = separateNamespaceHierarchy, SerializeEmptyCollections = serializeEmptyCollections, - AllowDtdParse = allowDtdParse + AllowDtdParse = allowDtdParse, }; if (nameSubstituteMap.Any()) diff --git a/XmlSchemaClassGenerator/CodeUtilities.cs b/XmlSchemaClassGenerator/CodeUtilities.cs index 1819522e..550fb151 100644 --- a/XmlSchemaClassGenerator/CodeUtilities.cs +++ b/XmlSchemaClassGenerator/CodeUtilities.cs @@ -14,6 +14,14 @@ 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 ToLegacyPascalCase(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(@"[^_\p{L}\p{N}]"); private static readonly Regex whiteSpace = new(@"(?<=\s)"); private static readonly Regex startsWithLowerCaseChar = new(@"^\p{Ll}"); diff --git a/XmlSchemaClassGenerator/NamingExtensions.cs b/XmlSchemaClassGenerator/NamingExtensions.cs index 48104918..1c47ae31 100644 --- a/XmlSchemaClassGenerator/NamingExtensions.cs +++ b/XmlSchemaClassGenerator/NamingExtensions.cs @@ -106,6 +106,9 @@ public static string ToTitleCase(this string s, NamingScheme namingScheme) case NamingScheme.PascalCase: s = s.ToPascalCase(); break; + case NamingScheme.LegacyPascalCase: + s = s.ToLegacyPascalCase(); + break; } return s.MakeValidIdentifier(); } diff --git a/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs b/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs index cba9a081..a9533adc 100644 --- a/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs +++ b/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs @@ -7,14 +7,26 @@ namespace XmlSchemaClassGenerator.NamingProviders /// /// Provides options to customize member names, and automatically substitute names for defined types/members. /// - public class SubstituteNamingProvider - : NamingProvider, INamingProvider + /// + /// Initializes a new instance of the class. + /// + /// The naming scheme. + /// + /// A dictionary containing name substitute pairs. + /// + /// Keys need to be prefixed with an appropriate kind ID as documented at: + /// https://t.ly/HHEI. + /// + /// Prefix with A: to substitute any type/member. + /// + public class SubstituteNamingProvider(NamingScheme namingScheme, Dictionary nameSubstitutes) + : NamingProvider(namingScheme), INamingProvider { - private readonly Dictionary _nameSubstitutes; + private readonly Dictionary _nameSubstitutes = nameSubstitutes; /// public SubstituteNamingProvider(NamingScheme namingScheme) - : this(namingScheme, new()) + : this(namingScheme, []) { } @@ -24,24 +36,6 @@ public SubstituteNamingProvider(Dictionary nameSubstitutes) { } - /// - /// Initializes a new instance of the class. - /// - /// The naming scheme. - /// - /// A dictionary containing name substitute pairs. - /// - /// Keys need to be prefixed with an appropriate kind ID as documented at: - /// https://t.ly/HHEI. - /// - /// Prefix with A: to substitute any type/member. - /// - public SubstituteNamingProvider(NamingScheme namingScheme, Dictionary nameSubstitutes) - : base(namingScheme) - { - _nameSubstitutes = nameSubstitutes; - } - /// public override string PropertyNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute) => SubstituteName("P", base.PropertyNameFromAttribute(typeModelName, attributeName, attribute)); @@ -101,8 +95,7 @@ private string SubstituteName(string typeIdPrefix, string name) return name; } - string substituteName; - if (_nameSubstitutes.TryGetValue($"{typeIdPrefix}:{name}", out substituteName) || _nameSubstitutes.TryGetValue($"A:{name}", out substituteName)) + if (_nameSubstitutes.TryGetValue($"{typeIdPrefix}:{name}", out string substituteName) || _nameSubstitutes.TryGetValue($"A:{name}", out substituteName)) { return substituteName; } diff --git a/XmlSchemaClassGenerator/NamingScheme.cs b/XmlSchemaClassGenerator/NamingScheme.cs index 38863996..0a3db60c 100644 --- a/XmlSchemaClassGenerator/NamingScheme.cs +++ b/XmlSchemaClassGenerator/NamingScheme.cs @@ -9,6 +9,7 @@ namespace XmlSchemaClassGenerator public enum NamingScheme { Direct, - PascalCase + PascalCase, + LegacyPascalCase, } }