Skip to content

Commit

Permalink
Added new NamingProvider, SubstituteNamingProvider, which can store a…
Browse files Browse the repository at this point in the history
… dictionary of generated names and an alternate to substitute with

- Updated the INamingProvider to add two specific Type-based methods for PropertyNameFromAttribute and PropertyNameFromElement, to ensure that custom implementors can split the implementation accordingly.
- Updated XmlSchemaClassGenerator.Console to take in command line options for --typeNameSubstitute and typeNameSubstituteFile.

What's left:

- Update README
- Add tests
  • Loading branch information
Adam Smith-Platts committed Apr 27, 2023
1 parent 870d816 commit f07f9f7
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 4 deletions.
47 changes: 45 additions & 2 deletions XmlSchemaClassGenerator.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using XmlSchemaClassGenerator;
using XmlSchemaClassGenerator.NamingProviders;
using System;
using System.CodeDom;
using System.Collections.Generic;
Expand All @@ -11,7 +12,7 @@
using System.Threading.Tasks;
using Mono.Options;
using Ganss.IO;


namespace XmlSchemaClassGenerator.Console
{
static class Program
Expand All @@ -20,6 +21,7 @@ static void Main(string[] args)
{
var showHelp = args.Length == 0;
var namespaces = new List<string>();
var nameSubstitutes = new List<string>();
var outputFolder = (string)null;
Type integerType = null;
var useIntegerTypeAsFallback = false;
Expand Down Expand Up @@ -59,6 +61,7 @@ static void Main(string[] args)
var useArrayItemAttribute = true;
var enumAsString = false;
var namespaceFiles = new List<string>();
var nameSubstituteFiles = new List<string>();

var options = new OptionSet {
{ "h|help", "show this message and exit", v => showHelp = v != null },
Expand All @@ -67,9 +70,16 @@ static void Main(string[] args)
One option must be given for each namespace to be mapped.
A file name may be given by appending a pipe sign (|) followed by a file name (like schema.xsd) to the XML namespace.
If no mapping is found for an XML namespace, a name is generated automatically (may fail).", v => namespaces.Add(v) },
{ "nf|namespaceFile=", @"file containing mapppings from XML namespaces to C# namespaces
{ "nf|namespaceFile=", @"file containing mappings from XML namespaces to C# namespaces
The line format is one mapping per line: XML namespace = C# namespace [optional file name].
Lines starting with # and empty lines are ignored.", v => namespaceFiles.Add(v) },
{ "tns|typeNameSubstitute=", @"substitute a generated type/member name
Separate type/member name and substitute name by '='.
Prefix type/member name with an appropriate kind ID as documented at: https://t.ly/HHEI.
Prefix with 'A:' to substitute any type/member.", v => nameSubstitutes.Add(v) },
{ "tnsf|typeNameSubstituteFile=", @"file containing generated type/member name substitute mappings
The line format is one mapping per line: prefixed type/member name = substitute name.
Lines starting with # and empty lines are ignored.", v => nameSubstituteFiles.Add(v) },
{ "o|output=", "the {FOLDER} to write the resulting .cs files to", v => outputFolder = v },
{ "i|integer=", @"map xs:integer and derived types to {TYPE} instead of automatic approximation
{TYPE} can be i[nt], l[ong], or d[ecimal]", v => {
Expand Down Expand Up @@ -178,6 +188,9 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
return name;
});

ParseNameSubstituteFiles(nameSubstitutes, nameSubstituteFiles);
var nameSubstituteMap = nameSubstitutes.ToDictionary();

if (!string.IsNullOrEmpty(outputFolder))
{
outputFolder = Path.GetFullPath(outputFolder);
Expand Down Expand Up @@ -221,6 +234,11 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
EnumAsString = enumAsString,
};

if (nameSubstituteMap.Any())
{
generator.NamingProvider = new SubstituteNamingProvider(nameSubstituteMap);
}

generator.CommentLanguages.AddRange(commentLanguages);

if (pclCompatible)
Expand Down Expand Up @@ -265,6 +283,31 @@ private static void ParseNamespaceFiles(List<string> namespaces, List<string> na
}
}

private static void ParseNameSubstituteFiles(List<string> nameSubstitutes, List<string> nameSubstituteFiles)
{
foreach (var nameSubstituteFile in nameSubstituteFiles)
{
foreach (var (line, number) in File.ReadAllLines(nameSubstituteFile)
.Select((l, i) => (Line: l.Trim(), Number: i + 1))
.Where(l => !string.IsNullOrWhiteSpace(l.Line) && !l.Line.StartsWith("#")))
{
var parts = line.Split('=');

if (parts.Length != 2)
{
System.Console.WriteLine($"{nameSubstituteFile}:{number}: Line format is prefixed type/member name = substitute name");
Environment.Exit(2);
}

var generatedName = parts[0].Trim();
var substituteName = parts[1].Trim();
var nameSubstitute = $"{generatedName}={substituteName}";

nameSubstitutes.Add(nameSubstitute);
}
}
}

static void ShowHelp(OptionSet p)
{
System.Console.WriteLine("Usage: xscgen [OPTIONS]+ xsdFile...");
Expand Down
18 changes: 18 additions & 0 deletions XmlSchemaClassGenerator/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,23 @@ public static IEnumerable<NamespaceHierarchyItem> MarkAmbiguousNamespaceTypes(
yield return hierarchyItem;
}
}

/// <summary>
/// Creates a <see cref="Dictionary{TKey,TValue}" /> from an <see cref="IEnumerable{T}" /> splitting the values into key and value pairs based on the <paramref name="delimiter"/>.
/// </summary>
/// <param name="source">An <see cref="IEnumerable{T}" /> to create a <see cref="Dictionary{TKey,TValue}" /> from.</param>
/// <param name="delimiter">An optional value that supplies the delimiter to use to create the key and value from the <paramref name="source"/>.</param>
/// <returns>A <see cref="Dictionary{TKey,TValue}" /> that contains keys and values. The values within each group are in the same order as in <paramref name="source" />.</returns>
public static Dictionary<string, string> ToDictionary(this IEnumerable<string> source, string delimiter = "=")
{
var result = new Dictionary<string, string>();
foreach (string value in source ?? Enumerable.Empty<string>())
{
var pair = value.Split(new string[] { delimiter ?? "=" }, 2, StringSplitOptions.None);
result.Add(pair[0], pair[1]);
}

return result;
}
}
}
18 changes: 18 additions & 0 deletions XmlSchemaClassGenerator/INamingProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ public interface INamingProvider
/// <returns>Name of the property</returns>
string PropertyNameFromElement(string typeModelName, string elementName, XmlSchemaElement element);

/// <summary>
/// Creates a name for an inner type from an attribute name
/// </summary>
/// <param name="typeModelName">Name of the typeModel</param>
/// <param name="attributeName">Attribute name</param>
/// <param name="attribute">Original XSD attribute</param>
/// <returns>Name of the inner type</returns>
string TypeNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute);

/// <summary>
/// Creates a name for an inner type from an element name
/// </summary>
/// <param name="typeModelName">Name of the typeModel</param>
/// <param name="elementName">Element name</param>
/// <param name="element">Original XSD element</param>
/// <returns>Name of the inner type</returns>
string TypeNameFromElement(string typeModelName, string elementName, XmlSchemaElement element);

/// <summary>
/// Creates a name for an enum member based on a value
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions XmlSchemaClassGenerator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ private PropertyModel PropertyFromAttribute(TypeModel owningTypeModel, XmlSchema
if (attributeQualifiedName.IsEmpty || string.IsNullOrEmpty(attributeQualifiedName.Namespace))
{
// inner type, have to generate a type name
var typeName = _configuration.NamingProvider.PropertyNameFromAttribute(owningTypeModel.Name, attribute.QualifiedName.Name, attribute);
var typeName = _configuration.NamingProvider.TypeNameFromAttribute(owningTypeModel.Name, attribute.QualifiedName.Name, attribute);
attributeQualifiedName = new XmlQualifiedName(typeName, owningTypeModel.XmlSchemaName.Namespace);
// try to avoid name clashes
if (NameExists(attributeQualifiedName))
Expand Down Expand Up @@ -1024,7 +1024,7 @@ private XmlQualifiedName GetQualifiedName(TypeModel typeModel, XmlSchemaParticle
{
// inner type, have to generate a type name
var typeModelName = xmlParticle is XmlSchemaGroupRef groupRef ? groupRef.RefName : typeModel.XmlSchemaName;
var typeName = _configuration.NamingProvider.PropertyNameFromElement(typeModelName.Name, element.QualifiedName.Name, element);
var typeName = _configuration.NamingProvider.TypeNameFromElement(typeModelName.Name, element.QualifiedName.Name, element);
elementQualifiedName = new XmlQualifiedName(typeName, typeModel.XmlSchemaName.Namespace);
// try to avoid name clashes
if (NameExists(elementQualifiedName))
Expand Down
9 changes: 9 additions & 0 deletions XmlSchemaClassGenerator/NamingProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace XmlSchemaClassGenerator
{
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

/// <summary>
Expand Down Expand Up @@ -44,6 +45,14 @@ public virtual string PropertyNameFromElement(string typeModelName, string eleme
return typeModelName.ToTitleCase(_namingScheme) + elementName.ToTitleCase(_namingScheme);
}

/// <inheritdoc/>
public virtual string TypeNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute)
=> PropertyNameFromAttribute(typeModelName, attributeName, attribute);

/// <inheritdoc/>
public virtual string TypeNameFromElement(string typeModelName, string elementName, XmlSchemaElement element)
=> PropertyNameFromElement(typeModelName, elementName, element);

/// <summary>
/// Creates a name for an enum member based on a value
/// </summary>
Expand Down
113 changes: 113 additions & 0 deletions XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;

namespace XmlSchemaClassGenerator.NamingProviders
{
/// <summary>
/// Provides options to customize member names, and automatically substitute names for defined types/members.
/// </summary>
public class SubstituteNamingProvider
: NamingProvider, INamingProvider
{
private readonly Dictionary<string, string> _nameSubstitutes;

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

/// <inheritdoc cref="SubstituteNamingProvider(NamingScheme, Dictionary{string, string})"/>
public SubstituteNamingProvider(Dictionary<string, string> nameSubstitutes)
: this(NamingScheme.PascalCase, 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));

/// <inheritdoc/>
public override string PropertyNameFromElement(string typeModelName, string elementName, XmlSchemaElement element)
=> SubstituteName("P", base.PropertyNameFromElement(typeModelName, elementName, element));

/// <inheritdoc/>
public override string TypeNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute)
=> SubstituteName("T", base.PropertyNameFromAttribute(typeModelName, attributeName, attribute));

/// <inheritdoc/>
public override string TypeNameFromElement(string typeModelName, string elementName, XmlSchemaElement element)
=> SubstituteName("T", base.PropertyNameFromElement(typeModelName, elementName, element));

/// <inheritdoc/>
public override string EnumMemberNameFromValue(string enumName, string value, XmlSchemaEnumerationFacet xmlFacet)
=> SubstituteName($"T:{enumName}", base.EnumMemberNameFromValue(enumName, value, xmlFacet));

/// <inheritdoc/>
public override string ComplexTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaComplexType complexType)
=> SubstituteName("T", base.ComplexTypeNameFromQualifiedName(qualifiedName, complexType));

/// <inheritdoc/>
public override string AttributeGroupTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaAttributeGroup attributeGroup)
=> SubstituteName("T", base.AttributeGroupTypeNameFromQualifiedName(qualifiedName, attributeGroup));

/// <inheritdoc/>
public override string GroupTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaGroup group)
=> SubstituteName("T", base.GroupTypeNameFromQualifiedName(qualifiedName, group));

/// <inheritdoc/>
public override string SimpleTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaSimpleType simpleType)
=> SubstituteName("T", base.SimpleTypeNameFromQualifiedName(qualifiedName, simpleType));

/// <inheritdoc/>
public override string RootClassNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaElement xmlElement)
=> SubstituteName("T", base.RootClassNameFromQualifiedName(qualifiedName, xmlElement));

/// <inheritdoc/>
public override string EnumTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaSimpleType xmlSimpleType)
=> SubstituteName("T", base.EnumTypeNameFromQualifiedName(qualifiedName, xmlSimpleType));

/// <inheritdoc/>
public override string AttributeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaAttribute xmlAttribute)
=> SubstituteName("P", base.AttributeNameFromQualifiedName(qualifiedName, xmlAttribute));

/// <inheritdoc/>
public override string ElementNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaElement xmlElement)
=> SubstituteName("P", base.ElementNameFromQualifiedName(qualifiedName, xmlElement));

private string SubstituteName(string typeIdPrefix, string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return name;
}

string substituteName;
if (_nameSubstitutes.TryGetValue($"{typeIdPrefix}:{name}", out substituteName) || _nameSubstitutes.TryGetValue($"A:{name}", out substituteName))
{
return substituteName;
}

return name;
}
}
}

0 comments on commit f07f9f7

Please sign in to comment.