Skip to content

Commit

Permalink
Merge pull request #340 from jmatss/support-nested-choice-elements
Browse files Browse the repository at this point in the history
Add support for nested non-nullable choice members
  • Loading branch information
mganss authored Jun 16, 2022
2 parents 8204bee + dd4c63f commit 961b3a2
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
83 changes: 83 additions & 0 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,89 @@ public void ChoiceMembersAreNullable()
Assert.Contains("Opt4Specified", content);
}

[Fact]
public void NestedElementInChoiceIsNullable()
{
// Because nullability isn't directly exposed in the generated C#, we use "XXXSpecified" on a value type
// as a proxy.
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:element name=""Root"">
<xs:complexType>
<xs:choice>
<xs:sequence>
<xs:element name=""ElementA"" type=""xs:int""/>
</xs:sequence>
<xs:group ref=""Group""/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:group name=""Group"">
<xs:sequence>
<xs:element name=""ElementB"" type=""xs:int""/>
</xs:sequence>
</xs:group>
</xs:schema>";

var generator = new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test"
}
};
var contents = ConvertXml(nameof(NestedElementInChoiceIsNullable), xsd, generator);
var content = Assert.Single(contents);

Assert.Contains("ElementASpecified", content);
Assert.Contains("ElementBSpecified", content);
}

[Fact]
public void OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice()
{
// Because nullability isn't directly exposed in the generated C#, we use the "RequiredAttribute"
// as a proxy.
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:element name=""Root"">
<xs:complexType>
<xs:choice>
<xs:element name=""ElementWithChild"">
<xs:complexType>
<xs:sequence>
<xs:element name=""NestedChild"" type=""xs:int""/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>";

var generator = new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test"
}
};
var contents = ConvertXml(nameof(OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice), xsd, generator).ToArray();
var assembly = Compiler.Compile(nameof(OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice), contents);

var elementWithChildProperty = assembly.GetType("Test.Root")?.GetProperty("ElementWithChild");
var nestedChildProperty = assembly.GetType("Test.RootElementWithChild")?.GetProperty("NestedChild");
Assert.NotNull(elementWithChildProperty);
Assert.NotNull(nestedChildProperty);

Type requiredType = typeof(System.ComponentModel.DataAnnotations.RequiredAttribute);
bool elementWithChildIsRequired = Attribute.GetCustomAttribute(elementWithChildProperty, requiredType) != null;
bool nestedChildIsRequired = Attribute.GetCustomAttribute(nestedChildProperty, requiredType) != null;
Assert.False(elementWithChildIsRequired);
Assert.True(nestedChildIsRequired);
}

[Fact]
public void AssemblyVisibleIsInternalClass()
{
Expand Down
28 changes: 25 additions & 3 deletions XmlSchemaClassGenerator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,7 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
}

var effectiveElement = substitute?.Element ?? element;
var isNullableByChoice = IsNullableByChoice(item.XmlParent);
var propertyName = _configuration.NamingProvider.ElementNameFromQualifiedName(effectiveElement.QualifiedName, effectiveElement);
var originalPropertyName = propertyName;
if (propertyName == typeModel.Name)
Expand All @@ -934,9 +935,9 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
OriginalPropertyName = originalPropertyName,
Type = substitute?.Type ?? CreateTypeModel(element.ElementSchemaType, elementQualifiedName),
IsNillable = element.IsNillable,
IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice),
IsNullable = item.MinOccurs < 1.0m || isNullableByChoice,
IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx
DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && item.XmlParent is not XmlSchemaChoice) ? element.FixedValue : null),
DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && !isNullableByChoice) ? element.FixedValue : null),
FixedValue = element.FixedValue,
XmlNamespace = !string.IsNullOrEmpty(effectiveElement.QualifiedName.Namespace) && effectiveElement.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace
? effectiveElement.QualifiedName.Namespace : null,
Expand Down Expand Up @@ -969,7 +970,7 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
OwningType = typeModel,
Name = "Any",
Type = new SimpleModel(_configuration) { ValueType = (_configuration.UseXElementForAny ? typeof(XElement) : typeof(XmlElement)), UseDataTypeAttribute = false },
IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice),
IsNullable = item.MinOccurs < 1.0m || IsNullableByChoice(item.XmlParent),
IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx
IsAny = true,
XmlParticle = item.XmlParticle,
Expand Down Expand Up @@ -1020,6 +1021,27 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
return properties;
}

private static bool IsNullableByChoice(XmlSchemaObject parent)
{
while (parent != null)
{
switch (parent)
{
case XmlSchemaChoice:
return true;
// Any ancestor element between the current item and the
// choice would already have been forced to nullable.
case XmlSchemaElement:
case XmlSchemaParticle p when p.MinOccurs < 1.0m:
return false;
default:
break;
}
parent = parent.Parent;
}
return false;
}

private NamespaceModel CreateNamespaceModel(Uri source, XmlQualifiedName qualifiedName)
{
NamespaceModel namespaceModel = null;
Expand Down

0 comments on commit 961b3a2

Please sign in to comment.