Skip to content

Commit 3c93a82

Browse files
Copilotmganss
andcommitted
Add EnumValue adapter property for simpleContent enum restrictions
Added an adapter property as suggested in #562 that provides strongly-typed access to enum values while keeping the string Value property for XML serialization. The EnumValue property: - Is marked with [XmlIgnore] to prevent serialization - Returns nullable enum type - Uses Enum.TryParse in getter to convert from string Value - Updates Value in setter by calling ToString() on the enum Example generated code: ```csharp [XmlIgnore] public TransConfirmationCodeTypeEnum? EnumValue { get { if (Enum.TryParse(typeof(TransConfirmationCodeTypeEnum), Value, true, out var result)) return (TransConfirmationCodeTypeEnum)result; return null; } set { Value = value?.ToString(); } } ``` Co-authored-by: mganss <976344+mganss@users.noreply.github.com>
1 parent 1d78055 commit 3c93a82

2 files changed

Lines changed: 118 additions & 13 deletions

File tree

XmlSchemaClassGenerator.Tests/XmlTests.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,36 @@ public void TestSimpleContentEnum()
245245
var baseType = assembly.GetType($"{ns}.CodeType");
246246
Assert.Equal(baseType, type.BaseType);
247247

248-
// The derived class should NOT have its own Value property
249-
// It inherits the string Value property from the base class
250-
// Users can manually convert between string and enum as needed
248+
// The derived class inherits the string Value property from the base class
251249
var valueProperty = type.GetProperty("Value");
252250
Assert.NotNull(valueProperty);
253251
Assert.Equal(typeof(string), valueProperty.PropertyType);
252+
253+
// The derived class should have an EnumValue adapter property
254+
var enumValueProperty = type.GetProperty("EnumValue");
255+
Assert.NotNull(enumValueProperty);
256+
Assert.Equal(typeof(Nullable<>).MakeGenericType(enumType), enumValueProperty.PropertyType);
257+
258+
// Test that the EnumValue property works correctly
259+
var instance = Activator.CreateInstance(type);
260+
Assert.NotNull(instance);
261+
262+
// Set Value to a string and verify EnumValue returns the correct enum
263+
valueProperty.SetValue(instance, "Always");
264+
var enumValue = enumValueProperty.GetValue(instance);
265+
Assert.NotNull(enumValue);
266+
Assert.Equal("Always", enumValue.ToString());
267+
268+
// Set EnumValue and verify Value is updated
269+
var alwaysValue = Enum.Parse(enumType, "Never");
270+
enumValueProperty.SetValue(instance, alwaysValue);
271+
var stringValue = valueProperty.GetValue(instance);
272+
Assert.Equal("Never", stringValue);
273+
274+
// Set EnumValue to null and verify Value is null
275+
enumValueProperty.SetValue(instance, null);
276+
stringValue = valueProperty.GetValue(instance);
277+
Assert.Null(stringValue);
254278
}
255279

256280
[Fact, TestPriority(1)]

XmlSchemaClassGenerator/Models/ClassModel.cs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,97 @@ public override CodeTypeDeclaration Generate()
9898
classDeclaration.BaseTypes.Add(BaseClass.GetReferenceFor(Namespace));
9999

