Skip to content

Commit

Permalink
Merge pull request #190 from AVTit/master
Browse files Browse the repository at this point in the history
Ensure generated enum members are unique
  • Loading branch information
mganss authored May 1, 2020
2 parents 88f0d12 + 9d32425 commit 00c10ee
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 17 deletions.
200 changes: 200 additions & 0 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1382,5 +1382,205 @@ public void RestrictedIntegerDerivedTypesAreMappedToExpectedCSharpTypes(string r
Assert.Contains($"public {expectedTypeName} RestrictedInteger{i}", generatedType);
}
}

[Fact]
public void EnumWithNonUniqueEntriesTest()
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"" elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
<xs:simpleType name=""TestEnum"">
<xs:restriction base=""xs:string"">
<xs:enumeration value=""test_case""/>
<xs:enumeration value=""test_Case""/>
<xs:enumeration value=""Test_case""/>
<xs:enumeration value=""Test_Case""/>
</xs:restriction>
</xs:simpleType>
</xs:schema>";

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<XmlEnumAttribute>().FirstOrDefault();
Assert.NotNull(xmlEnumAttribute);
Assert.Equal("test_Case", xmlEnumAttribute.Name);
}

[Fact]
public void RenameInterfacePropertyInDerivedClassTest()
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema""
elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
<xs:complexType name=""ClassItemBase"">
<xs:sequence>
<xs:group ref=""Level1""/>
</xs:sequence>
</xs:complexType>
<xs:element name=""ClassItem"">
<xs:complexType>
<xs:complexContent>
<xs:extension base=""ClassItemBase""/>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name=""SomeType1"">
<xs:complexType>
<xs:group ref=""Level1""/>
</xs:complexType>
</xs:element>
<xs:group name=""Level1"">
<xs:choice>
<xs:group ref=""Level2""/>
</xs:choice>
</xs:group>
<xs:group name=""Level2"">
<xs:choice>
<xs:group ref=""Level3""/>
</xs:choice>
</xs:group>
<xs:group name=""Level3"">
<xs:choice>
<xs:element name=""ClassItemBase"" type=""xs:string""/>
</xs:choice>
</xs:group>
</xs:schema>";

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 = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema""
elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
<xs:element name=""ParentClass"">
<xs:complexType>
<xs:group ref=""Level1""/>
</xs:complexType>
</xs:element>
<xs:group name=""Level1"">
<xs:choice>
<xs:sequence>
<xs:element name=""InterfaceProperty"" type=""xs:string""/>
<xs:group ref=""Level2""/>
</xs:sequence>
</xs:choice>
</xs:group>
<xs:group name=""Level2"">
<xs:sequence>
<xs:element name=""InterfaceProperty"" type=""xs:string""/>
<xs:group ref=""Level3""/>
</xs:sequence>
</xs:group>
<xs:group name=""Level22"">
<xs:sequence>
<xs:element name=""InterfaceProperty"" type=""xs:string""/>
<xs:element name=""Level22OwnProperty"" type=""xs:string""/>
<xs:group ref=""Level3""/>
</xs:sequence>
</xs:group>
<xs:group name=""Level3"">
<xs:choice>
<xs:element name=""InterfaceProperty"" type=""xs:string""/>
</xs:choice>
</xs:group>
</xs:schema>";

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);

}
}
}
98 changes: 90 additions & 8 deletions XmlSchemaClassGenerator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,66 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set)
var elements = set.GlobalElements.Values.Cast<XmlSchemaElement>().Where(s => s.GetSchema() == schema);
CreateElements(elements);
}

if (configuration.GenerateInterfaces)
{
RenameInterfacePropertiesIfRenamedInDerivedClasses();
RemoveDuplicateInterfaceProperties();
}
}

private void RemoveDuplicateInterfaceProperties()
{
foreach (var interfaceModel in Types.Values.OfType<InterfaceModel>())
{
var parentProperties = interfaceModel.Properties.ToList();
foreach (var baseInterfaceType in interfaceModel.AllDerivedReferenceTypes().OfType<InterfaceModel>())
{
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<InterfaceModel>())
{
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<XmlSchema> dependencyOrder, HashSet<XmlSchema> seenSchemas)
Expand Down Expand Up @@ -217,7 +277,7 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaGroup group, NamespaceMod
interfaceModel.Properties.AddRange(properties);
var interfaces = items.Select(i => i.XmlParticle).OfType<XmlSchemaGroupRef>()
.Select(i => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(i.SourceUri), Groups[i.RefName], i.RefName));
interfaceModel.Interfaces.AddRange(interfaces);
interfaceModel.AddInterfaces(interfaces);

return interfaceModel;
}
Expand Down Expand Up @@ -249,7 +309,7 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaAttributeGroup attributeG
interfaceModel.Properties.AddRange(properties);
var interfaces = items.OfType<XmlSchemaAttributeGroupRef>()
.Select(a => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(a.SourceUri), AttributeGroups[a.RefName], a.RefName));
interfaceModel.Interfaces.AddRange(interfaces);
interfaceModel.AddInterfaces(interfaces);

return interfaceModel;
}
Expand Down Expand Up @@ -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<XmlSchemaGroupRef>()
.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)
{
Expand All @@ -344,7 +406,7 @@ private TypeModel CreateTypeModel(Uri source, XmlSchemaComplexType complexType,
{
var attributeInterfaces = attributes.OfType<XmlSchemaAttributeGroupRef>()
.Select(i => (InterfaceModel)CreateTypeModel(CodeUtilities.CreateUri(i.SourceUri), AttributeGroups[i.RefName], i.RefName));
classModel.Interfaces.AddRange(attributeInterfaces);
classModel.AddInterfaces(attributeInterfaces);
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -459,6 +522,23 @@ private TypeModel CreateTypeModel(XmlSchemaSimpleType simpleType, NamespaceModel
return simpleModel;
}

private static List<EnumValueModel> EnsureEnumValuesUnique(List<EnumValueModel> 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<PropertyModel> CreatePropertiesForAttributes(Uri source, TypeModel typeModel, IEnumerable<XmlSchemaObject> items)
{
var properties = new List<PropertyModel>();
Expand Down Expand Up @@ -579,6 +659,7 @@ private IEnumerable<PropertyModel> 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
Expand All @@ -589,6 +670,7 @@ private IEnumerable<PropertyModel> 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),
Expand Down
Loading

0 comments on commit 00c10ee

Please sign in to comment.