Skip to content

Commit 961b3a2

Browse files
authored
Merge pull request #340 from jmatss/support-nested-choice-elements
Add support for nested non-nullable choice members
2 parents 8204bee + dd4c63f commit 961b3a2

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

XmlSchemaClassGenerator.Tests/XmlTests.cs

+83
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,89 @@ public void ChoiceMembersAreNullable()
11001100
Assert.Contains("Opt4Specified", content);
11011101
}
11021102

1103+
[Fact]
1104+
public void NestedElementInChoiceIsNullable()
1105+
{
1106+
// Because nullability isn't directly exposed in the generated C#, we use "XXXSpecified" on a value type
1107+
// as a proxy.
1108+
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
1109+
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
1110+
<xs:element name=""Root"">
1111+
<xs:complexType>
1112+
<xs:choice>
1113+
<xs:sequence>
1114+
<xs:element name=""ElementA"" type=""xs:int""/>
1115+
</xs:sequence>
1116+
<xs:group ref=""Group""/>
1117+
</xs:choice>
1118+
</xs:complexType>
1119+
</xs:element>
1120+
1121+
<xs:group name=""Group"">
1122+
<xs:sequence>
1123+
<xs:element name=""ElementB"" type=""xs:int""/>
1124+
</xs:sequence>
1125+
</xs:group>
1126+
</xs:schema>";
1127+
1128+
var generator = new Generator
1129+
{
1130+
NamespaceProvider = new NamespaceProvider
1131+
{
1132+
GenerateNamespace = key => "Test"
1133+
}
1134+
};
1135+
var contents = ConvertXml(nameof(NestedElementInChoiceIsNullable), xsd, generator);
1136+
var content = Assert.Single(contents);
1137+
1138+
Assert.Contains("ElementASpecified", content);
1139+
Assert.Contains("ElementBSpecified", content);
1140+
}
1141+
1142+
[Fact]
1143+
public void OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice()
1144+
{
1145+
// Because nullability isn't directly exposed in the generated C#, we use the "RequiredAttribute"
1146+
// as a proxy.
1147+
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
1148+
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
1149+
<xs:element name=""Root"">
1150+
<xs:complexType>
1151+
<xs:choice>
1152+
<xs:element name=""ElementWithChild"">
1153+
<xs:complexType>
1154+
<xs:sequence>
1155+
<xs:element name=""NestedChild"" type=""xs:int""/>
1156+
</xs:sequence>
1157+
</xs:complexType>
1158+
</xs:element>
1159+
</xs:choice>
1160+
</xs:complexType>
1161+
</xs:element>
1162+
</xs:schema>";
1163+
1164+
var generator = new Generator
1165+
{
1166+
NamespaceProvider = new NamespaceProvider
1167+
{
1168+
GenerateNamespace = key => "Test"
1169+
}
1170+
};
1171+
var contents = ConvertXml(nameof(OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice), xsd, generator).ToArray();
1172+
var assembly = Compiler.Compile(nameof(OnlyFirstElementOfNestedElementsIsForcedToNullableInChoice), contents);
1173+
1174+
var elementWithChildProperty = assembly.GetType("Test.Root")?.GetProperty("ElementWithChild");
1175+
var nestedChildProperty = assembly.GetType("Test.RootElementWithChild")?.GetProperty("NestedChild");
1176+
Assert.NotNull(elementWithChildProperty);
1177+
Assert.NotNull(nestedChildProperty);
1178+
1179+
Type requiredType = typeof(System.ComponentModel.DataAnnotations.RequiredAttribute);
1180+
bool elementWithChildIsRequired = Attribute.GetCustomAttribute(elementWithChildProperty, requiredType) != null;
1181+
bool nestedChildIsRequired = Attribute.GetCustomAttribute(nestedChildProperty, requiredType) != null;
1182+
Assert.False(elementWithChildIsRequired);
1183+
Assert.True(nestedChildIsRequired);
1184+
}
1185+
11031186
[Fact]
11041187
public void AssemblyVisibleIsInternalClass()
11051188
{

XmlSchemaClassGenerator/ModelBuilder.cs

+25-3
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
917917
}
918918

919919
var effectiveElement = substitute?.Element ?? element;
920+
var isNullableByChoice = IsNullableByChoice(item.XmlParent);
920921
var propertyName = _configuration.NamingProvider.ElementNameFromQualifiedName(effectiveElement.QualifiedName, effectiveElement);
921922
var originalPropertyName = propertyName;
922923
if (propertyName == typeModel.Name)
@@ -934,9 +935,9 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
934935
OriginalPropertyName = originalPropertyName,
935936
Type = substitute?.Type ?? CreateTypeModel(element.ElementSchemaType, elementQualifiedName),
936937
IsNillable = element.IsNillable,
937-
IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice),
938+
IsNullable = item.MinOccurs < 1.0m || isNullableByChoice,
938939
IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx
939-
DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && item.XmlParent is not XmlSchemaChoice) ? element.FixedValue : null),
940+
DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && !isNullableByChoice) ? element.FixedValue : null),
940941
FixedValue = element.FixedValue,
941942
XmlNamespace = !string.IsNullOrEmpty(effectiveElement.QualifiedName.Namespace) && effectiveElement.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace
942943
? effectiveElement.QualifiedName.Namespace : null,
@@ -969,7 +970,7 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
969970
OwningType = typeModel,
970971
Name = "Any",
971972
Type = new SimpleModel(_configuration) { ValueType = (_configuration.UseXElementForAny ? typeof(XElement) : typeof(XmlElement)), UseDataTypeAttribute = false },
972-
IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice),
973+
IsNullable = item.MinOccurs < 1.0m || IsNullableByChoice(item.XmlParent),
973974
IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx
974975
IsAny = true,
975976
XmlParticle = item.XmlParticle,
@@ -1020,6 +1021,27 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
10201021
return properties;
10211022
}
10221023

1024+
private static bool IsNullableByChoice(XmlSchemaObject parent)
1025+
{
1026+
while (parent != null)
1027+
{
1028+
switch (parent)
1029+
{
1030+
case XmlSchemaChoice:
1031+
return true;
1032+
// Any ancestor element between the current item and the
1033+
// choice would already have been forced to nullable.
1034+
case XmlSchemaElement:
1035+
case XmlSchemaParticle p when p.MinOccurs < 1.0m:
1036+
return false;
1037+
default:
1038+
break;
1039+
}
1040+
parent = parent.Parent;
1041+
}
1042+
return false;
1043+
}
1044+
10231045
private NamespaceModel CreateNamespaceModel(Uri source, XmlQualifiedName qualifiedName)
10241046
{
10251047
NamespaceModel namespaceModel = null;

0 commit comments

Comments
 (0)