Skip to content

Commit

Permalink
Merge pull request #90 from girtsl/master
Browse files Browse the repository at this point in the history
More fine-grained mapping for xs:integer and derived types
  • Loading branch information
mganss authored Oct 19, 2018
2 parents 72abaaf + 329cbd3 commit dad0192
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 38 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Features
from schema restrictions
* Use [`Collection<T>`](http://msdn.microsoft.com/en-us/library/ms132397.aspx) properties
(initialized in constructor and with private setter)
* Use either int, long, decimal, or string for xs:integer and derived types
* Map xs:integer and derived types to the closest possible .NET type, if not possible - fall back to string. Can be overriden by explicitly defined type (int, long, or decimal)
* Automatic properties
* Pascal case for classes and properties
* Generate nullable adapter properties for optional elements and attributes without default values (see [below](#nullables))
Expand Down Expand Up @@ -255,6 +255,26 @@ Collection types
Values for the `--collectionType` and `--collectionImplementationType` options have to be given in the format accepted by
the [`Type.GetType()`](https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype) method. For the `System.Collections.Generic.List<T>` class this means ``System.Collections.Generic.List`1``.

Integer and derived types
---------------------
Not all numeric types defined by XML Schema can be safely and accurately mapped to .NET numeric data types, however, it's possible to approximate the mapping based on the integer bounds and restrictions such as `totalDigits`.
If an explicit integer type mapping is specified via `--integer=TYPE`, that type will be used, otherwise an approximation will be made based on the following table:

| XML Schema type | totalDigits | C# type|
|-----------------|-------------|---------|
| xs:positiveInteger, xs:nonNegativeInteger| <3 | byte |
| xs:positiveInteger, xs:nonNegativeInteger| <5 | ushort |
| xs:positiveInteger, xs:nonNegativeInteger| <10 | uint |
| xs:positiveInteger, xs:nonNegativeInteger| <20 | ulong |
| xs:positiveInteger, xs:nonNegativeInteger| <30 | decimal |
| xs:positiveInteger, xs:nonNegativeInteger| >=30 | string |
| xs:integer, xs:nonPositiveInteger, xs:negativeInteger| <3 | sbyte |
| xs:integer, xs:nonPositiveInteger, xs:negativeInteger| <5 | short |
| xs:integer, xs:nonPositiveInteger, xs:negativeInteger| <10 | int |
| xs:integer, xs:nonPositiveInteger, xs:negativeInteger| <19 | long |
| xs:integer, xs:nonPositiveInteger, xs:negativeInteger| <29 | decimal |
| xs:integer, xs:nonPositiveInteger, xs:negativeInteger| >=29 | string |

Contributing
------------

Expand Down
177 changes: 163 additions & 14 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Ganss.IO;
using Microsoft.CodeAnalysis;
using Microsoft.Xml.XMLGen;
using System;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
Expand All @@ -12,6 +9,9 @@
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Ganss.IO;
using Microsoft.CodeAnalysis;
using Microsoft.Xml.XMLGen;
using Xunit;

namespace XmlSchemaClassGenerator.Tests
Expand Down Expand Up @@ -347,7 +347,7 @@ public void DontGenerateElementForEmptyCollectionInChoice()
public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeReferenceOptions codeTypeReferenceOptions, string expectedLine)
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:complexType name=""document"">
<xs:attribute name=""some-value"">
<xs:simpleType>
Expand All @@ -358,7 +358,7 @@ public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeRef
</xs:simpleType>
</xs:attribute>
<xs:attribute name=""system"" type=""xs:string""/>
</xs:complexType>
</xs:complexType>
</xs:schema>";

var generatedType = ConvertXml(nameof(EditorBrowsableAttributeRespectsCodeTypeReferenceOptions), xsd, new Generator
Expand All @@ -381,12 +381,12 @@ public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeRef
public void MixedTypeMustNotCollideWithExistingMembers()
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" targetNamespace=""http://local.none"" xmlns:l=""http://local.none"">
<xs:element name=""document"" type=""l:elem"">
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" targetNamespace=""http://local.none"" xmlns:l=""http://local.none"">
<xs:element name=""document"" type=""l:elem"">
</xs:element>
<xs:complexType name=""elem"" mixed=""true"">
<xs:attribute name=""Text"" type=""xs:string""/>
</xs:complexType>
</xs:complexType>
</xs:schema>";

var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
Expand All @@ -406,11 +406,11 @@ public void MixedTypeMustNotCollideWithExistingMembers()
public void MixedTypeMustNotCollideWithContainingTypeName()
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" targetNamespace=""http://local.none"" xmlns:l=""http://local.none"">
<xs:element name=""document"" type=""l:Text"">
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" targetNamespace=""http://local.none"" xmlns:l=""http://local.none"">
<xs:element name=""document"" type=""l:Text"">
</xs:element>
<xs:complexType name=""Text"" mixed=""true"">
</xs:complexType>
<xs:complexType name=""Text"" mixed=""true"">
</xs:complexType>
</xs:schema>";

var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
Expand All @@ -431,7 +431,7 @@ public void MixedTypeMustNotCollideWithContainingTypeName()
public void CollidingAttributeAndPropertyNamesCanBeResolved(params string[] files)
{
// Compilation would previously throw due to duplicate type name within type
var assembly = Compiler.GenerateFiles("AttributesWithSameName", files);
var assembly = Compiler.GenerateFiles("AttributesWithSameName", files);

Assert.NotNull(assembly);
}
Expand Down Expand Up @@ -587,5 +587,154 @@ private static void CompareOutput(string expected, string actual)
string Normalize(string input) => Regex.Replace(input, @"[ \t]*\r\n", "\n");
Assert.Equal(Normalize(expected), Normalize(actual));
}


[Theory]
[InlineData(typeof(decimal), "decimal")]
[InlineData(typeof(long), "long")]
[InlineData(null, "string")]
public void UnmappedIntegerDerivedTypesAreMappedToExpectedCSharpType(Type integerDataType, string expectedTypeName)
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"" elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
<xs:complexType name=""root"">
<xs:sequence>
<xs:element name=""unboundedInteger01"" type=""xs:integer""/>
<xs:element name=""unboundedInteger02"" type=""xs:nonNegativeInteger""/>
<xs:element name=""unboundedInteger03"" type=""xs:positiveInteger""/>
<xs:element name=""unboundedInteger04"" type=""xs:nonPositiveInteger""/>
<xs:element name=""unboundedInteger05"" type=""xs:negativeInteger""/>
<xs:element name=""outOfBoundsInteger01"" type=""tooLongPositiveInteger""/>
<xs:element name=""outOfBoundsInteger02"" type=""tooLongNonNegativeInteger""/>
<xs:element name=""outOfBoundsInteger03"" type=""tooLongInteger""/>
<xs:element name=""outOfBoundsInteger04"" type=""tooLongNegativeInteger""/>
<xs:element name=""outOfBoundsInteger05"" type=""tooLongNonPositiveInteger""/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name=""tooLongPositiveInteger"">
<xs:restriction base=""xs:positiveInteger"">
<xs:totalDigits value=""30""/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name=""tooLongNonNegativeInteger"">
<xs:restriction base=""xs:nonNegativeInteger"">
<xs:totalDigits value=""30""/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name=""tooLongInteger"">
<xs:restriction base=""xs:integer"">
<xs:totalDigits value=""29""/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name=""tooLongNegativeInteger"">
<xs:restriction base=""xs:negativeInteger"">
<xs:totalDigits value=""29""/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name=""tooLongNonPositiveInteger"">
<xs:restriction base=""xs:nonPositiveInteger"">
<xs:totalDigits value=""29""/>
</xs:restriction>
</xs:simpleType>
</xs:schema>";

var generatedType = ConvertXml(nameof(UnmappedIntegerDerivedTypesAreMappedToExpectedCSharpType), xsd, new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test",
},
GenerateNullables = true,
NamingScheme = NamingScheme.PascalCase,
IntegerDataType = integerDataType,
}).First();

Assert.Contains($"public {expectedTypeName} UnboundedInteger01", generatedType);
Assert.Contains($"public {expectedTypeName} UnboundedInteger02", generatedType);
Assert.Contains($"public {expectedTypeName} UnboundedInteger03", generatedType);
Assert.Contains($"public {expectedTypeName} UnboundedInteger04", generatedType);
Assert.Contains($"public {expectedTypeName} UnboundedInteger05", generatedType);
Assert.Contains($"public {expectedTypeName} OutOfBoundsInteger01", generatedType);
Assert.Contains($"public {expectedTypeName} OutOfBoundsInteger02", generatedType);
Assert.Contains($"public {expectedTypeName} OutOfBoundsInteger03", generatedType);
Assert.Contains($"public {expectedTypeName} OutOfBoundsInteger04", generatedType);
Assert.Contains($"public {expectedTypeName} OutOfBoundsInteger05", generatedType);
}

[Theory]
[InlineData("xs:positiveInteger", 1, 2, "byte")]
[InlineData("xs:nonNegativeInteger", 1, 2, "byte")]
[InlineData("xs:integer", 1, 2, "sbyte")]
[InlineData("xs:negativeInteger", 1, 2, "sbyte")]
[InlineData("xs:nonPositiveInteger", 1, 2, "sbyte")]
[InlineData("xs:positiveInteger", 3, 4, "ushort")]
[InlineData("xs:nonNegativeInteger", 3, 4, "ushort")]
[InlineData("xs:integer", 3, 4, "short")]
[InlineData("xs:negativeInteger", 3, 4, "short")]
[InlineData("xs:nonPositiveInteger", 3, 4, "short")]
[InlineData("xs:positiveInteger", 5, 9, "uint")]
[InlineData("xs:nonNegativeInteger", 5, 9, "uint")]
[InlineData("xs:integer", 5, 9, "int")]
[InlineData("xs:negativeInteger", 5, 9, "int")]
[InlineData("xs:nonPositiveInteger", 5, 9, "int")]
[InlineData("xs:positiveInteger", 10, 19, "ulong")]
[InlineData("xs:nonNegativeInteger", 10, 19, "ulong")]
[InlineData("xs:integer", 10, 18, "long")]
[InlineData("xs:negativeInteger", 10, 18, "long")]
[InlineData("xs:nonPositiveInteger", 10, 18, "long")]
[InlineData("xs:positiveInteger", 20, 29, "decimal")]
[InlineData("xs:nonNegativeInteger", 20, 29, "decimal")]
[InlineData("xs:integer", 20, 28, "decimal")]
[InlineData("xs:negativeInteger", 20, 28, "decimal")]
[InlineData("xs:nonPositiveInteger", 20, 28, "decimal")]
public void RestrictedIntegerDerivedTypesAreMappedToExpectedCSharpTypes(string restrictionBase, int totalDigitsRangeFrom, int totalDigitsRangeTo, string expectedTypeName)
{
const string xsdTemplate = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"" elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
<xs:complexType name=""root"">
<xs:sequence>
{0}
</xs:sequence>
</xs:complexType>
{1}
</xs:schema>";

const string elementTemplate = @"<xs:element name=""restrictedInteger{0}"" type=""RestrictedInteger{0}""/>";

const string simpleTypeTemplate = @"
<xs:simpleType name=""RestrictedInteger{1}"">
<xs:restriction base=""{0}"">
<xs:totalDigits value=""{1}""/>
</xs:restriction>
</xs:simpleType>
";

string elementDefinitions = "", simpleTypeDefinitions = "";
for (var i = totalDigitsRangeFrom; i <= totalDigitsRangeTo; i++)
{
elementDefinitions += string.Format(elementTemplate, i);
simpleTypeDefinitions += string.Format(simpleTypeTemplate, restrictionBase, i);
}

var xsd = string.Format(xsdTemplate, elementDefinitions, simpleTypeDefinitions);
var generatedType = ConvertXml(nameof(RestrictedIntegerDerivedTypesAreMappedToExpectedCSharpTypes), xsd,
new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test",
},
GenerateNullables = true,
NamingScheme = NamingScheme.PascalCase,
}).First();

for (var i = totalDigitsRangeFrom; i <= totalDigitsRangeTo; i++)
{
Assert.Contains($"public {expectedTypeName} RestrictedInteger{i}", generatedType);
}
}
}
}
7 changes: 6 additions & 1 deletion XmlSchemaClassGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmlSampleGenerator", "XmlSa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmlSchemaClassGenerator.Console", "XmlSchemaClassGenerator.Console\XmlSchemaClassGenerator.Console.csproj", "{F0000FE1-DE27-4BF9-A179-FC9643A57EEE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xscgen", "xscgen\xscgen.csproj", "{C5C1FF7F-31AD-4D4F-81F3-C9F54516D9D0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "xscgen", "xscgen\xscgen.csproj", "{C5C1FF7F-31AD-4D4F-81F3-C9F54516D9D0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6ED7BE60-B3EC-4CBA-8732-09DA45AC14BF}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Loading

0 comments on commit dad0192

Please sign in to comment.