From c9f408f9868402e7478ddd6759dfe92230974a5f Mon Sep 17 00:00:00 2001 From: Joel Dumas Date: Fri, 5 Jul 2024 21:52:14 +0200 Subject: [PATCH 1/5] Fixes #68 and add support for element default/fixed values --- XObjectsCode/Src/ClrBasePropertyInfo.cs | 14 +- XObjectsCode/Src/ClrPropertyInfo.cs | 137 ++++++++++---------- XObjectsCode/Src/SimpleTypeCodeDomHelper.cs | 22 ++-- XObjectsCode/Src/XsdToTypesConverter.cs | 33 ++++- XObjectsCore/API/XTypedServices.cs | 3 + 5 files changed, 119 insertions(+), 90 deletions(-) diff --git a/XObjectsCode/Src/ClrBasePropertyInfo.cs b/XObjectsCode/Src/ClrBasePropertyInfo.cs index 79eda1d0..168c68c5 100644 --- a/XObjectsCode/Src/ClrBasePropertyInfo.cs +++ b/XObjectsCode/Src/ClrBasePropertyInfo.cs @@ -12,7 +12,6 @@ public abstract class ClrBasePropertyInfo : ContentInfo protected bool hasSet; protected XCodeTypeReference returnType; - protected XCodeTypeReference defaultValueType; protected bool isVirtual; protected bool isOverride; @@ -21,10 +20,9 @@ public abstract class ClrBasePropertyInfo : ContentInfo public ClrBasePropertyInfo() { - this.IsVirtual = false; - this.isOverride = false; - this.returnType = null; - this.defaultValueType = null; + IsVirtual = false; + isOverride = false; + returnType = null; annotations = new List(); } @@ -94,12 +92,6 @@ public virtual XCodeTypeReference ReturnType set { returnType = value; } } - public virtual XCodeTypeReference DefaultValueType - { - get { return defaultValueType; } - set { defaultValueType = value; } - } - public virtual string ClrTypeName { get { return null; } diff --git a/XObjectsCode/Src/ClrPropertyInfo.cs b/XObjectsCode/Src/ClrPropertyInfo.cs index cbd986b8..7f504b46 100644 --- a/XObjectsCode/Src/ClrPropertyInfo.cs +++ b/XObjectsCode/Src/ClrPropertyInfo.cs @@ -37,7 +37,6 @@ public ClrPropertyInfo(string propertyName, string propertyNs, string schemaName this.schemaName = schemaName; this.hasSet = true; this.returnType = null; - this.defaultValueType = null; this.clrTypeName = null; this.occursInSchema = occursInSchema; if (this.occursInSchema > Occurs.ZeroOrOne) @@ -282,9 +281,6 @@ public override bool VerifyRequired public override XCodeTypeReference ReturnType => returnType ??= CreateReturnType(IsEnum ? typeRef.ClrFullTypeName : clrTypeName); - public override XCodeTypeReference DefaultValueType - => defaultValueType ??= CreateReturnType(clrTypeName); - private string QualifiedType => typeRef.IsLocalType && !typeRef.IsSimpleType ? parentTypeFullName + "." + clrTypeName : clrTypeName; @@ -802,8 +798,6 @@ private void AddGetStatements(CodeStatementCollection getStatements) return; } - CodeExpression returnExp = null; - if (FixedValue != null) { getStatements.Add( @@ -818,11 +812,13 @@ private void AddGetStatements(CodeStatementCollection getStatements) getStatements.Add(GetValueMethodCall()); CheckOccurrence(getStatements); CheckNillable(getStatements); + GetDefaultValue(getStatements); CodeVariableReferenceExpression returnValueExp = new CodeVariableReferenceExpression("x"); + CodeExpression returnExp; if (!IsRef && typeRef.IsSimpleType) { //for referencing properties, directly create the object of referenced type - CodeTypeReference parseType = DefaultValueType; + CodeTypeReference parseType = ReturnType; if (typeRef.IsValueType && IsNullable) { parseType = new CodeTypeReference(clrTypeName); @@ -862,15 +858,7 @@ private void AddGetStatements(CodeStatementCollection getStatements) returnValueExp, simpleTypeExpression); - if (DefaultValue != null) - { - ((CodeMethodInvokeExpression) returnExp).Parameters.Add( - new CodeFieldReferenceExpression(null, - NameGenerator.ChangeClrName(this.propertyName, - NameOptions.MakeDefaultValueField))); - } - - if (this.IsEnum) + if (IsEnum) { // (EnumType) Enum.Parse(typeof(EnumType), returnExp) returnExp = CodeDomHelper.CreateParseEnumCall(this.TypeReference.ClrFullTypeName, returnExp); @@ -889,11 +877,25 @@ private void CheckOccurrence(CodeStatementCollection getStatements) { Debug.Assert(!this.IsList); CodeStatement returnStatement = null; - if (CanBeAbsent && DefaultValue == null) + if (CanBeAbsent) { - // For value types, this is needed to return T?, since ParseValue return T. - // It's not mandatory for ref types but it's more consistent and performant to do it always. - returnStatement = new CodeMethodReturnStatement(new CodePrimitiveExpression(null)); + // Absent attributes return their default value (if any). + // Note that absent elements return null, only empty elements return their default value (per xsd specs). + if (DefaultValue != null && propertyOrigin == SchemaOrigin.Attribute) + { + returnStatement = new CodeMethodReturnStatement( + new CodeFieldReferenceExpression( + null, + NameGenerator.ChangeClrName(propertyName, NameOptions.MakeDefaultValueField) + ) + ); + } + else + { + // For value types, this is needed to return T?, since ParseValue return T. + // It's not mandatory for ref types but it's more consistent and performant to do it always. + returnStatement = new CodeMethodReturnStatement(new CodePrimitiveExpression(null)); + } } else if (VerifyRequired) { @@ -931,6 +933,30 @@ private void CheckNillable(CodeStatementCollection getStatements) } } + private void GetDefaultValue(CodeStatementCollection getStatements) + { + if (DefaultValue != null && propertyOrigin != SchemaOrigin.Attribute) + { + var x = new CodeVariableReferenceExpression("x"); + + getStatements.Add( + new CodeConditionStatement( + new CodeBinaryOperatorExpression( + new CodeBinaryOperatorExpression(x, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)), + CodeBinaryOperatorType.BooleanAnd, + new CodeFieldReferenceExpression(x, "IsEmpty") + ), + new CodeMethodReturnStatement( + new CodeFieldReferenceExpression( + null, + NameGenerator.ChangeClrName(propertyName, NameOptions.MakeDefaultValueField) + ) + ) + ) + ); + } + } + private void AddSetStatements(CodeStatementCollection setStatements) { AddFixedValueChecking(setStatements); @@ -1149,36 +1175,29 @@ public void SetFixedDefaultValue(ClrWrapperTypeInfo typeInfo) protected void CreateFixedDefaultValue(CodeTypeDeclaration typeDecl) { - if (fixedDefaultValue != null) - { - //Add Fixed/Default value wrapping field - CodeMemberField fixedOrDefaultField = null; - CodeTypeReference returnType = DefaultValueType; - if (this.unionDefaultType != null) - { - returnType = new CodeTypeReference(unionDefaultType.ToString()); - } + if (fixedDefaultValue == null) return; + // Add Fixed/Default value wrapping field - if (FixedValue != null) - { - fixedOrDefaultField = new CodeMemberField(returnType, - NameGenerator.ChangeClrName(PropertyName, NameOptions.MakeFixedValueField)); - } - else // if (DefaultValue != null) - { - fixedOrDefaultField = new CodeMemberField(returnType, - NameGenerator.ChangeClrName(PropertyName, NameOptions.MakeDefaultValueField)); - } + CodeTypeReference returnType = unionDefaultType != null + ? new CodeTypeReference(unionDefaultType.ToString()) + : ReturnType; - CodeDomHelper.AddBrowseNever(fixedOrDefaultField); - fixedOrDefaultField.Attributes = (fixedOrDefaultField.Attributes & ~MemberAttributes.AccessMask & - ~MemberAttributes.ScopeMask) - | MemberAttributes.Private | MemberAttributes.Static; + var fieldName = NameGenerator.ChangeClrName( + PropertyName, + FixedValue != null ? NameOptions.MakeFixedValueField : NameOptions.MakeDefaultValueField /* DefaultValue != null */); - fixedOrDefaultField.InitExpression = - SimpleTypeCodeDomHelper.CreateFixedDefaultValueExpression(returnType, fixedDefaultValue); - typeDecl.Members.Add(fixedOrDefaultField); - } + var fixedOrDefaultField = new CodeMemberField(returnType, fieldName); + CodeDomHelper.AddBrowseNever(fixedOrDefaultField); + + fixedOrDefaultField.Attributes = + (fixedOrDefaultField.Attributes & ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask) + | MemberAttributes.Private + | MemberAttributes.Static; + + fixedOrDefaultField.InitExpression = + SimpleTypeCodeDomHelper.CreateFixedDefaultValueExpression(returnType, fixedDefaultValue, IsEnum); + + typeDecl.Members.Add(fixedOrDefaultField); } protected void AddFixedValueChecking(CodeStatementCollection setStatements) @@ -1191,9 +1210,9 @@ protected void AddFixedValueChecking(CodeStatementCollection setStatements) setStatements.Add( new CodeConditionStatement( CodeDomHelper.CreateMethodCall( - new CodePropertySetValueReferenceExpression(), + fixedValueExpr, Constants.EqualityCheck, - fixedValueExpr + new CodePropertySetValueReferenceExpression() ), new CodeStatement[] { }, new CodeStatement[] @@ -1207,25 +1226,5 @@ protected void AddFixedValueChecking(CodeStatementCollection setStatements) ); } } - - public virtual void InsertDefaultFixedValueInDefaultCtor(CodeConstructor ctor) - { - if (this.FixedValue != null) - { - ctor.Statements.Add( - new CodeAssignStatement( - CodeDomHelper.CreateFieldReference(null, propertyName), - CodeDomHelper.CreateFieldReference(null, - NameGenerator.ChangeClrName(propertyName, NameOptions.MakeFixedValueField)))); - } - else if (DefaultValue != null) - { - ctor.Statements.Add( - new CodeAssignStatement( - CodeDomHelper.CreateFieldReference(null, propertyName), - CodeDomHelper.CreateFieldReference(null, - NameGenerator.ChangeClrName(propertyName, NameOptions.MakeDefaultValueField)))); - } - } } } \ No newline at end of file diff --git a/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs b/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs index bb356a80..c6edf540 100644 --- a/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs +++ b/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs @@ -405,7 +405,7 @@ private static CodeExpression CreateTypeConversionExpr(string typeName, object v new CodePrimitiveExpression(value.ToString())); } - internal static CodeExpression CreateValueExpression(string builtInType, string strValue) + internal static CodeExpression CreateValueExpression(string builtInType, string strValue, bool isEnum) { int dot = builtInType.LastIndexOf('.'); Debug.Assert(dot != -1); @@ -416,6 +416,10 @@ internal static CodeExpression CreateValueExpression(string builtInType, string { return new CodePrimitiveExpression(strValue); } + else if (isEnum) + { + return new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(builtInType), strValue); + } else if (localType == "Uri") { return new CodeObjectCreateExpression("Uri", new CodePrimitiveExpression(strValue)); @@ -426,30 +430,30 @@ internal static CodeExpression CreateValueExpression(string builtInType, string } } - internal static CodeArrayCreateExpression CreateFixedDefaultArrayValueInit(string baseType, string value) + internal static CodeArrayCreateExpression CreateFixedDefaultArrayValueInit(string baseType, string value, bool isEnum) { - CodeArrayCreateExpression array = new CodeArrayCreateExpression(baseType); + var array = new CodeArrayCreateExpression(baseType); foreach (string s in value.Split(' ')) { - array.Initializers.Add(CreateValueExpression(baseType, s)); + array.Initializers.Add(CreateValueExpression(baseType, s, isEnum)); } return array; } - internal static CodeExpression CreateFixedDefaultValueExpression(CodeTypeReference type, string value) + internal static CodeExpression CreateFixedDefaultValueExpression(CodeTypeReference type, string value, bool isEnum) { string baseType = type.BaseType; if (baseType.Contains("Nullable")) { Debug.Assert(type.TypeArguments.Count == 1); baseType = type.TypeArguments[0].BaseType; - return CreateValueExpression(baseType, value); + return CreateValueExpression(baseType, value, isEnum); } else if (type.ArrayRank != 0) { baseType = type.ArrayElementType.BaseType; - return CreateFixedDefaultArrayValueInit(baseType, value); + return CreateFixedDefaultArrayValueInit(baseType, value, isEnum); } else if (baseType.Contains("List")) { @@ -457,10 +461,10 @@ internal static CodeExpression CreateFixedDefaultValueExpression(CodeTypeReferen Debug.Assert(type.TypeArguments.Count == 1); baseType = type.TypeArguments[0].BaseType; - return CreateFixedDefaultArrayValueInit(baseType, value); + return CreateFixedDefaultArrayValueInit(baseType, value, isEnum); } - return CreateValueExpression(baseType, value); + return CreateValueExpression(baseType, value, isEnum); } } } \ No newline at end of file diff --git a/XObjectsCode/Src/XsdToTypesConverter.cs b/XObjectsCode/Src/XsdToTypesConverter.cs index 2aecf9ce..5dd5eb51 100644 --- a/XObjectsCode/Src/XsdToTypesConverter.cs +++ b/XObjectsCode/Src/XsdToTypesConverter.cs @@ -985,7 +985,7 @@ private ClrPropertyInfo BuildPropertyForElement(XmlSchemaElement elem, bool from propertyInfo.ClrNamespace = clrNs; propertyInfo.IsNillable = elem.IsNillable; - //SetFixedDefaultValue(elem, propertyInfo); + SetFixedDefaultValue(elem, propertyInfo); if (substitutionMembers != null) { @@ -1191,5 +1191,36 @@ private void SetFixedDefaultValue(XmlSchemaAttribute attribute, ClrPropertyInfo } } } + + private void SetFixedDefaultValue(XmlSchemaElement element, ClrPropertyInfo propertyInfo) + { + //saves fixed/default value in the corresponding property + //Currently only consider fixed/default values for simple types + if (element.RefName != null && !element.RefName.IsEmpty) + { + var globalEl = (XmlSchemaElement)schemas.GlobalElements[element.RefName]; + propertyInfo.FixedValue = globalEl.FixedValue; + propertyInfo.DefaultValue = globalEl.DefaultValue; + } + else + { + propertyInfo.FixedValue = element.FixedValue; + propertyInfo.DefaultValue = element.DefaultValue; + } + + if (element.ElementSchemaType.DerivedBy == XmlSchemaDerivationMethod.Union) + { + string value = propertyInfo.FixedValue; + if (value == null) + value = propertyInfo.DefaultValue; + if (value != null) + { + propertyInfo.unionDefaultType = element + .ElementSchemaType.Datatype + .ParseValue(value, new NameTable(), null) + .GetType(); + } + } + } } } \ No newline at end of file diff --git a/XObjectsCore/API/XTypedServices.cs b/XObjectsCore/API/XTypedServices.cs index a4956bff..27c6403b 100644 --- a/XObjectsCore/API/XTypedServices.cs +++ b/XObjectsCore/API/XTypedServices.cs @@ -399,6 +399,9 @@ public static T ParseValue(XAttribute attribute, XmlSchemaDatatype datatype) return ParseValue(attribute.Value, attribute.Parent, datatype); } + // Kept for backward compatibility with code generated in previous versions. + // Current generator does not use this method anymore, as attributes with default properties + // have `if (attr == null) return defaultValue` directly in getter to workaround enum parsing. public static T ParseValue(XAttribute attribute, XmlSchemaDatatype datatype, T defaultValue) { if (attribute == null) From 4756f43d6d93cabde7b611c2cdaacbac6769cf71 Mon Sep 17 00:00:00 2001 From: Joel Dumas Date: Mon, 8 Jul 2024 19:45:45 +0200 Subject: [PATCH 2/5] Fix various issues in Elements new implementation --- LinqToXsd/Properties/launchSettings.json | 18 +++ XObjectsCode/Src/ClrPropertyInfo.cs | 126 +++++++++++++------- XObjectsCode/Src/SimpleTypeCodeDomHelper.cs | 10 +- XObjectsCode/Src/XsdToTypesConverter.cs | 4 + 4 files changed, 115 insertions(+), 43 deletions(-) diff --git a/LinqToXsd/Properties/launchSettings.json b/LinqToXsd/Properties/launchSettings.json index a5ec9c8b..efa9bbdb 100644 --- a/LinqToXsd/Properties/launchSettings.json +++ b/LinqToXsd/Properties/launchSettings.json @@ -32,6 +32,24 @@ "commandLineArgs": "gen \"Microsoft.Search.Query.xsd\" -a", "workingDirectory": "..\\GeneratedSchemaLibraries\\Microsoft Search", "hotReloadEnabled": false + }, + "AIXM": { + "commandName": "Project", + "commandLineArgs": "gen \"AIXM_Features.xsd\" -a", + "workingDirectory": "..\\GeneratedSchemaLibraries\\AIXM\\aixm-5.1.1", + "hotReloadEnabled": false + }, + "Pubmed": { + "commandName": "Project", + "commandLineArgs": "gen \"efetch-pubmed.xsd\" -a", + "workingDirectory": "..\\GeneratedSchemaLibraries\\Pubmed", + "hotReloadEnabled": false + }, + "Windows": { + "commandName": "Project", + "commandLineArgs": "gen \"windowsTaskSched.xsd\" -a", + "workingDirectory": "..\\GeneratedSchemaLibraries\\Windows", + "hotReloadEnabled": false } } } \ No newline at end of file diff --git a/XObjectsCode/Src/ClrPropertyInfo.cs b/XObjectsCode/Src/ClrPropertyInfo.cs index 7f504b46..228738cb 100644 --- a/XObjectsCode/Src/ClrPropertyInfo.cs +++ b/XObjectsCode/Src/ClrPropertyInfo.cs @@ -159,7 +159,14 @@ public override bool IsList public override bool IsNullable { - get { return (CanBeAbsent && fixedDefaultValue == null) || IsNillable; } + get + { + return + // Elements can be absent and must be read as null even when they have a default value + // Absent attributes will take their default value when they have one + (CanBeAbsent && (propertyOrigin != SchemaOrigin.Attribute || fixedDefaultValue == null)) + || IsNillable; + } } public bool CanBeAbsent @@ -798,7 +805,10 @@ private void AddGetStatements(CodeStatementCollection getStatements) return; } - if (FixedValue != null) + // Fixed attributes always have the same value, whether they're present or not. + // Fixed elements are treated in GetElementDefaultValue, if they are present. + // An optional, missing element should still be interpreted as null even if it has a fixed value when present. + if (FixedValue != null && propertyOrigin == SchemaOrigin.Attribute) { getStatements.Add( new CodeMethodReturnStatement( @@ -812,7 +822,7 @@ private void AddGetStatements(CodeStatementCollection getStatements) getStatements.Add(GetValueMethodCall()); CheckOccurrence(getStatements); CheckNillable(getStatements); - GetDefaultValue(getStatements); + GetElementFixedOrDefaultValue(getStatements); // Attribute default value is handled in CheckOccurence CodeVariableReferenceExpression returnValueExp = new CodeVariableReferenceExpression("x"); CodeExpression returnExp; if (!IsRef && typeRef.IsSimpleType) @@ -933,28 +943,36 @@ private void CheckNillable(CodeStatementCollection getStatements) } } - private void GetDefaultValue(CodeStatementCollection getStatements) + private void GetElementFixedOrDefaultValue(CodeStatementCollection getStatements) { - if (DefaultValue != null && propertyOrigin != SchemaOrigin.Attribute) - { - var x = new CodeVariableReferenceExpression("x"); + // Default/fixed values of attributes are already handled previously based on attribute presence + if (propertyOrigin == SchemaOrigin.Attribute) return; - getStatements.Add( - new CodeConditionStatement( - new CodeBinaryOperatorExpression( - new CodeBinaryOperatorExpression(x, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)), - CodeBinaryOperatorType.BooleanAnd, - new CodeFieldReferenceExpression(x, "IsEmpty") - ), - new CodeMethodReturnStatement( - new CodeFieldReferenceExpression( - null, - NameGenerator.ChangeClrName(propertyName, NameOptions.MakeDefaultValueField) - ) + var fieldNaming = + DefaultValue != null ? NameOptions.MakeDefaultValueField : + FixedValue != null ? NameOptions.MakeFixedValueField : + NameOptions.None; + if (fieldNaming == NameOptions.None) return; + + var x = new CodeVariableReferenceExpression("x"); + + // Default/fixed values only apply when element is present, if `x != null` + var condition = new CodeBinaryOperatorExpression(x, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)); + // In addition, default value applies if element is empty, if `x != null && x.IsEmpty` + if (fieldNaming == NameOptions.MakeDefaultValueField) + condition = new(condition, CodeBinaryOperatorType.BooleanAnd, new CodeFieldReferenceExpression(x, "IsEmpty")); + + getStatements.Add( + new CodeConditionStatement( + condition, + new CodeMethodReturnStatement( + new CodeFieldReferenceExpression( + null, + NameGenerator.ChangeClrName(propertyName, fieldNaming) ) ) - ); - } + ) + ); } private void AddSetStatements(CodeStatementCollection setStatements) @@ -1202,29 +1220,55 @@ protected void CreateFixedDefaultValue(CodeTypeDeclaration typeDecl) protected void AddFixedValueChecking(CodeStatementCollection setStatements) { - if (FixedValue != null) - { - CodeExpression fixedValueExpr = - new CodeFieldReferenceExpression(null, - NameGenerator.ChangeClrName(this.propertyName, NameOptions.MakeFixedValueField)); - setStatements.Add( - new CodeConditionStatement( - CodeDomHelper.CreateMethodCall( - fixedValueExpr, - Constants.EqualityCheck, - new CodePropertySetValueReferenceExpression() - ), - new CodeStatement[] { }, - new CodeStatement[] - { - new CodeThrowExceptionStatement( - new CodeObjectCreateExpression(typeof(LinqToXsdFixedValueException), - new CodePropertySetValueReferenceExpression(), - fixedValueExpr)) - } + if (FixedValue == null) return; + + // The set value must match the property fixed value. + + CodeExpression fixedValueExpr = + new CodeFieldReferenceExpression(null, + NameGenerator.ChangeClrName(propertyName, NameOptions.MakeFixedValueField)); + + var valueExpr = new CodePropertySetValueReferenceExpression(); + + // Note the condition is opposite, because CodeDOM doesn't have unary negation... + // so we're doing `if (value == fixed) { /* ok */ } else throw` + CodeExpression condition = CodeDomHelper.CreateMethodCall( + fixedValueExpr, + Constants.EqualityCheck, + valueExpr); + + // If the property is an optional element, setting it to null is also acceptable: it removes the element from document. + // So we're checking `if (value == fixed || value == null) ...` + // On principle, setting attributes to null can also make sense as it would remove the XAttribute from document, + // but that doesn't mesh well with the type system (attributes with default/fixed values are never nullable, + // which would still be workable on Ref types with [AllowNull] on property, but we can't assign null to a value type setter. + // This is really an edge case and can be achieved by removing the XAttribute from Untyped. + if (IsNullable) + { + condition = new CodeBinaryOperatorExpression( + condition, + CodeBinaryOperatorType.BooleanOr, + new CodeBinaryOperatorExpression( + valueExpr, + CodeBinaryOperatorType.IdentityEquality, + new CodePrimitiveExpression(null) ) ); } + + setStatements.Add( + new CodeConditionStatement( + condition, + new CodeStatement[] { }, + new CodeStatement[] + { + new CodeThrowExceptionStatement( + new CodeObjectCreateExpression(typeof(LinqToXsdFixedValueException), + new CodePropertySetValueReferenceExpression(), + fixedValueExpr)) + } + ) + ); } } } \ No newline at end of file diff --git a/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs b/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs index c6edf540..ccbd1296 100644 --- a/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs +++ b/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Diagnostics; using System.Globalization; +using System.Text.RegularExpressions; namespace Xml.Schema.Linq.CodeGen { @@ -444,18 +445,23 @@ internal static CodeArrayCreateExpression CreateFixedDefaultArrayValueInit(strin internal static CodeExpression CreateFixedDefaultValueExpression(CodeTypeReference type, string value, bool isEnum) { string baseType = type.BaseType; - if (baseType.Contains("Nullable")) + if (Regex.IsMatch(baseType, @"\bNullable\b")) { Debug.Assert(type.TypeArguments.Count == 1); baseType = type.TypeArguments[0].BaseType; return CreateValueExpression(baseType, value, isEnum); } + else if (baseType.EndsWith("?")) + { + baseType = baseType.Substring(0, baseType.Length - 1); + return CreateValueExpression(baseType, value, isEnum); + } else if (type.ArrayRank != 0) { baseType = type.ArrayElementType.BaseType; return CreateFixedDefaultArrayValueInit(baseType, value, isEnum); } - else if (baseType.Contains("List")) + else if (Regex.IsMatch(baseType, @"\bList\b")) { //Create sth like: new List(new string[] { }); Debug.Assert(type.TypeArguments.Count == 1); diff --git a/XObjectsCode/Src/XsdToTypesConverter.cs b/XObjectsCode/Src/XsdToTypesConverter.cs index 5dd5eb51..98b3f7ee 100644 --- a/XObjectsCode/Src/XsdToTypesConverter.cs +++ b/XObjectsCode/Src/XsdToTypesConverter.cs @@ -1195,7 +1195,11 @@ private void SetFixedDefaultValue(XmlSchemaAttribute attribute, ClrPropertyInfo private void SetFixedDefaultValue(XmlSchemaElement element, ClrPropertyInfo propertyInfo) { //saves fixed/default value in the corresponding property + + if ((element.FixedValue ?? element.DefaultValue) == null) return; //Currently only consider fixed/default values for simple types + if (element.ElementSchemaType is XmlSchemaComplexType) return; + if (element.RefName != null && !element.RefName.IsEmpty) { var globalEl = (XmlSchemaElement)schemas.GlobalElements[element.RefName]; From 561ca35505a390bf1721375bc1577871bf334d68 Mon Sep 17 00:00:00 2001 From: Joel Dumas Date: Mon, 8 Jul 2024 20:25:55 +0200 Subject: [PATCH 3/5] Improve fixed value handling in elements --- XObjectsCode/Src/ClrPropertyInfo.cs | 61 +++++++++++++++++------------ 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/XObjectsCode/Src/ClrPropertyInfo.cs b/XObjectsCode/Src/ClrPropertyInfo.cs index 228738cb..57ac5f24 100644 --- a/XObjectsCode/Src/ClrPropertyInfo.cs +++ b/XObjectsCode/Src/ClrPropertyInfo.cs @@ -806,23 +806,23 @@ private void AddGetStatements(CodeStatementCollection getStatements) } // Fixed attributes always have the same value, whether they're present or not. - // Fixed elements are treated in GetElementDefaultValue, if they are present. - // An optional, missing element should still be interpreted as null even if it has a fixed value when present. if (FixedValue != null && propertyOrigin == SchemaOrigin.Attribute) { - getStatements.Add( - new CodeMethodReturnStatement( - new CodeFieldReferenceExpression(null, - NameGenerator.ChangeClrName(this.propertyName, NameOptions.MakeFixedValueField) - )) - ); + ReturnFixedValue(getStatements); return; } getStatements.Add(GetValueMethodCall()); CheckOccurrence(getStatements); CheckNillable(getStatements); - GetElementFixedOrDefaultValue(getStatements); // Attribute default value is handled in CheckOccurence + // Fixed elements always have the same value, _when they're present._ + // If they're optional they can still be absent and read as null, which is handled in CheckOccurence + if (FixedValue != null && propertyOrigin != SchemaOrigin.Attribute) + { + ReturnFixedValue(getStatements); + return; + } + GetElementDefaultValue(getStatements); // Attribute default value is handled in CheckOccurence CodeVariableReferenceExpression returnValueExp = new CodeVariableReferenceExpression("x"); CodeExpression returnExp; if (!IsRef && typeRef.IsSimpleType) @@ -943,32 +943,43 @@ private void CheckNillable(CodeStatementCollection getStatements) } } - private void GetElementFixedOrDefaultValue(CodeStatementCollection getStatements) + private void ReturnFixedValue(CodeStatementCollection getStatements) { - // Default/fixed values of attributes are already handled previously based on attribute presence - if (propertyOrigin == SchemaOrigin.Attribute) return; + getStatements.Add( + new CodeMethodReturnStatement( + new CodeFieldReferenceExpression( + null, + NameGenerator.ChangeClrName(propertyName, NameOptions.MakeFixedValueField) + ) + ) + ); + } - var fieldNaming = - DefaultValue != null ? NameOptions.MakeDefaultValueField : - FixedValue != null ? NameOptions.MakeFixedValueField : - NameOptions.None; - if (fieldNaming == NameOptions.None) return; + private void GetElementDefaultValue(CodeStatementCollection getStatements) + { + // Default values of attributes are already handled previously based on attribute presence + if (propertyOrigin == SchemaOrigin.Attribute || DefaultValue == null) return; var x = new CodeVariableReferenceExpression("x"); - // Default/fixed values only apply when element is present, if `x != null` - var condition = new CodeBinaryOperatorExpression(x, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)); - // In addition, default value applies if element is empty, if `x != null && x.IsEmpty` - if (fieldNaming == NameOptions.MakeDefaultValueField) - condition = new(condition, CodeBinaryOperatorType.BooleanAnd, new CodeFieldReferenceExpression(x, "IsEmpty")); - + // Default values apply when element is present and empty. + // Technically at this point x should be != null thanks to CheckOccurence but let's not crash with NRE if we're reading malformed XML. + // `if (x != null && x.IsEmpty) return defaultValue;` getStatements.Add( new CodeConditionStatement( - condition, + new CodeBinaryOperatorExpression( + new CodeBinaryOperatorExpression( + x, + CodeBinaryOperatorType.IdentityInequality, + new CodePrimitiveExpression(null) + ), + CodeBinaryOperatorType.BooleanAnd, + new CodeFieldReferenceExpression(x, "IsEmpty") + ), new CodeMethodReturnStatement( new CodeFieldReferenceExpression( null, - NameGenerator.ChangeClrName(propertyName, fieldNaming) + NameGenerator.ChangeClrName(propertyName, NameOptions.MakeDefaultValueField) ) ) ) From 076f59fe8f434e9339e18e5866b1744d0c628a53 Mon Sep 17 00:00:00 2001 From: Joel Dumas Date: Wed, 10 Jul 2024 15:55:39 +0200 Subject: [PATCH 4/5] Fix List regular expression to support IList --- XObjectsCode/Src/SimpleTypeCodeDomHelper.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs b/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs index ccbd1296..49c7b82a 100644 --- a/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs +++ b/XObjectsCode/Src/SimpleTypeCodeDomHelper.cs @@ -409,9 +409,10 @@ private static CodeExpression CreateTypeConversionExpr(string typeName, object v internal static CodeExpression CreateValueExpression(string builtInType, string strValue, bool isEnum) { int dot = builtInType.LastIndexOf('.'); - Debug.Assert(dot != -1); - string localType = builtInType.Substring(dot + 1); + string localType = dot < 0 ? builtInType : builtInType.Substring(dot + 1); + + Debug.Assert(dot != -1 || isEnum); // Enums are local types that may be simple names if (localType == "String" || localType == "Object") { @@ -445,7 +446,7 @@ internal static CodeArrayCreateExpression CreateFixedDefaultArrayValueInit(strin internal static CodeExpression CreateFixedDefaultValueExpression(CodeTypeReference type, string value, bool isEnum) { string baseType = type.BaseType; - if (Regex.IsMatch(baseType, @"\bNullable\b")) + if (Regex.IsMatch(baseType, @"\bNullable`1")) { Debug.Assert(type.TypeArguments.Count == 1); baseType = type.TypeArguments[0].BaseType; @@ -461,7 +462,7 @@ internal static CodeExpression CreateFixedDefaultValueExpression(CodeTypeReferen baseType = type.ArrayElementType.BaseType; return CreateFixedDefaultArrayValueInit(baseType, value, isEnum); } - else if (Regex.IsMatch(baseType, @"\bList\b")) + else if (Regex.IsMatch(baseType, @"\bI?List`1")) { //Create sth like: new List(new string[] { }); Debug.Assert(type.TypeArguments.Count == 1); From 3431c53d92607cc679f56840e36dba624f592a3c Mon Sep 17 00:00:00 2001 From: Muhammad Miftah Date: Mon, 15 Jul 2024 21:13:21 +1000 Subject: [PATCH 5/5] Updated version. --- Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Version.props b/Version.props index 57b55ef3..da2e0af2 100644 --- a/Version.props +++ b/Version.props @@ -1,6 +1,6 @@ - 3.4.6 + 3.4.7 $(VersionSuffix) $(Version)-$(VersionSuffix)