Skip to content

Commit

Permalink
Merge branch 'master' of github.com:mganss/XmlSchemaClassGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Ganss committed Nov 30, 2018
2 parents 88995ae + dad0192 commit cad751b
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 23 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
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@
<None Update="xml\office_min.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="xml\sameattributenames.xsd">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="xml\sameattributenames_import.xsd">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="xml\seniorCare_max.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
257 changes: 252 additions & 5 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Ganss.IO;
using Microsoft.CodeAnalysis;
using Microsoft.Xml.XMLGen;
using System;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -11,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;
using Xunit.Abstractions;

Expand Down Expand Up @@ -42,6 +43,7 @@ private IEnumerable<string> ConvertXml(string name, string xsd, Generator genera
EntityFramework = generatorPrototype.EntityFramework,
GenerateInterfaces = generatorPrototype.GenerateInterfaces,
MemberVisitor = generatorPrototype.MemberVisitor,
CodeTypeReferenceOptions = generatorPrototype.CodeTypeReferenceOptions
};

var set = new XmlSchemaSet();
Expand Down Expand Up @@ -386,7 +388,103 @@ public void DontGenerateElementForEmptyCollectionInChoice()
Assert.DoesNotContain("tags", xml, StringComparison.OrdinalIgnoreCase);
}

[Fact]

[Theory]
[InlineData(CodeTypeReferenceOptions.GlobalReference, "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]")]
[InlineData((CodeTypeReferenceOptions)0, "[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]")]
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:complexType name=""document"">
<xs:attribute name=""some-value"">
<xs:simpleType>
<xs:restriction base=""xs:string"">
<xs:enumeration value=""one""/>
<xs:enumeration value=""two""/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name=""system"" type=""xs:string""/>
</xs:complexType>
</xs:schema>";

var generatedType = ConvertXml(nameof(EditorBrowsableAttributeRespectsCodeTypeReferenceOptions), xsd, new Generator
{
CodeTypeReferenceOptions = codeTypeReferenceOptions,
GenerateNullables = true,
GenerateInterfaces = false,
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test"
}
});

Assert.Contains(
expectedLine,
generatedType.First());
}

[Fact]
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:element>
<xs:complexType name=""elem"" mixed=""true"">
<xs:attribute name=""Text"" type=""xs:string""/>
</xs:complexType>
</xs:schema>";

var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test"
}
});

Assert.Contains(
@"public string[] Text_1 { get; set; }",
generatedType.First());
}

[Fact]
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:element>
<xs:complexType name=""Text"" mixed=""true"">
</xs:complexType>
</xs:schema>";

var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test"
}
});

Assert.Contains(
@"public string[] Text_1 { get; set; }",
generatedType.First());
}

[Theory]
[InlineData(@"xml/sameattributenames.xsd", @"xml/sameattributenames_import.xsd")]
public void CollidingAttributeAndPropertyNamesCanBeResolved(params string[] files)
{
// Compilation would previously throw due to duplicate type name within type
var assembly = Compiler.GenerateFiles("AttributesWithSameName", files);

Assert.NotNull(assembly);
}

[Fact]
public void ComplexTypeWithAttributeGroupExtension()
{
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
Expand Down Expand Up @@ -537,5 +635,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);
}
}
}
}
16 changes: 16 additions & 0 deletions XmlSchemaClassGenerator.Tests/xml/sameattributenames.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:a="http://none.local/a" xmlns:b="http://none.local/b" elementFormDefault="qualified" targetNamespace="http://none.local/a">
<import namespace="http://none.local/b" schemaLocation="sameattributenames_import.xsd" />
<element name="document" type="a:elem" />
<element name="document2" type="a:elem2" />
<complexType name="elem">
<sequence>
<element name="Type" type="a:elem2" />
</sequence>
<attribute ref="b:type" />
<attribute name="type" type="string" />
</complexType>
<complexType name="elem2">
<attribute name="type" type="string" />
</complexType>
</schema>
10 changes: 10 additions & 0 deletions XmlSchemaClassGenerator.Tests/xml/sameattributenames_import.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://none.local/b" xmlns:b="http://none.local/b">
<xs:attribute name="type" type="b:typeType"/>
<xs:simpleType name="typeType">
<xs:restriction base="xs:token">
<xs:enumeration value="a"/>
<xs:enumeration value="b"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
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 cad751b

Please sign in to comment.