Skip to content

Commit

Permalink
Add naming scheme command line option (fixes #520)
Browse files Browse the repository at this point in the history
Refactor syntax
  • Loading branch information
mganss committed Jul 2, 2024
1 parent 8d8f5ff commit bb45e71
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 41 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
</pre>

For use from code use the [library NuGet package](https://www.nuget.org/packages/XmlSchemaClassGenerator-beta/):
Expand Down
45 changes: 29 additions & 16 deletions XmlSchemaClassGenerator.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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 },
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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())
Expand Down
8 changes: 8 additions & 0 deletions XmlSchemaClassGenerator/CodeUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
Expand Down
3 changes: 3 additions & 0 deletions XmlSchemaClassGenerator/NamingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
41 changes: 17 additions & 24 deletions XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ namespace XmlSchemaClassGenerator.NamingProviders
/// <summary>
/// Provides options to customize member names, and automatically substitute names for defined types/members.
/// </summary>
public class SubstituteNamingProvider
: NamingProvider, INamingProvider
/// <remarks>
/// Initializes a new instance of the <see cref="SubstituteNamingProvider"/> class.
/// </remarks>
/// <param name="namingScheme">The naming scheme.</param>
/// <param name="nameSubstitutes">
/// A dictionary containing name substitute pairs.
/// <para>
/// Keys need to be prefixed with an appropriate kind ID as documented at:
/// <see href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments#d42-id-string-format">https://t.ly/HHEI</see>.
/// </para>
/// <para>Prefix with <c>A:</c> to substitute any type/member.</para>
/// </param>
public class SubstituteNamingProvider(NamingScheme namingScheme, Dictionary<string, string> nameSubstitutes)
: NamingProvider(namingScheme), INamingProvider
{
private readonly Dictionary<string, string> _nameSubstitutes;
private readonly Dictionary<string, string> _nameSubstitutes = nameSubstitutes;

/// <inheritdoc cref="SubstituteNamingProvider(NamingScheme, Dictionary{string, string})"/>
public SubstituteNamingProvider(NamingScheme namingScheme)
: this(namingScheme, new())
: this(namingScheme, [])
{
}

Expand All @@ -24,24 +36,6 @@ public SubstituteNamingProvider(Dictionary<string, string> nameSubstitutes)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SubstituteNamingProvider"/> class.
/// </summary>
/// <param name="namingScheme">The naming scheme.</param>
/// <param name="nameSubstitutes">
/// A dictionary containing name substitute pairs.
/// <para>
/// Keys need to be prefixed with an appropriate kind ID as documented at:
/// <see href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments#d42-id-string-format">https://t.ly/HHEI</see>.
/// </para>
/// <para>Prefix with <c>A:</c> to substitute any type/member.</para>
/// </param>
public SubstituteNamingProvider(NamingScheme namingScheme, Dictionary<string, string> nameSubstitutes)
: base(namingScheme)
{
_nameSubstitutes = nameSubstitutes;
}

/// <inheritdoc/>
public override string PropertyNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute)
=> SubstituteName("P", base.PropertyNameFromAttribute(typeModelName, attributeName, attribute));
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion XmlSchemaClassGenerator/NamingScheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace XmlSchemaClassGenerator
public enum NamingScheme
{
Direct,
PascalCase
PascalCase,
LegacyPascalCase,
}
}

0 comments on commit bb45e71

Please sign in to comment.