diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 59c1e4e9..1f314f54 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -110,6 +110,7 @@ private static IEnumerable ConvertXml(string name, string xsd, Generator const string WfsPattern = "xsd/wfs/schemas.opengis.net/wfs/2.0/wfs.xsd"; const string EppPattern = "xsd/epp/*.xsd"; const string GraphMLPattern = "xsd/graphml/ygraphml.xsd"; + const string UnionPattern = "xsd/union/union.xsd"; const string NullableReferenceAttributesPattern = "xsd/nullablereferenceattributes/nullablereference.xsd"; // IATA test takes too long to perform every time @@ -155,6 +156,31 @@ public void TestClient() SharedTestFunctions.TestSamples(Output, "Client", ClientPattern); } + [Fact, TestPriority(1)] + [UseCulture("en-US")] + public void TestUnion() + { + var assembly = Compiler.Generate("Union", UnionPattern); + 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() diff --git a/XmlSchemaClassGenerator.Tests/xsd/union/union.xsd b/XmlSchemaClassGenerator.Tests/xsd/union/union.xsd new file mode 100644 index 00000000..012afc35 --- /dev/null +++ b/XmlSchemaClassGenerator.Tests/xsd/union/union.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XmlSchemaClassGenerator/CodeUtilities.cs b/XmlSchemaClassGenerator/CodeUtilities.cs index c7c8ecb0..fbe10e79 100644 --- a/XmlSchemaClassGenerator/CodeUtilities.cs +++ b/XmlSchemaClassGenerator/CodeUtilities.cs @@ -99,11 +99,11 @@ private static Type GetIntegerDerivedType(XmlSchemaDatatype xml, GeneratorConfig Type FromFallback() => configuration.UseIntegerDataTypeAsFallback && configuration.IntegerDataType != null ? configuration.IntegerDataType : typeof(string); } - public static Type GetEffectiveType(this XmlSchemaDatatype type, GeneratorConfiguration configuration, IEnumerable restrictions, bool attribute = false) + public static Type GetEffectiveType(this XmlSchemaDatatype type, GeneratorConfiguration configuration, IEnumerable restrictions, XmlSchemaType schemaType, bool attribute = false) { var resultType = type.TypeCode switch { - XmlTypeCode.AnyAtomicType => typeof(string),// union + XmlTypeCode.AnyAtomicType => GetUnionType(configuration, schemaType, attribute), // union XmlTypeCode.AnyUri or XmlTypeCode.GDay or XmlTypeCode.GMonth or XmlTypeCode.GMonthDay or XmlTypeCode.GYear or XmlTypeCode.GYearMonth => typeof(string), XmlTypeCode.Duration => configuration.NetCoreSpecificCode ? type.ValueType : typeof(string), XmlTypeCode.Time => typeof(DateTime), @@ -131,6 +131,96 @@ public static Type GetEffectiveType(this XmlSchemaDatatype type, GeneratorConfig return resultType; } + static readonly Type[] intTypes = new[] { typeof(byte), typeof(sbyte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(decimal) }; + static readonly Type[] decimalTypes = new[] { typeof(float), typeof(double), typeof(decimal) }; + + private static Type GetUnionType(GeneratorConfiguration configuration, XmlSchemaType schemaType, bool attribute) + { + if (schemaType is not XmlSchemaSimpleType simpleType || simpleType.Content is not XmlSchemaSimpleTypeUnion unionType) return typeof(string); + + var baseMemberEffectiveTypes = unionType.BaseMemberTypes.Select(t => + { + var restriction = t.Content as XmlSchemaSimpleTypeRestriction; + var facets = restriction?.Facets.OfType().ToList(); + var restrictions = GetRestrictions(facets, t, configuration).Where(r => r != null).Sanitize().ToList(); + return GetEffectiveType(t.Datatype, configuration, restrictions, t, attribute); + }).ToList(); + + // all member types are the same + if (baseMemberEffectiveTypes.Distinct().Count() == 1) return baseMemberEffectiveTypes[0]; + + // all member types are integer types + if (baseMemberEffectiveTypes.All(t => intTypes.Contains(t))) + { + var maxTypeIndex = baseMemberEffectiveTypes.Max(t => Array.IndexOf(intTypes, t)); + var maxType = intTypes[maxTypeIndex]; + // if the max type is signed and the corresponding unsigned type is also in the set we have to use the next higher type + if (maxTypeIndex % 2 == 1 && baseMemberEffectiveTypes.Any(t => Array.IndexOf(intTypes, t) == maxTypeIndex - 1)) + return intTypes[maxTypeIndex + 1]; + return maxType; + } + + // all member types are float/double/decimal + if (baseMemberEffectiveTypes.All(t => decimalTypes.Contains(t))) + { + var maxTypeIndex = baseMemberEffectiveTypes.Max(t => Array.IndexOf(decimalTypes, t)); + var maxType = decimalTypes[maxTypeIndex]; + return maxType; + } + + return typeof(string); + } + + public static IEnumerable GetRestrictions(IEnumerable facets, XmlSchemaSimpleType type, GeneratorConfiguration _configuration) + { + var min = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Max(); + var max = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Min(); + + if (_configuration.DataAnnotationMode == DataAnnotationMode.All) + { + if (min > 0) yield return new MinLengthRestrictionModel(_configuration) { Value = min }; + if (max > 0) yield return new MaxLengthRestrictionModel(_configuration) { Value = max }; + } + else if (min > 0 || max > 0) + { + yield return new MinMaxLengthRestrictionModel(_configuration) { Min = min, Max = max }; + } + + foreach (var facet in facets) + { + var valueType = type.Datatype.ValueType; + switch (facet) + { + case XmlSchemaLengthFacet: + var value = int.Parse(facet.Value); + if (_configuration.DataAnnotationMode == DataAnnotationMode.All) + { + yield return new MinLengthRestrictionModel(_configuration) { Value = value }; + yield return new MaxLengthRestrictionModel(_configuration) { Value = value }; + } + else + { + yield return new MinMaxLengthRestrictionModel(_configuration) { Min = value, Max = value }; + } + break; + case XmlSchemaTotalDigitsFacet: + yield return new TotalDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; break; + case XmlSchemaFractionDigitsFacet: + yield return new FractionDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; break; + case XmlSchemaPatternFacet: + yield return new PatternRestrictionModel(_configuration) { Value = facet.Value }; break; + case XmlSchemaMinInclusiveFacet: + yield return new MinInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + case XmlSchemaMinExclusiveFacet: + yield return new MinExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + case XmlSchemaMaxInclusiveFacet: + yield return new MaxInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + case XmlSchemaMaxExclusiveFacet: + yield return new MaxExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; + } + } + } + public static XmlQualifiedName GetQualifiedName(this XmlSchemaType schemaType) { return schemaType.QualifiedName.IsEmpty diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index a81d2abe..547d3816 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -639,7 +639,7 @@ XmlSchemaSimpleTypeUnion typeUnion when AllMembersHaveFacets(typeUnion, out base if (enumFacets.Count > 0 && (baseFacets is null || baseFacets.All(fs => fs.OfType().Any())) && !_configuration.EnumAsString) return CreateEnumModel(simpleType, enumFacets); - restrictions = GetRestrictions(facets, simpleType).Where(r => r != null).Sanitize().ToList(); + restrictions = CodeUtilities.GetRestrictions(facets, simpleType, _configuration).Where(r => r != null).Sanitize().ToList(); } return CreateSimpleModel(simpleType, restrictions ?? new()); @@ -653,56 +653,6 @@ static bool AllMembersHaveFacets(XmlSchemaSimpleTypeUnion typeUnion, out List GetRestrictions(IEnumerable facets, XmlSchemaSimpleType type) - { - var min = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Max(); - var max = facets.OfType().Select(f => int.Parse(f.Value)).DefaultIfEmpty().Min(); - - if (_configuration.DataAnnotationMode == DataAnnotationMode.All) - { - if (min > 0) yield return new MinLengthRestrictionModel(_configuration) { Value = min }; - if (max > 0) yield return new MaxLengthRestrictionModel(_configuration) { Value = max }; - } - else if (min > 0 || max > 0) - { - yield return new MinMaxLengthRestrictionModel(_configuration) { Min = min, Max = max }; - } - - foreach (var facet in facets) - { - var valueType = type.Datatype.ValueType; - switch (facet) - { - case XmlSchemaLengthFacet: - var value = int.Parse(facet.Value); - if (_configuration.DataAnnotationMode == DataAnnotationMode.All) - { - yield return new MinLengthRestrictionModel(_configuration) { Value = value }; - yield return new MaxLengthRestrictionModel(_configuration) { Value = value }; - } - else - { - yield return new MinMaxLengthRestrictionModel(_configuration) { Min = value, Max = value }; - } - break; - case XmlSchemaTotalDigitsFacet: - yield return new TotalDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; break; - case XmlSchemaFractionDigitsFacet: - yield return new FractionDigitsRestrictionModel(_configuration) { Value = int.Parse(facet.Value) }; break; - case XmlSchemaPatternFacet: - yield return new PatternRestrictionModel(_configuration) { Value = facet.Value }; break; - case XmlSchemaMinInclusiveFacet: - yield return new MinInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; - case XmlSchemaMinExclusiveFacet: - yield return new MinExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; - case XmlSchemaMaxInclusiveFacet: - yield return new MaxInclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; - case XmlSchemaMaxExclusiveFacet: - yield return new MaxExclusiveRestrictionModel(_configuration) { Value = facet.Value, Type = valueType }; break; - } - } - } - private static List EnsureEnumValuesUnique(List enumModelValues) { var enumValueGroups = from enumValue in enumModelValues @@ -775,7 +725,7 @@ private SimpleModel CreateSimpleModel(XmlSchemaSimpleType simpleType, List