100100
// When a derived class has a simpleContent restriction with enum facets (TextValueType != null),
101-
// we generate the enum type but do NOT add a new Value property in the derived class.
102-
// This is because the C# XmlSerializer has limitations when dealing with simpleContent
103-
// restrictions in inheritance hierarchies - it cannot handle a derived class that adds
104-
// a new XmlText property when the base class already has one.
105-
//
106-
// The enum type is still generated (in ModelBuilder) and can be used for validation/conversion manually:
107-
// e.g., var enumValue = (MyEnum)Enum.Parse(typeof(MyEnum), instance.Value);
108-
//
109-
// This is a compromise to work within XmlSerializer's constraints while still providing
110-
// the enum type that users requested in issue #561.
101+
// we generate an adapter property that allows strongly-typed access to the enum values.
102+
// We cannot add a new XmlText property because the XmlSerializer doesn't allow it when
103+
// the base class already has one. Instead, we use an [XmlIgnore] adapter property.
104+
if (TextValueType != null && !string.IsNullOrEmpty(Configuration.TextValuePropertyName))
105+
{
106+
var textName = Configuration.TextValuePropertyName;
107+
var enumTypeReference = TextValueType.GetReferenceFor(Namespace);
108+
var nullableEnumTypeReference = new CodeTypeReference(typeof(Nullable<>));
109+
nullableEnumTypeReference.TypeArguments.Add(enumTypeReference);
110+
111+
// Create the EnumValue adapter property
112+
var enumValueProperty = new CodeMemberProperty
113+
{
114+
Name = "EnumValue",
115+
Type = nullableEnumTypeReference,
116+
Attributes = MemberAttributes.Public,
117+
HasGet = true,
118+
HasSet = true
119+
};
120+
121+
// Add [XmlIgnore] attribute
122+
var ignoreAttribute = AttributeDecl<XmlIgnoreAttribute>();
123+
enumValueProperty.CustomAttributes.Add(ignoreAttribute);
124+
125+
// Getter: Try to parse the Value property to enum
126+
// if (Enum.TryParse(typeof(EnumType), Value, true, out var result))
127+
// return (EnumType)result;
128+
// return null;
129+
var resultVariable = new CodeVariableDeclarationStatement(typeof(object), "result");
130+
var tryParseCondition = new CodeMethodInvokeExpression(
131+
new CodeTypeReferenceExpression(typeof(Enum)),
132+
"TryParse",
133+
new CodeTypeOfExpression(enumTypeReference),
134+
new CodePropertyReferenceExpression(
135+
new CodeThisReferenceExpression(),
136+
textName),
137+
new CodePrimitiveExpression(true),
138+
new CodeDirectionExpression(FieldDirection.Out, new CodeVariableReferenceExpression("result")));
139+
140+
var returnCastResult = new CodeMethodReturnStatement(
141+
new CodeCastExpression(
142+
nullableEnumTypeReference,
143+
new CodeVariableReferenceExpression("result")));
144+
145+
var ifTryParse = new CodeConditionStatement(
146+
tryParseCondition,
147+
returnCastResult);
148+
149+
enumValueProperty.GetStatements.Add(resultVariable);
150+
enumValueProperty.GetStatements.Add(ifTryParse);
151+
enumValueProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(null)));
152+
153+
// Setter: Value = value?.ToString();
154+
// Since CodeDOM doesn't support null-conditional operator, we need to check and set
155+
var valueNotNull = new CodeBinaryOperatorExpression(
156+
new CodePropertySetValueReferenceExpression(),
157+
CodeBinaryOperatorType.IdentityInequality,
158+
new CodePrimitiveExpression(null));
159+
160+
var setToString = new CodeAssignStatement(
161+
new CodePropertyReferenceExpression(
162+
new CodeThisReferenceExpression(),
163+
textName),
164+
new CodeMethodInvokeExpression(
165+
new CodePropertySetValueReferenceExpression(),
166+
"ToString"));
167+
168+
var setToNull = new CodeAssignStatement(
169+
new CodePropertyReferenceExpression(
170+
new CodeThisReferenceExpression(),
171+
textName),
172+
new CodePrimitiveExpression(null));
173+
174+
enumValueProperty.SetStatements.Add(
175+
new CodeConditionStatement(
176+
valueNotNull,
177+
new CodeStatement[] { setToString },
178+
new CodeStatement[] { setToNull }));
179+
180+
var docs = new List<DocumentationModel> {
181+
new() { Language = English, Text = "Gets or sets the typed value of the text content." },
182+
new() { Language = German, Text = "Ruft den typisierten Wert des Textinhalts ab oder legt diesen fest." }
183+
};
184+
185+
enumValueProperty.Comments.AddRange(GetComments(docs).ToArray());
186+
187+
classDeclaration.Members.Add(enumValueProperty);
188+
189+
var enumValuePropertyModel = new PropertyModel(Configuration, "EnumValue", TextValueType, this);
190+
Configuration.MemberVisitor(enumValueProperty, enumValuePropertyModel);
191+
}
111192
}
112193
else if (!string.IsNullOrEmpty(Configuration.TextValuePropertyName))
113194
{

0 commit comments

Comments
 (0)