diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index bdd3b5bd..bc0c0888 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -1382,5 +1382,205 @@ public void RestrictedIntegerDerivedTypesAreMappedToExpectedCSharpTypes(string r Assert.Contains($"public {expectedTypeName} RestrictedInteger{i}", generatedType); } } + + [Fact] + public void EnumWithNonUniqueEntriesTest() + { + const string xsd = @" + + + + + + + + + + "; + + var generator = new Generator + { + NamespaceProvider = new NamespaceProvider + { + GenerateNamespace = key => "Test" + }, + GenerateInterfaces = true, + AssemblyVisible = true + }; + var contents = ConvertXml(nameof(EnumWithNonUniqueEntriesTest), xsd, generator); + var content = Assert.Single(contents); + + var assembly = Compiler.Compile(nameof(EnumWithNonUniqueEntriesTest), content); + var durationEnumType = assembly.GetType("Test.TestEnum"); + Assert.NotNull(durationEnumType); + + var expectedEnumValues = new[] {"Test_Case", "Test_Case1", "Test_Case2", "Test_Case3"}; + var enumValues = durationEnumType.GetEnumNames().OrderBy(n => n).ToList(); + Assert.Equal(expectedEnumValues, enumValues); + + var mEnumValue = durationEnumType.GetMembers().First(mi => mi.Name == "Test_Case1"); + var xmlEnumAttribute = mEnumValue.GetCustomAttributes().FirstOrDefault(); + Assert.NotNull(xmlEnumAttribute); + Assert.Equal("test_Case", xmlEnumAttribute.Name); + } + + [Fact] + public void RenameInterfacePropertyInDerivedClassTest() + { + const string xsd = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + + var generator = new Generator + { + NamespaceProvider = new NamespaceProvider + { + GenerateNamespace = key => "Test" + }, + GenerateInterfaces = true, + AssemblyVisible = true + }; + var contents = ConvertXml(nameof(RenameInterfacePropertyInDerivedClassTest), xsd, generator); + var content = Assert.Single(contents); + + var assembly = Compiler.Compile(nameof(RenameInterfacePropertyInDerivedClassTest), content); + var classType = assembly.GetType("Test.ClassItem"); + Assert.NotNull(classType); + Assert.Single(classType.GetProperties()); + Assert.Equal("ClassItemBaseProperty", classType.GetProperties().First().Name); + + var level1Interface = assembly.GetType("Test.ILevel1"); + Assert.NotNull(level1Interface); + Assert.Empty(level1Interface.GetProperties()); + + var level2Interface = assembly.GetType("Test.ILevel1"); + Assert.NotNull(level2Interface); + Assert.Empty(level2Interface.GetProperties()); + + var level3Interface = assembly.GetType("Test.ILevel3"); + Assert.NotNull(level3Interface); + Assert.Single(level3Interface.GetProperties()); + Assert.Equal("ClassItemBaseProperty", level3Interface.GetProperties().First().Name); + } + + [Fact] + public void DoNotGenerateSamePropertiesInDerivedInterfacesClassTest() + { + const string xsd = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + + var generator = new Generator + { + NamespaceProvider = new NamespaceProvider + { + GenerateNamespace = key => "Test" + }, + GenerateInterfaces = true, + AssemblyVisible = true + }; + var contents = ConvertXml(nameof(DoNotGenerateSamePropertiesInDerivedInterfacesClassTest), xsd, generator); + var content = Assert.Single(contents); + + var assembly = Compiler.Compile(nameof(DoNotGenerateSamePropertiesInDerivedInterfacesClassTest), content); + + var listType = assembly.GetType("Test.ParentClass"); + Assert.NotNull(listType); + + var listTypePropertyInfo = listType.GetProperties().FirstOrDefault(p => p.Name == "InterfaceProperty"); + Assert.NotNull(listTypePropertyInfo); + + var level1Interface = assembly.GetType("Test.ILevel1"); + Assert.NotNull(level1Interface); + Assert.Empty(level1Interface.GetProperties()); + + var level2Interface = assembly.GetType("Test.ILevel2"); + Assert.NotNull(level2Interface); + Assert.Empty(level2Interface.GetProperties()); + + var level3Interface = assembly.GetType("Test.ILevel3"); + Assert.NotNull(level3Interface); + var level3InterfacePropertyInfo = level3Interface.GetProperties().FirstOrDefault(p => p.Name == "InterfaceProperty"); + Assert.NotNull(level3InterfacePropertyInfo); + + } } } diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index e461cea5..c281f361 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -62,6 +62,66 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) var elements = set.GlobalElements.Values.Cast().Where(s => s.GetSchema() == schema); CreateElements(elements); } + + if (configuration.GenerateInterfaces) + { + RenameInterfacePropertiesIfRenamedInDerivedClasses(); + RemoveDuplicateInterfaceProperties(); + } + } + + private void RemoveDuplicateInterfaceProperties() + { + foreach (var interfaceModel in Types.Values.OfType()) + { + var parentProperties = interfaceModel.Properties.ToList(); + foreach (var baseInterfaceType in interfaceModel.AllDerivedReferenceTypes().OfType()) + { + foreach (var parentProperty in parentProperties) + { + var baseProperties = baseInterfaceType.Properties.ToList(); + foreach (var baseProperty in baseProperties.Where(baseProperty => parentProperty.Name == baseProperty.Name && parentProperty.Type.Name == baseProperty.Type.Name)) + { + baseInterfaceType.Properties.Remove(baseProperty); + } + } + } + } + } + + private void RenameInterfacePropertiesIfRenamedInDerivedClasses() + { + foreach (var interfaceModel in Types.Values.OfType()) + { + foreach (var interfaceProperty in interfaceModel.Properties) + { + foreach (var implementationClass in interfaceModel.AllDerivedReferenceTypes()) + foreach (var implementationClassProperty in implementationClass.Properties) + { + if (implementationClassProperty.Name != implementationClassProperty.OriginalPropertyName + && implementationClassProperty.OriginalPropertyName == interfaceProperty.Name + ) + { + RenameInterfacePropertyInBaseClasses(interfaceModel, implementationClass, interfaceProperty, implementationClassProperty.Name); + interfaceProperty.Name = implementationClassProperty.Name; + } + } + } + } + } + + private static void RenameInterfacePropertyInBaseClasses(InterfaceModel interfaceModel, ReferenceTypeModel implementationClass, + PropertyModel interfaceProperty, string newName) + { + foreach (var interfaceModelImplementationClass in interfaceModel.AllDerivedReferenceTypes().Where(c => + c != implementationClass)) + { + foreach (var propertyModel in interfaceModelImplementationClass.Properties.Where(p => + p.Name == interfaceProperty.Name)) + { + propertyModel.Name = newName; + } + } } private void ResolveDependencies(XmlSchema schema, List dependencyOrder, HashSet seenSchemas) @@ -217,7 +277,7 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaGroup group, NamespaceMod interfaceModel.Properties.AddRange(properties); var interfaces = items.Select(i => i.XmlParticle).OfType() .Select(i => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(i.SourceUri), Groups[i.RefName], i.RefName)); - interfaceModel.Interfaces.AddRange(interfaces); + interfaceModel.AddInterfaces(interfaces); return interfaceModel; } @@ -249,7 +309,7 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaAttributeGroup attributeG interfaceModel.Properties.AddRange(properties); var interfaces = items.OfType() .Select(a => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(a.SourceUri), AttributeGroups[a.RefName], a.RefName)); - interfaceModel.Interfaces.AddRange(interfaces); + interfaceModel.AddInterfaces(interfaces); return interfaceModel; } @@ -307,17 +367,19 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaComplexType complexType, } else particle = complexType.Particle ?? complexType.ContentTypeParticle; - var items = GetElements(particle, complexType); - var properties = CreatePropertiesForElements(source, classModel, particle, items); - classModel.Properties.AddRange(properties); + var items = GetElements(particle, complexType).ToList(); if (_configuration.GenerateInterfaces) { var interfaces = items.Select(i => i.XmlParticle).OfType() - .Select(i => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(i.SourceUri), Groups[i.RefName], i.RefName)); - classModel.Interfaces.AddRange(interfaces); + .Select(i => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(i.SourceUri), Groups[i.RefName], i.RefName)).ToList(); + + classModel.AddInterfaces(interfaces); } + var properties = CreatePropertiesForElements(source, classModel, particle, items); + classModel.Properties.AddRange(properties); + XmlSchemaObjectCollection attributes = null; if (classModel.BaseClass != null) { @@ -344,7 +406,7 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaComplexType complexType, { var attributeInterfaces = attributes.OfType() .Select(i => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(i.SourceUri), AttributeGroups[i.RefName], i.RefName)); - classModel.Interfaces.AddRange(attributeInterfaces); + classModel.AddInterfaces(attributeInterfaces); } } @@ -413,6 +475,7 @@ private TypeModel CreateTypeModel(XmlSchemaSimpleType simpleType, NamespaceModel enumModel.Values.Add(value); } + enumModel.Values = EnsureEnumValuesUnique(enumModel.Values); if (namespaceModel != null) { namespaceModel.Types[enumModel.Name] = enumModel; @@ -459,6 +522,23 @@ private TypeModel CreateTypeModel(XmlSchemaSimpleType simpleType, NamespaceModel return simpleModel; } + private static List EnsureEnumValuesUnique(List enumModelValues) + { + var enumValueGroups = from enumValue in enumModelValues + group enumValue by enumValue.Name; + + foreach (var g in enumValueGroups) + { + var i = 1; + foreach (var t in g.Skip(1)) + { + t.Name = $"{t.Name}{i++}"; + } + } + + return enumModelValues; + } + private IEnumerable CreatePropertiesForAttributes(Uri source, TypeModel typeModel, IEnumerable items) { var properties = new List(); @@ -579,6 +659,7 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM } var propertyName = _configuration.NamingProvider.ElementNameFromQualifiedName(element.QualifiedName); + var originalPropertyName = propertyName; if (propertyName == typeModel.Name) { propertyName += "Property"; // member names cannot be the same as their enclosing type @@ -589,6 +670,7 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM OwningType = typeModel, XmlSchemaName = element.QualifiedName, Name = propertyName, + OriginalPropertyName = originalPropertyName, Type = CreateTypeModel(source, element.ElementSchemaType, elementQualifiedName), IsNillable = element.IsNillable, IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice), diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index 9d13a07e..8740962e 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -1,6 +1,7 @@ using System; using System.CodeDom; using System.CodeDom.Compiler; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; @@ -193,17 +194,16 @@ public virtual CodeExpression GetDefaultValueFor(string defaultString, bool attr } } - public class InterfaceModel : TypeModel + public class InterfaceModel : ReferenceTypeModel { public InterfaceModel(GeneratorConfiguration configuration) : base(configuration) { Properties = new List(); - Interfaces = new List(); + DerivedTypes = new List(); } - public List Properties { get; set; } - public List Interfaces { get; set; } + public List DerivedTypes { get; set; } public override CodeTypeDeclaration Generate() { @@ -225,23 +225,48 @@ public override CodeTypeDeclaration Generate() Configuration.TypeVisitor(interfaceDeclaration, this); return interfaceDeclaration; } + + public IEnumerable AllDerivedReferenceTypes() + { + foreach (var interfaceModelDerivedType in DerivedTypes) + { + yield return interfaceModelDerivedType; + switch (interfaceModelDerivedType) + { + case InterfaceModel derivedInterfaceModel: + { + foreach (var referenceTypeModel in derivedInterfaceModel.AllDerivedReferenceTypes()) + { + yield return referenceTypeModel; + } + + break; + } + case ClassModel derivedClassModel: + { + foreach (var baseClass in derivedClassModel.GetAllDerivedTypes()) + { + yield return baseClass; + } + + break; + } + } + } + } } - public class ClassModel : TypeModel + public class ClassModel : ReferenceTypeModel { public bool IsAbstract { get; set; } public bool IsMixed { get; set; } public bool IsSubstitution { get; set; } public XmlQualifiedName SubstitutionName { get; set; } public TypeModel BaseClass { get; set; } - public List Properties { get; set; } - public List Interfaces { get; set; } public List DerivedTypes { get; set; } public ClassModel(GeneratorConfiguration configuration) : base(configuration) { - Properties = new List(); - Interfaces = new List(); DerivedTypes = new List(); } @@ -516,11 +541,35 @@ public override CodeExpression GetDefaultValueFor(string defaultString, bool att } } + public class ReferenceTypeModel : TypeModel + { + public ReferenceTypeModel(GeneratorConfiguration configuration) + : base(configuration) + { + Properties = new List(); + Interfaces = new List(); + } + + public List Properties { get; set; } + public List Interfaces { get; } + + public void AddInterfaces(IEnumerable interfaces) + { + foreach (var interfaceModel in interfaces) + { + Interfaces.Add(interfaceModel); + interfaceModel.DerivedTypes.Add(this); + } + + } + } + [DebuggerDisplay("{Name}")] public class PropertyModel { public TypeModel OwningType { get; set; } public string Name { get; set; } + public string OriginalPropertyName { get; set; } public bool IsAttribute { get; set; } public TypeModel Type { get; set; } public bool IsNullable { get; set; }