From 0823f9a70da21ceb258426eac07348078958e9ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:18:05 +0000 Subject: [PATCH 1/6] Initial plan From 7cb7392be1e0607830553f1524243fc67b62e34c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 17:07:21 +0000 Subject: [PATCH 2/6] Handle enum in simpleContent complex types Co-authored-by: mganss <976344+mganss@users.noreply.github.com> --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 93 ++++++++++++------- .../xsd/simple/simplecontent-enum.xsd | 22 +++++ XmlSchemaClassGenerator/ModelBuilder.cs | 52 +++++++++++ XmlSchemaClassGenerator/Models/ClassModel.cs | 41 ++++++++ 4 files changed, 174 insertions(+), 34 deletions(-) create mode 100644 XmlSchemaClassGenerator.Tests/xsd/simple/simplecontent-enum.xsd diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 279b9631..4660bf32 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -186,40 +186,65 @@ public void TestGuid() [Fact, TestPriority(1)] [UseCulture("en-US")] - public void TestUnion() - { - var assembly = Compiler.Generate("Union", UnionPattern, new Generator - { - NamespacePrefix = "Union", - IntegerDataType = typeof(int), - MapUnionToWidestCommonType = true - }); - - Assert.NotNull(assembly); - - SharedTestFunctions.TestSamples(Output, "Union", UnionPattern); - - var snapshotType = assembly.GetType("Union.Snapshot"); - Assert.NotNull(snapshotType); - - var date = snapshotType.GetProperty("Date"); - Assert.NotNull(date); - Assert.Equal(typeof(DateTime), date.PropertyType); - - var count = snapshotType.GetProperty("Count"); - Assert.NotNull(count); - Assert.Equal(typeof(int), count.PropertyType); - - var num = snapshotType.GetProperty("Num"); - Assert.NotNull(num); - Assert.Equal(typeof(decimal), num.PropertyType); - } - - [Fact, TestPriority(1)] - [UseCulture("en-US")] - public void TestList() - { - Compiler.Generate("List", ListPattern); + public void TestUnion() + { + var assembly = Compiler.Generate("Union", UnionPattern, new Generator + { + NamespacePrefix = "Union", + IntegerDataType = typeof(int), + MapUnionToWidestCommonType = true + }); + + Assert.NotNull(assembly); + + SharedTestFunctions.TestSamples(Output, "Union", UnionPattern); + + var snapshotType = assembly.GetType("Union.Snapshot"); + Assert.NotNull(snapshotType); + + var date = snapshotType.GetProperty("Date"); + Assert.NotNull(date); + Assert.Equal(typeof(DateTime), date.PropertyType); + + var count = snapshotType.GetProperty("Count"); + Assert.NotNull(count); + Assert.Equal(typeof(int), count.PropertyType); + + var num = snapshotType.GetProperty("Num"); + Assert.NotNull(num); + Assert.Equal(typeof(decimal), num.PropertyType); + } + + [Fact, TestPriority(1)] + [UseCulture("en-US")] + public void TestSimpleContentEnum() + { + var assembly = Compiler.Generate("SimpleContentEnum", "xsd/simple/simplecontent-enum.xsd"); + + const string ns = "SimpleContentEnum.Simplecontent"; + + var enumType = assembly.GetType($"{ns}.TransConfirmationCodeTypeEnum"); + if (enumType == null) + { + var names = string.Join(", ", assembly.GetTypes().Select(t => t.FullName)); + Assert.Fail($"Enum type not found. Available types: {names}"); + } + + var type = assembly.GetType($"{ns}.TransConfirmationCodeType"); + Assert.NotNull(type); + + var baseType = assembly.GetType($"{ns}.CodeType"); + Assert.Equal(baseType, type.BaseType); + + var valueProperty = type.GetProperties().Single(p => p.PropertyType == enumType); + Assert.Equal("Value", valueProperty.Name); + } + + [Fact, TestPriority(1)] + [UseCulture("en-US")] + public void TestList() + { + Compiler.Generate("List", ListPattern); SharedTestFunctions.TestSamples(Output, "List", ListPattern); } diff --git a/XmlSchemaClassGenerator.Tests/xsd/simple/simplecontent-enum.xsd b/XmlSchemaClassGenerator.Tests/xsd/simple/simplecontent-enum.xsd new file mode 100644 index 00000000..4e62daf7 --- /dev/null +++ b/XmlSchemaClassGenerator.Tests/xsd/simple/simplecontent-enum.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index 34c35477..d995c01a 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -551,6 +551,15 @@ private TypeModel CreateTypeModel(XmlSchemaComplexType complexType) } } + if (complexType.ContentModel?.Content is XmlSchemaSimpleContentRestriction simpleContentRestriction) + { + var enumFacets = simpleContentRestriction.Facets?.OfType().ToList(); + if (enumFacets?.Count > 0 && !_configuration.EnumAsString) + { + classModel.TextValueType = CreateSimpleContentEnumModel(classModel, enumFacets); + } + } + XmlSchemaParticle xmlParticle = null; if (classModel.BaseClass != null) { @@ -723,6 +732,49 @@ private static List EnsureEnumValuesUnique(List return enumModelValues; } + private EnumModel CreateSimpleContentEnumModel(ClassModel classModel, List enumFacets) + { + var enumNamespace = namespaceModel?.Key.XmlSchemaNamespace ?? qualifiedName.Namespace; + var enumQualifiedName = qualifiedName.IsEmpty + ? new XmlQualifiedName($"{classModel.Name}Enum", enumNamespace) + : new XmlQualifiedName($"{qualifiedName.Name}Enum", enumNamespace); + + var enumName = $"{classModel.Name}Enum"; + if (namespaceModel != null) + enumName = namespaceModel.GetUniqueTypeName(enumName); + + var enumModel = new EnumModel(_configuration) + { + Name = enumName, + Namespace = namespaceModel, + XmlSchemaName = enumQualifiedName, + IsAnonymous = false, + }; + + foreach (var facet in enumFacets.DistinctBy(f => f.Value)) + { + var value = new EnumValueModel + { + Name = _configuration.NamingProvider.EnumMemberNameFromValue(enumModel.Name, facet.Value, facet), + Value = facet.Value + }; + + var valueDocs = GetDocumentation(facet); + value.Documentation.AddRange(valueDocs); + + value.IsDeprecated = facet.Annotation?.Items.OfType() + .Any(a => Array.Exists(a.Markup, m => m.Name == "annox:annotate" && m.HasChildNodes && m.FirstChild.Name == "jl:Deprecated")) == true; + + enumModel.Values.Add(value); + } + + enumModel.Values = EnsureEnumValuesUnique(enumModel.Values); + if (namespaceModel != null) + namespaceModel.Types[enumModel.Name] = enumModel; + + return enumModel; + } + private EnumModel CreateEnumModel(XmlSchemaSimpleType simpleType, List enumFacets) { // we got an enum diff --git a/XmlSchemaClassGenerator/Models/ClassModel.cs b/XmlSchemaClassGenerator/Models/ClassModel.cs index 5695082d..801c93dd 100644 --- a/XmlSchemaClassGenerator/Models/ClassModel.cs +++ b/XmlSchemaClassGenerator/Models/ClassModel.cs @@ -17,6 +17,7 @@ public class ClassModel(GeneratorConfiguration configuration) : ReferenceTypeMod public bool IsMixed { get; set; } public bool IsSubstitution { get; set; } public TypeModel BaseClass { get; set; } + public TypeModel TextValueType { get; set; } public List DerivedTypes { get; set; } = []; public override bool IsSubtype => BaseClass != null; @@ -95,6 +96,46 @@ public override CodeTypeDeclaration Generate() if (BaseClass is ClassModel) { classDeclaration.BaseTypes.Add(BaseClass.GetReferenceFor(Namespace)); + + if (TextValueType != null && !string.IsNullOrEmpty(Configuration.TextValuePropertyName)) + { + var textName = Configuration.TextValuePropertyName; + var enableDataBinding = Configuration.EnableDataBinding; + var typeReference = TextValueType.GetReferenceFor(Namespace); + + CodeMemberField backingFieldMember = null; + if (enableDataBinding) + { + backingFieldMember = new CodeMemberField(typeReference, textName.ToBackingField(Configuration.PrivateMemberPrefix)) + { + Attributes = MemberAttributes.Private + }; + classDeclaration.Members.Add(backingFieldMember); + } + + CodeMemberField text = new(typeReference, textName + PropertyModel.GetAccessors(backingFieldMember, enableDataBinding, TextValueType.GetPropertyValueTypeCode())) + { + Attributes = MemberAttributes.Public | MemberAttributes.New, + }; + + var docs = new List { + new() { Language = English, Text = "Gets or sets the text value." }, + new() { Language = German, Text = "Ruft den Text ab oder legt diesen fest." } + }; + + docs.AddRange(TextValueType.Documentation); + + var attribute = AttributeDecl(); + + text.Comments.AddRange(GetComments(docs).ToArray()); + + text.CustomAttributes.Add(attribute); + classDeclaration.Members.Add(text); + + var valuePropertyModel = new PropertyModel(Configuration, textName, TextValueType, this); + + Configuration.MemberVisitor(text, valuePropertyModel); + } } else if (!string.IsNullOrEmpty(Configuration.TextValuePropertyName)) { From 9212607a4119dbf4a80b0da31a11224a36f51c6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 13:13:01 +0000 Subject: [PATCH 3/6] Initial plan From ccfd47ef0ee9d7e5432c24254e8668148181a1da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 13:31:42 +0000 Subject: [PATCH 4/6] Fix PR #577: Generate enum types for simpleContent restrictions Fixed the issue where generating enums for simpleContent complex types with restrictions broke existing tests. The C# XmlSerializer has limitations that prevent derived classes from adding new XmlText properties when the base class already has one. Solution: Generate the enum type for simpleContent restrictions, but don't add a new Value property in the derived class. Users can manually convert between the inherited string Value and the enum type. - Updated ClassModel.cs to not generate Value property in derived classes with simpleContent enum restrictions - Updated test to verify enum generation without expecting a strongly-typed Value property - All originally failing tests now pass (TestSimple, TestEmptyKeyProviderGeneratorPrefix, AirspaceServicesTest1) Co-authored-by: mganss <976344+mganss@users.noreply.github.com> --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 59 ++++++++++++-------- XmlSchemaClassGenerator/Models/ClassModel.cs | 47 ++++------------ 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 4660bf32..a423223a 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -215,29 +215,42 @@ public void TestUnion() Assert.Equal(typeof(decimal), num.PropertyType); } - [Fact, TestPriority(1)] - [UseCulture("en-US")] - public void TestSimpleContentEnum() - { - var assembly = Compiler.Generate("SimpleContentEnum", "xsd/simple/simplecontent-enum.xsd"); - - const string ns = "SimpleContentEnum.Simplecontent"; - - var enumType = assembly.GetType($"{ns}.TransConfirmationCodeTypeEnum"); - if (enumType == null) - { - var names = string.Join(", ", assembly.GetTypes().Select(t => t.FullName)); - Assert.Fail($"Enum type not found. Available types: {names}"); - } - - var type = assembly.GetType($"{ns}.TransConfirmationCodeType"); - Assert.NotNull(type); - - var baseType = assembly.GetType($"{ns}.CodeType"); - Assert.Equal(baseType, type.BaseType); - - var valueProperty = type.GetProperties().Single(p => p.PropertyType == enumType); - Assert.Equal("Value", valueProperty.Name); + [Fact, TestPriority(1)] + [UseCulture("en-US")] + public void TestSimpleContentEnum() + { + var assembly = Compiler.Generate("SimpleContentEnum", "xsd/simple/simplecontent-enum.xsd"); + + const string ns = "SimpleContentEnum.Simplecontent"; + + // The enum type should be generated + var enumType = assembly.GetType($"{ns}.TransConfirmationCodeTypeEnum"); + if (enumType == null) + { + var names = string.Join(", ", assembly.GetTypes().Select(t => t.FullName)); + Assert.Fail($"Enum type not found. Available types: {names}"); + } + + // Verify it's an enum with the expected values + Assert.True(enumType.IsEnum); + var enumValues = Enum.GetNames(enumType); + Assert.Contains("Always", enumValues); + Assert.Contains("Never", enumValues); + Assert.Contains("OnError", enumValues); + + // The derived class should exist and inherit from the base + var type = assembly.GetType($"{ns}.TransConfirmationCodeType"); + Assert.NotNull(type); + + var baseType = assembly.GetType($"{ns}.CodeType"); + Assert.Equal(baseType, type.BaseType); + + // The derived class should NOT have its own Value property + // It inherits the string Value property from the base class + // Users can manually convert between string and enum as needed + var valueProperty = type.GetProperty("Value"); + Assert.NotNull(valueProperty); + Assert.Equal(typeof(string), valueProperty.PropertyType); } [Fact, TestPriority(1)] diff --git a/XmlSchemaClassGenerator/Models/ClassModel.cs b/XmlSchemaClassGenerator/Models/ClassModel.cs index 801c93dd..5ba69090 100644 --- a/XmlSchemaClassGenerator/Models/ClassModel.cs +++ b/XmlSchemaClassGenerator/Models/ClassModel.cs @@ -99,42 +99,17 @@ public override CodeTypeDeclaration Generate() if (TextValueType != null && !string.IsNullOrEmpty(Configuration.TextValuePropertyName)) { - var textName = Configuration.TextValuePropertyName; - var enableDataBinding = Configuration.EnableDataBinding; - var typeReference = TextValueType.GetReferenceFor(Namespace); - - CodeMemberField backingFieldMember = null; - if (enableDataBinding) - { - backingFieldMember = new CodeMemberField(typeReference, textName.ToBackingField(Configuration.PrivateMemberPrefix)) - { - Attributes = MemberAttributes.Private - }; - classDeclaration.Members.Add(backingFieldMember); - } - - CodeMemberField text = new(typeReference, textName + PropertyModel.GetAccessors(backingFieldMember, enableDataBinding, TextValueType.GetPropertyValueTypeCode())) - { - Attributes = MemberAttributes.Public | MemberAttributes.New, - }; - - var docs = new List { - new() { Language = English, Text = "Gets or sets the text value." }, - new() { Language = German, Text = "Ruft den Text ab oder legt diesen fest." } - }; - - docs.AddRange(TextValueType.Documentation); - - var attribute = AttributeDecl(); - - text.Comments.AddRange(GetComments(docs).ToArray()); - - text.CustomAttributes.Add(attribute); - classDeclaration.Members.Add(text); - - var valuePropertyModel = new PropertyModel(Configuration, textName, TextValueType, this); - - Configuration.MemberVisitor(text, valuePropertyModel); + // When a derived class has a simpleContent restriction with enum facets, + // we generate the enum type but do NOT add a new Value property in the derived class. + // This is because the C# XmlSerializer has limitations when dealing with simpleContent + // restrictions in inheritance hierarchies - it cannot handle a derived class that adds + // a new XmlText property when the base class already has one. + // + // The enum type is still generated and can be used for validation/conversion manually: + // e.g., var enumValue = (MyEnum)Enum.Parse(typeof(MyEnum), instance.Value); + // + // This is a compromise to work within XmlSerializer's constraints while still providing + // the enum type that users requested in issue #561. } } else if (!string.IsNullOrEmpty(Configuration.TextValuePropertyName)) From 1d78055eacaa62d5a5da3ba4d77f92202da5ca71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 13:34:50 +0000 Subject: [PATCH 5/6] Address code review feedback - Moved comment explaining enum generation to proper location outside if block - Added null safety checks for qualifiedName in CreateSimpleContentEnumModel - Improved code clarity and safety Co-authored-by: mganss <976344+mganss@users.noreply.github.com> --- XmlSchemaClassGenerator/ModelBuilder.cs | 4 ++-- XmlSchemaClassGenerator/Models/ClassModel.cs | 25 +++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index d995c01a..c87c55e9 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -734,8 +734,8 @@ private static List EnsureEnumValuesUnique(List private EnumModel CreateSimpleContentEnumModel(ClassModel classModel, List enumFacets) { - var enumNamespace = namespaceModel?.Key.XmlSchemaNamespace ?? qualifiedName.Namespace; - var enumQualifiedName = qualifiedName.IsEmpty + var enumNamespace = namespaceModel?.Key.XmlSchemaNamespace ?? qualifiedName?.Namespace ?? ""; + var enumQualifiedName = qualifiedName == null || qualifiedName.IsEmpty ? new XmlQualifiedName($"{classModel.Name}Enum", enumNamespace) : new XmlQualifiedName($"{qualifiedName.Name}Enum", enumNamespace); diff --git a/XmlSchemaClassGenerator/Models/ClassModel.cs b/XmlSchemaClassGenerator/Models/ClassModel.cs index 5ba69090..cd865b09 100644 --- a/XmlSchemaClassGenerator/Models/ClassModel.cs +++ b/XmlSchemaClassGenerator/Models/ClassModel.cs @@ -97,20 +97,17 @@ public override CodeTypeDeclaration Generate() { classDeclaration.BaseTypes.Add(BaseClass.GetReferenceFor(Namespace)); - if (TextValueType != null && !string.IsNullOrEmpty(Configuration.TextValuePropertyName)) - { - // When a derived class has a simpleContent restriction with enum facets, - // we generate the enum type but do NOT add a new Value property in the derived class. - // This is because the C# XmlSerializer has limitations when dealing with simpleContent - // restrictions in inheritance hierarchies - it cannot handle a derived class that adds - // a new XmlText property when the base class already has one. - // - // The enum type is still generated and can be used for validation/conversion manually: - // e.g., var enumValue = (MyEnum)Enum.Parse(typeof(MyEnum), instance.Value); - // - // This is a compromise to work within XmlSerializer's constraints while still providing - // the enum type that users requested in issue #561. - } + // When a derived class has a simpleContent restriction with enum facets (TextValueType != null), + // we generate the enum type but do NOT add a new Value property in the derived class. + // This is because the C# XmlSerializer has limitations when dealing with simpleContent + // restrictions in inheritance hierarchies - it cannot handle a derived class that adds + // a new XmlText property when the base class already has one. + // + // The enum type is still generated (in ModelBuilder) and can be used for validation/conversion manually: + // e.g., var enumValue = (MyEnum)Enum.Parse(typeof(MyEnum), instance.Value); + // + // This is a compromise to work within XmlSerializer's constraints while still providing + // the enum type that users requested in issue #561. } else if (!string.IsNullOrEmpty(Configuration.TextValuePropertyName)) { From 3c93a82ea6ea16751337a064a4bd7d42fc93d47c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:47:40 +0000 Subject: [PATCH 6/6] 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> --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 30 +++++- XmlSchemaClassGenerator/Models/ClassModel.cs | 101 +++++++++++++++++-- 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index a423223a..6a9eddc2 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -245,12 +245,36 @@ public void TestSimpleContentEnum() var baseType = assembly.GetType($"{ns}.CodeType"); Assert.Equal(baseType, type.BaseType); - // The derived class should NOT have its own Value property - // It inherits the string Value property from the base class - // Users can manually convert between string and enum as needed + // The derived class inherits the string Value property from the base class var valueProperty = type.GetProperty("Value"); Assert.NotNull(valueProperty); Assert.Equal(typeof(string), valueProperty.PropertyType); + + // The derived class should have an EnumValue adapter property + var enumValueProperty = type.GetProperty("EnumValue"); + Assert.NotNull(enumValueProperty); + Assert.Equal(typeof(Nullable<>).MakeGenericType(enumType), enumValueProperty.PropertyType); + + // Test that the EnumValue property works correctly + var instance = Activator.CreateInstance(type); + Assert.NotNull(instance); + + // Set Value to a string and verify EnumValue returns the correct enum + valueProperty.SetValue(instance, "Always"); + var enumValue = enumValueProperty.GetValue(instance); + Assert.NotNull(enumValue); + Assert.Equal("Always", enumValue.ToString()); + + // Set EnumValue and verify Value is updated + var alwaysValue = Enum.Parse(enumType, "Never"); + enumValueProperty.SetValue(instance, alwaysValue); + var stringValue = valueProperty.GetValue(instance); + Assert.Equal("Never", stringValue); + + // Set EnumValue to null and verify Value is null + enumValueProperty.SetValue(instance, null); + stringValue = valueProperty.GetValue(instance); + Assert.Null(stringValue); } [Fact, TestPriority(1)] diff --git a/XmlSchemaClassGenerator/Models/ClassModel.cs b/XmlSchemaClassGenerator/Models/ClassModel.cs index cd865b09..c20c1ebc 100644 --- a/XmlSchemaClassGenerator/Models/ClassModel.cs +++ b/XmlSchemaClassGenerator/Models/ClassModel.cs @@ -98,16 +98,97 @@ public override CodeTypeDeclaration Generate() classDeclaration.BaseTypes.Add(BaseClass.GetReferenceFor(Namespace)); // When a derived class has a simpleContent restriction with enum facets (TextValueType != null), - // we generate the enum type but do NOT add a new Value property in the derived class. - // This is because the C# XmlSerializer has limitations when dealing with simpleContent - // restrictions in inheritance hierarchies - it cannot handle a derived class that adds - // a new XmlText property when the base class already has one. - // - // The enum type is still generated (in ModelBuilder) and can be used for validation/conversion manually: - // e.g., var enumValue = (MyEnum)Enum.Parse(typeof(MyEnum), instance.Value); - // - // This is a compromise to work within XmlSerializer's constraints while still providing - // the enum type that users requested in issue #561. + // we generate an adapter property that allows strongly-typed access to the enum values. + // We cannot add a new XmlText property because the XmlSerializer doesn't allow it when + // the base class already has one. Instead, we use an [XmlIgnore] adapter property. + if (TextValueType != null && !string.IsNullOrEmpty(Configuration.TextValuePropertyName)) + { + var textName = Configuration.TextValuePropertyName; + var enumTypeReference = TextValueType.GetReferenceFor(Namespace); + var nullableEnumTypeReference = new CodeTypeReference(typeof(Nullable<>)); + nullableEnumTypeReference.TypeArguments.Add(enumTypeReference); + + // Create the EnumValue adapter property + var enumValueProperty = new CodeMemberProperty + { + Name = "EnumValue", + Type = nullableEnumTypeReference, + Attributes = MemberAttributes.Public, + HasGet = true, + HasSet = true + }; + + // Add [XmlIgnore] attribute + var ignoreAttribute = AttributeDecl(); + enumValueProperty.CustomAttributes.Add(ignoreAttribute); + + // Getter: Try to parse the Value property to enum + // if (Enum.TryParse(typeof(EnumType), Value, true, out var result)) + // return (EnumType)result; + // return null; + var resultVariable = new CodeVariableDeclarationStatement(typeof(object), "result"); + var tryParseCondition = new CodeMethodInvokeExpression( + new CodeTypeReferenceExpression(typeof(Enum)), + "TryParse", + new CodeTypeOfExpression(enumTypeReference), + new CodePropertyReferenceExpression( + new CodeThisReferenceExpression(), + textName), + new CodePrimitiveExpression(true), + new CodeDirectionExpression(FieldDirection.Out, new CodeVariableReferenceExpression("result"))); + + var returnCastResult = new CodeMethodReturnStatement( + new CodeCastExpression( + nullableEnumTypeReference, + new CodeVariableReferenceExpression("result"))); + + var ifTryParse = new CodeConditionStatement( + tryParseCondition, + returnCastResult); + + enumValueProperty.GetStatements.Add(resultVariable); + enumValueProperty.GetStatements.Add(ifTryParse); + enumValueProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(null))); + + // Setter: Value = value?.ToString(); + // Since CodeDOM doesn't support null-conditional operator, we need to check and set + var valueNotNull = new CodeBinaryOperatorExpression( + new CodePropertySetValueReferenceExpression(), + CodeBinaryOperatorType.IdentityInequality, + new CodePrimitiveExpression(null)); + + var setToString = new CodeAssignStatement( + new CodePropertyReferenceExpression( + new CodeThisReferenceExpression(), + textName), + new CodeMethodInvokeExpression( + new CodePropertySetValueReferenceExpression(), + "ToString")); + + var setToNull = new CodeAssignStatement( + new CodePropertyReferenceExpression( + new CodeThisReferenceExpression(), + textName), + new CodePrimitiveExpression(null)); + + enumValueProperty.SetStatements.Add( + new CodeConditionStatement( + valueNotNull, + new CodeStatement[] { setToString }, + new CodeStatement[] { setToNull })); + + var docs = new List { + new() { Language = English, Text = "Gets or sets the typed value of the text content." }, + new() { Language = German, Text = "Ruft den typisierten Wert des Textinhalts ab oder legt diesen fest." } + }; + + enumValueProperty.Comments.AddRange(GetComments(docs).ToArray()); + + classDeclaration.Members.Add(enumValueProperty); + + var enumValuePropertyModel = new PropertyModel(Configuration, "EnumValue", TextValueType, this); + Configuration.MemberVisitor(enumValueProperty, enumValuePropertyModel); + } } else if (!string.IsNullOrEmpty(Configuration.TextValuePropertyName)) {