From adb27ca50b3e45786e9ea34c8e4e6c1fcfe42e6a Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Thu, 30 Apr 2020 21:37:21 +0300 Subject: [PATCH 1/3] Ensure generated enum members are unique It is valid to have an XSD enumeration with values that differs only in character case. E.g. one can use enum value 'M' for month definition and another value 'm' for minutes. To ensure that enum members are unique in generated code after name generation this commit adds positive integer postfix for each non unique enum member name: A A1 BB BB1 BB2 generated for input enum values a A bb bB BB --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 41 +++++++++++++++++++++++ XmlSchemaClassGenerator/ModelBuilder.cs | 18 ++++++++++ 2 files changed, 59 insertions(+) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index bdd3b5bd..3799f20c 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -1382,5 +1382,46 @@ 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); + } } } diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index e461cea5..dba2a9b9 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -413,6 +413,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 +460,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(); From b2196584906bfcf9d11d625084013eccfd4fa7d4 Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Fri, 1 May 2020 11:21:14 +0300 Subject: [PATCH 2/3] Rename interface property if property renamed in derived class If some class has property with the same name as class, then property renamed to 'xxxProperty'. If some interface has property with a name same as one of derived classes then interface property must be renamed too. This commit ensures that interface property and properties in all interface implementations renamed too. --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 79 +++++++++++++++++++++++ XmlSchemaClassGenerator/ModelBuilder.cs | 60 ++++++++++++++--- XmlSchemaClassGenerator/TypeModel.cs | 67 ++++++++++++++++--- 3 files changed, 189 insertions(+), 17 deletions(-) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 3799f20c..dba8010d 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -1423,5 +1423,84 @@ public void EnumWithNonUniqueEntriesTest() 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); + } } } diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index dba2a9b9..4d12b569 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -62,6 +62,46 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) var elements = set.GlobalElements.Values.Cast().Where(s => s.GetSchema() == schema); CreateElements(elements); } + + if (configuration.GenerateInterfaces) + { + RenameInterfacePropertiesIfRenamedInDerivedClasses(); + } + } + + 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 +257,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 +289,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 +347,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 +386,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); } } @@ -597,6 +639,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 @@ -607,6 +650,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 730c273a..e61a9f9b 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() { @@ -224,23 +224,48 @@ public override CodeTypeDeclaration Generate() 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(); } @@ -514,11 +539,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; } From 9d32425d3271e4f6e5b6f27290da4a050a90e34c Mon Sep 17 00:00:00 2001 From: Alexander Titov Date: Fri, 1 May 2020 11:26:22 +0300 Subject: [PATCH 3/3] Remove dupplicate interface members in derived interfaces This commit ensures that all interface properties with the same name and type as in base interfaces are removed as duplicates. --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 80 +++++++++++++++++++++++ XmlSchemaClassGenerator/ModelBuilder.cs | 20 ++++++ 2 files changed, 100 insertions(+) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index dba8010d..bc0c0888 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -1502,5 +1502,85 @@ public void RenameInterfacePropertyInDerivedClassTest() 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 4d12b569..c281f361 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -66,6 +66,26 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) 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); + } + } + } } }