diff --git a/README.md b/README.md
index 86d19d25..bd24a4b4 100644
--- a/README.md
+++ b/README.md
@@ -66,12 +66,26 @@ Options:
If no mapping is found for an XML namespace, a
name is generated automatically (may fail).
--nf, --namespaceFile=VALUE
- file containing mapppings from XML namespaces to C#
- namespaces
+ 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.
+ --tns, --typeNameSubstitute=VALUE
+ 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.
+ --tnsf, --typeNameSubstituteFile=VALUE
+ 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.
-o, --output=FOLDER the FOLDER to write the resulting .cs files to
-i, --integer=TYPE map xs:integer and derived types to TYPE instead
of automatic approximation
@@ -230,6 +244,37 @@ http://example.com = Example.NamespaceB b.xsd
Use the `--nf` option to specify the mapping file.
+### Substituting generated C# type and member names
+
+If a xsd file specifies obscure names for their types (classes, enums) or members (properties), you can substitute these using the `--tns`/`--typeNameSubstitute=` parameter:
+
+```
+xscgen --tns T:Example_RootType=Example --tns T:Example_RootTypeExampleScope=ExampleScope --tns P:StartDateDateTimeValue=StartDate example.xsd
+```
+
+The syntax for substitution is: `{kindId}:{generatedName}={substituteName}`
+
+The `{kindId}` is a single character identifier based on [documentation/analysis ID format](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments#d42-id-string-format), where valid values are:
+
+| ID | Scope |
+| - | - |
+| `P` | Property |
+| `T` | Type: `class`, `enum`, `interface` |
+| `A` | Any property and/or type |
+
+#### Using substitution files
+
+Instead of specifying the substitutions on the command line you can also use a substitution file which should contain one substitution per line in the following format:
+
+```
+# Comment
+T:Example_RootType = Example
+T:Example_RootTypeExampleScope = ExampleScope
+P:StartDateDateTimeValue = StartDate
+```
+
+Use the `--tnsf`/`--typeNameSubstituteFile` option to specify the substitution file.
+
Nullables
---------------------------------
diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs
index ec7d8c2b..ae8034fa 100644
--- a/XmlSchemaClassGenerator.Console/Program.cs
+++ b/XmlSchemaClassGenerator.Console/Program.cs
@@ -1,4 +1,5 @@
using XmlSchemaClassGenerator;
+using XmlSchemaClassGenerator.NamingProviders;
using System;
using System.CodeDom;
using System.Collections.Generic;
@@ -11,7 +12,7 @@
using System.Threading.Tasks;
using Mono.Options;
using Ganss.IO;
-
+
namespace XmlSchemaClassGenerator.Console
{
static class Program
@@ -20,6 +21,7 @@ static void Main(string[] args)
{
var showHelp = args.Length == 0;
var namespaces = new List();
+ var nameSubstitutes = new List();
var outputFolder = (string)null;
Type integerType = null;
var useIntegerTypeAsFallback = false;
@@ -59,6 +61,7 @@ static void Main(string[] args)
var useArrayItemAttribute = true;
var enumAsString = false;
var namespaceFiles = new List();
+ var nameSubstituteFiles = new List();
var options = new OptionSet {
{ "h|help", "show this message and exit", v => showHelp = v != null },
@@ -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 => {
@@ -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);
@@ -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)
@@ -265,6 +283,31 @@ private static void ParseNamespaceFiles(List namespaces, List na
}
}
+ private static void ParseNameSubstituteFiles(List nameSubstitutes, List 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...");
diff --git a/XmlSchemaClassGenerator/EnumerableExtensions.cs b/XmlSchemaClassGenerator/EnumerableExtensions.cs
index 038f60a3..5f1c02d9 100644
--- a/XmlSchemaClassGenerator/EnumerableExtensions.cs
+++ b/XmlSchemaClassGenerator/EnumerableExtensions.cs
@@ -97,5 +97,23 @@ public static IEnumerable MarkAmbiguousNamespaceTypes(
yield return hierarchyItem;
}
}
+
+ ///
+ /// Creates a from an splitting the values into key and value pairs based on the .
+ ///
+ /// An to create a from.
+ /// An optional value that supplies the delimiter to use to create the key and value from the .
+ /// A that contains keys and values. The values within each group are in the same order as in .
+ public static Dictionary ToDictionary(this IEnumerable source, string delimiter = "=")
+ {
+ var result = new Dictionary();
+ foreach (string value in source ?? Enumerable.Empty())
+ {
+ var pair = value.Split(new string[] { delimiter ?? "=" }, 2, StringSplitOptions.None);
+ result.Add(pair[0], pair[1]);
+ }
+
+ return result;
+ }
}
}
diff --git a/XmlSchemaClassGenerator/INamingProvider.cs b/XmlSchemaClassGenerator/INamingProvider.cs
index d04d6dad..65ca1dbb 100644
--- a/XmlSchemaClassGenerator/INamingProvider.cs
+++ b/XmlSchemaClassGenerator/INamingProvider.cs
@@ -23,6 +23,24 @@ public interface INamingProvider
/// Name of the property
string PropertyNameFromElement(string typeModelName, string elementName, XmlSchemaElement element);
+ ///
+ /// Creates a name for an inner type from an attribute name
+ ///
+ /// Name of the typeModel
+ /// Attribute name
+ /// Original XSD attribute
+ /// Name of the inner type
+ string TypeNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute);
+
+ ///
+ /// Creates a name for an inner type from an element name
+ ///
+ /// Name of the typeModel
+ /// Element name
+ /// Original XSD element
+ /// Name of the inner type
+ string TypeNameFromElement(string typeModelName, string elementName, XmlSchemaElement element);
+
///
/// Creates a name for an enum member based on a value
///
diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs
index 9d5fe194..a81d2abe 100644
--- a/XmlSchemaClassGenerator/ModelBuilder.cs
+++ b/XmlSchemaClassGenerator/ModelBuilder.cs
@@ -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))
@@ -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))
diff --git a/XmlSchemaClassGenerator/NamingProvider.cs b/XmlSchemaClassGenerator/NamingProvider.cs
index 754a0d00..a9de595e 100644
--- a/XmlSchemaClassGenerator/NamingProvider.cs
+++ b/XmlSchemaClassGenerator/NamingProvider.cs
@@ -1,6 +1,7 @@
namespace XmlSchemaClassGenerator
{
using System.Xml;
+ using System.Xml.Linq;
using System.Xml.Schema;
///
@@ -44,6 +45,14 @@ public virtual string PropertyNameFromElement(string typeModelName, string eleme
return typeModelName.ToTitleCase(_namingScheme) + elementName.ToTitleCase(_namingScheme);
}
+ ///
+ public virtual string TypeNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute)
+ => PropertyNameFromAttribute(typeModelName, attributeName, attribute);
+
+ ///
+ public virtual string TypeNameFromElement(string typeModelName, string elementName, XmlSchemaElement element)
+ => PropertyNameFromElement(typeModelName, elementName, element);
+
///
/// Creates a name for an enum member based on a value
///
diff --git a/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs b/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs
new file mode 100644
index 00000000..cba9a081
--- /dev/null
+++ b/XmlSchemaClassGenerator/NamingProviders/SubstituteNamingProvider.cs
@@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using System.Xml;
+using System.Xml.Schema;
+
+namespace XmlSchemaClassGenerator.NamingProviders
+{
+ ///
+ /// Provides options to customize member names, and automatically substitute names for defined types/members.
+ ///
+ public class SubstituteNamingProvider
+ : NamingProvider, INamingProvider
+ {
+ private readonly Dictionary _nameSubstitutes;
+
+ ///
+ public SubstituteNamingProvider(NamingScheme namingScheme)
+ : this(namingScheme, new())
+ {
+ }
+
+ ///
+ public SubstituteNamingProvider(Dictionary nameSubstitutes)
+ : this(NamingScheme.PascalCase, 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));
+
+ ///
+ public override string PropertyNameFromElement(string typeModelName, string elementName, XmlSchemaElement element)
+ => SubstituteName("P", base.PropertyNameFromElement(typeModelName, elementName, element));
+
+ ///
+ public override string TypeNameFromAttribute(string typeModelName, string attributeName, XmlSchemaAttribute attribute)
+ => SubstituteName("T", base.PropertyNameFromAttribute(typeModelName, attributeName, attribute));
+
+ ///
+ public override string TypeNameFromElement(string typeModelName, string elementName, XmlSchemaElement element)
+ => SubstituteName("T", base.PropertyNameFromElement(typeModelName, elementName, element));
+
+ ///
+ public override string EnumMemberNameFromValue(string enumName, string value, XmlSchemaEnumerationFacet xmlFacet)
+ => SubstituteName($"T:{enumName}", base.EnumMemberNameFromValue(enumName, value, xmlFacet));
+
+ ///
+ public override string ComplexTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaComplexType complexType)
+ => SubstituteName("T", base.ComplexTypeNameFromQualifiedName(qualifiedName, complexType));
+
+ ///
+ public override string AttributeGroupTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaAttributeGroup attributeGroup)
+ => SubstituteName("T", base.AttributeGroupTypeNameFromQualifiedName(qualifiedName, attributeGroup));
+
+ ///
+ public override string GroupTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaGroup group)
+ => SubstituteName("T", base.GroupTypeNameFromQualifiedName(qualifiedName, group));
+
+ ///
+ public override string SimpleTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaSimpleType simpleType)
+ => SubstituteName("T", base.SimpleTypeNameFromQualifiedName(qualifiedName, simpleType));
+
+ ///
+ public override string RootClassNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaElement xmlElement)
+ => SubstituteName("T", base.RootClassNameFromQualifiedName(qualifiedName, xmlElement));
+
+ ///
+ public override string EnumTypeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaSimpleType xmlSimpleType)
+ => SubstituteName("T", base.EnumTypeNameFromQualifiedName(qualifiedName, xmlSimpleType));
+
+ ///
+ public override string AttributeNameFromQualifiedName(XmlQualifiedName qualifiedName, XmlSchemaAttribute xmlAttribute)
+ => SubstituteName("P", base.AttributeNameFromQualifiedName(qualifiedName, xmlAttribute));
+
+ ///
+ 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;
+ }
+ }
+}