Skip to content

Commit 2ba5cfb

Browse files
committed
Fix default handling for optional elements with simple content (fixes #645)
1 parent 8021cc9 commit 2ba5cfb

5 files changed

Lines changed: 104 additions & 3 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.IO;
2+
using System.Xml.Serialization;
3+
using Xunit;
4+
5+
namespace XmlSchemaClassGenerator.Tests;
6+
7+
public class ElementTextDefaultTests
8+
{
9+
///
10+
/// The default of an optional element with simple content applies to the text value of the element,
11+
/// so the element must stay null when absent and get the default only when present but empty.
12+
///
13+
[Fact]
14+
public void OptionalElementDefaultIsAppliedToTextValue()
15+
{
16+
var assembly = Compiler.Generate(nameof(OptionalElementDefaultIsAppliedToTextValue), "xsd/ElementTextDefault/*.xsd", new Generator
17+
{
18+
NamespaceProvider = new NamespaceProvider { GenerateNamespace = _ => "Test" }
19+
});
20+
21+
var geometryType = assembly.GetType("Test.Geometry");
22+
Assert.NotNull(geometryType);
23+
var serializer = new XmlSerializer(geometryType);
24+
25+
var geometry = System.Activator.CreateInstance(geometryType);
26+
Assert.Null(geometryType.GetProperty("ThicknessReduction").GetValue(geometry));
27+
28+
const string absent = "";
29+
var absentObject = serializer.Deserialize(new StringReader(absent));
30+
Assert.Null(geometryType.GetProperty("ThicknessReduction").GetValue(absentObject));
31+
32+
var writer = new StringWriter();
33+
serializer.Serialize(writer, absentObject);
34+
Assert.DoesNotContain("thicknessReduction", writer.ToString());
35+
36+
const string empty = "\"Bottom\" />";
37+
var emptyObject = serializer.Deserialize(new StringReader(empty));
38+
var reduction = geometryType.GetProperty("ThicknessReduction").GetValue(emptyObject);
39+
Assert.NotNull(reduction);
40+
Assert.Equal(false, reduction.GetType().GetProperty("Value").GetValue(reduction));
41+
Assert.Equal("Bottom", reduction.GetType().GetProperty("Reference").GetValue(reduction).ToString());
42+
43+
const string explicitValue = "\"Top\">true";
44+
var explicitObject = serializer.Deserialize(new StringReader(explicitValue));
45+
reduction = geometryType.GetProperty("ThicknessReduction").GetValue(explicitObject);
46+
Assert.Equal(true, reduction.GetType().GetProperty("Value").GetValue(reduction));
47+
}
48+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
xml version="1.0" encoding="utf-8"?>
2+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
3+
<xs:simpleType name="ReferenceType">
4+
<xs:restriction base="xs:string">
5+
<xs:enumeration value="Top"/>
6+
<xs:enumeration value="Bottom"/>
7+
xs:restriction>
8+
xs:simpleType>
9+
<xs:element name="geometry">
10+
<xs:complexType>
11+
<xs:sequence>
12+
<xs:element name="thicknessReduction" minOccurs="0" default="false">
13+
<xs:complexType>
14+
<xs:simpleContent>
15+
<xs:extension base="xs:boolean">
16+
<xs:attribute name="reference" type="ReferenceType" use="required"/>
17+
xs:extension>
18+
xs:simpleContent>
19+
xs:complexType>
20+
xs:element>
21+
xs:sequence>
22+
xs:complexType>
23+
xs:element>
24+
xs:schema>

‎XmlSchemaClassGenerator/ModelBuilder.cs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,16 @@ private PropertyModel PropertyFromElement(TypeModel owningTypeModel, XmlSchemaEl
11861186
property.SetFromNode(originalName, isRequired, element);
11871187
property.SetSchemaNameAndNamespace(owningTypeModel, effectiveElement);
11881188

1189+
// The default of an optional element with simple content applies to its text value, not to the element:
1190+
// move it there so the element stays null when absent. Only possible if the anonymous type belongs to this element alone.
1191+
if (!isRequired && substitute == null && property.DefaultValue != null
1192+
&& typeModel is ClassModel { IsAnonymous: true, IsMixed: false, DerivedTypes.Count: 0, BaseClass: not null and not ClassModel } textClass
1193+
&& !string.IsNullOrEmpty(_configuration.TextValuePropertyName))
1194+
{
1195+
textClass.TextValueDefault = property.DefaultValue;
1196+
property.SetDefaultValue(null);
1197+
}
1198+
11891199
if (property.IsArray && !_configuration.GenerateComplexTypesForCollections)
11901200
property.Type.Namespace.Types.Remove(property.Type.Name);
11911201

‎XmlSchemaClassGenerator/Models/ClassModel.cs‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ public class ClassModel(GeneratorConfiguration configuration) : ReferenceTypeMod
1818
public bool IsSubstitution { get; set; }
1919
public TypeModel BaseClass { get; set; }
2020
public TypeModel TextValueType { get; set; }
21+
22+
///
23+
/// Default for the text value of a simple-content class, taken from the default of the
24+
/// (only) element that uses this anonymous type.
25+
///
26+
public string TextValueDefault { get; set; }
2127
public List<ClassModel> DerivedTypes { get; set; } = [];
2228
public override bool IsSubtype => BaseClass != null;
2329

@@ -199,12 +205,15 @@ public override CodeTypeDeclaration Generate()
199205
var enableDataBinding = Configuration.EnableDataBinding;
200206
var typeReference = BaseClass.GetReferenceFor(Namespace);
201207

208+
var textDefaultExpression = TextValueDefault != null ? BaseClass.GetDefaultValueFor(TextValueDefault, false) : null;
209+
202210
CodeMemberField backingFieldMember = null;
203-
if (enableDataBinding)
211+
if (enableDataBinding || textDefaultExpression != null)
204212
{
205213
backingFieldMember = new CodeMemberField(typeReference, textName.ToBackingField(Configuration.PrivateMemberPrefix))
206214
{
207-
Attributes = MemberAttributes.Private
215+
Attributes = MemberAttributes.Private,
216+
InitExpression = textDefaultExpression
208217
};
209218
classDeclaration.Members.Add(backingFieldMember);
210219
}
@@ -222,6 +231,14 @@ public override CodeTypeDeclaration Generate()
222231
var attribute = AttributeDecl<XmlTextAttribute>();
223232
var valuePropertyModel = new PropertyModel(Configuration, textName, BaseClass, this);
224233

234+
if (textDefaultExpression != null)
235+
{
236+
valuePropertyModel.SetDefaultValue(TextValueDefault);
237+
238+
if (textDefaultExpression is CodePrimitiveExpression or CodeFieldReferenceExpression)
239+
text.CustomAttributes.Add(valuePropertyModel.CreateDefaultValueAttribute(typeReference, textDefaultExpression));
240+
}
241+
225242
if (BaseClass is SimpleModel simpleModel)
226243
{
227244
docs.AddRange(simpleModel.Restrictions.Select(r => new DocumentationModel { Language = English, Text = r.Description }));

‎XmlSchemaClassGenerator/Models/PropertyModel.cs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ private void AddDocs(CodeTypeMember member)
178178
internal bool HasCollectionItemStringLengthAttribute
179179
=> IsEnumerable && PropertyType is SimpleModel simpleType && simpleType.GetCollectionItemStringLengthAttribute() != null;
180180

181-
private CodeAttributeDeclaration CreateDefaultValueAttribute(CodeTypeReference typeReference, CodeExpression defaultValueExpression)
181+
internal void SetDefaultValue(string defaultValue) => DefaultValue = defaultValue;
182+
183+
internal CodeAttributeDeclaration CreateDefaultValueAttribute(CodeTypeReference typeReference, CodeExpression defaultValueExpression)
182184
{
183185
var defaultValueAttribute = AttributeDecl<DefaultValueAttribute>();
184186

0 commit comments

Comments
 (0)