Skip to content

Commit

Permalink
Merge pull request #107 from RedX2501/do-not-create-intermediary-types
Browse files Browse the repository at this point in the history
Do not create intermediary types for array elements
  • Loading branch information
mganss authored Mar 11, 2019
2 parents 59cc0ba + d158786 commit f61b578
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 17 deletions.
69 changes: 53 additions & 16 deletions XmlSchemaClassGenerator.Tests/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ public static CompilationResult GenerateAssembly(Compilation compilation)

private static ConcurrentDictionary<string, Assembly> Assemblies = new ConcurrentDictionary<string, Assembly>();

private static readonly string[] DependencyAssemblies = new[]
{
"netstandard",
"System.ComponentModel.Annotations",
"System.ComponentModel.Primitives",
"System.Diagnostics.Tools",
"System.Linq",
"System.ObjectModel",
"System.Private.CoreLib",
"System.Private.Xml",
"System.Private.Xml.Linq",
"System.Runtime",
"System.Xml.XDocument",
"System.Xml.XmlSerializer",
};

public static Assembly GetAssembly(string name)
{
Assemblies.TryGetValue(name, out var assembly);
Expand All @@ -62,6 +78,7 @@ public static Assembly GenerateFiles(string name, IEnumerable<string> files, Gen
IntegerDataType = typeof(int),
DataAnnotationMode = DataAnnotationMode.All,
GenerateDesignerCategoryAttribute = false,
GenerateComplexTypesForCollections = true,
EntityFramework = false,
GenerateInterfaces = true,
NamespacePrefix = name,
Expand All @@ -78,6 +95,7 @@ public static Assembly GenerateFiles(string name, IEnumerable<string> files, Gen
IntegerDataType = generatorPrototype.IntegerDataType,
DataAnnotationMode = generatorPrototype.DataAnnotationMode,
GenerateDesignerCategoryAttribute = generatorPrototype.GenerateDesignerCategoryAttribute,
GenerateComplexTypesForCollections = generatorPrototype.GenerateComplexTypesForCollections,
EntityFramework = generatorPrototype.EntityFramework,
GenerateInterfaces = generatorPrototype.GenerateInterfaces,
MemberVisitor = generatorPrototype.MemberVisitor,
Expand All @@ -91,24 +109,9 @@ public static Assembly GenerateFiles(string name, IEnumerable<string> files, Gen

public static Assembly CompileFiles(string name, IEnumerable<string> files)
{
var assemblies = new[]
{
"netstandard",
"System.ComponentModel.Annotations",
"System.ComponentModel.Primitives",
"System.Diagnostics.Tools",
"System.Linq",
"System.ObjectModel",
"System.Private.CoreLib",
"System.Private.Xml",
"System.Private.Xml.Linq",
"System.Runtime",
"System.Xml.XDocument",
"System.Xml.XmlSerializer",
};
var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator);
var references = trustedAssembliesPaths
.Where(p => assemblies.Contains(Path.GetFileNameWithoutExtension(p)))
.Where(p => DependencyAssemblies.Contains(Path.GetFileNameWithoutExtension(p)))
.Select(p => MetadataReference.CreateFromFile(p))
.ToList();
var syntaxTrees = files.Select(f => CSharpSyntaxTree.ParseText(File.ReadAllText(f))).ToList();
Expand All @@ -128,5 +131,39 @@ public static Assembly CompileFiles(string name, IEnumerable<string> files)

return result.Assembly;
}

private static readonly LanguageVersion MaxLanguageVersion = Enum
.GetValues(typeof(LanguageVersion))
.Cast<LanguageVersion>()
.Max();

public static Assembly Compile(string name, string contents, Generator generator)
{
var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator);
var references = trustedAssembliesPaths
.Where(p => DependencyAssemblies.Contains(Path.GetFileNameWithoutExtension(p)))
.Select(p => MetadataReference.CreateFromFile(p))
.ToList();

var options = new CSharpParseOptions(kind: SourceCodeKind.Regular, languageVersion: MaxLanguageVersion);

// Return a syntax tree of our source code
var syntaxTree = CSharpSyntaxTree.ParseText(contents, options);

var compilation = CSharpCompilation.Create(name, new[] { syntaxTree })
.AddReferences(references)
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var result = Compiler.GenerateAssembly(compilation);


Assert.True(result.Result.Success);
var errors = result.Result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
Assert.False(errors.Any(), string.Join("\n", errors.Select(e => e.GetMessage())));
var warnings = result.Result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Warning).ToList();
Assert.False(warnings.Any(), string.Join("\n", errors.Select(w => w.GetMessage())));
Assert.NotNull(result.Assembly);

return result.Assembly;
}
}
}
92 changes: 92 additions & 0 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -40,6 +41,7 @@ private IEnumerable<string> ConvertXml(string name, string xsd, Generator genera
IntegerDataType = generatorPrototype.IntegerDataType,
DataAnnotationMode = generatorPrototype.DataAnnotationMode,
GenerateDesignerCategoryAttribute = generatorPrototype.GenerateDesignerCategoryAttribute,
GenerateComplexTypesForCollections = generatorPrototype.GenerateComplexTypesForCollections,
EntityFramework = generatorPrototype.EntityFramework,
AssemblyVisible = generatorPrototype.AssemblyVisible,
GenerateInterfaces = generatorPrototype.GenerateInterfaces,
Expand Down Expand Up @@ -766,6 +768,96 @@ public void DecimalSeparatorTest()
Assert.Contains("private decimal _someAttr = 1.5m;", content);
}

[Fact]
public void DoNotGenerateIntermediaryClassForArrayElements()
{
// see https://github.com/mganss/XmlSchemaClassGenerator/issues/32

const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema version=""1.0"" targetNamespace=""test""
elementFormDefault=""qualified""
xmlns:test=""test""
xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:element name=""foo"" type=""test:foo""/>
<xs:complexType name=""foo"">
<xs:sequence>
<xs:element name=""bar"" minOccurs=""0"">
<xs:complexType>
<xs:sequence>
<xs:element name=""baz"" type=""xs:string"" minOccurs=""0"" maxOccurs=""unbounded"">
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>";

var generator = new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test",
},
GenerateComplexTypesForCollections = false,
GenerateInterfaces = true,
AssemblyVisible = true
};
var contents = ConvertXml(nameof(ComplexTypeWithAttributeGroupExtension), xsd, generator);
var content = Assert.Single(contents);

var assembly = Compiler.Compile(nameof(DoNotGenerateIntermediaryClassForArrayElements), content, generator);

var fooType = Assert.Single(assembly.DefinedTypes);
Assert.NotNull(fooType);
Assert.True(fooType.FullName == "Test.Foo");
}

[Fact]
public void GenerateIntermediaryClassForArrayElements()
{
// see https://github.com/mganss/XmlSchemaClassGenerator/issues/32

const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema version=""1.0"" targetNamespace=""test""
elementFormDefault=""qualified""
xmlns:test=""test""
xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
<xs:element name=""foo"" type=""test:foo""/>
<xs:complexType name=""foo"">
<xs:sequence>
<xs:element name=""bar"" minOccurs=""0"">
<xs:complexType>
<xs:sequence>
<xs:element name=""baz"" type=""xs:string"" minOccurs=""0"" maxOccurs=""unbounded"">
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>";

var generator = new Generator
{
NamespaceProvider = new NamespaceProvider
{
GenerateNamespace = key => "Test",
},
GenerateComplexTypesForCollections = true,
GenerateInterfaces = true,
AssemblyVisible = true
};
var contents = ConvertXml(nameof(GenerateIntermediaryClassForArrayElements), xsd, generator);
var content = Assert.Single(contents);

var assembly = Compiler.Compile(nameof(GenerateIntermediaryClassForArrayElements), content, generator);

Assert.True(assembly.DefinedTypes.Count() == 2);
Assert.Single(assembly.DefinedTypes, x => x.FullName == "Test.Foo");
Assert.Single(assembly.DefinedTypes, x => x.FullName == "Test.FooBar");
}

[Fact]
public void BoolTest()
{
Expand Down
7 changes: 7 additions & 0 deletions XmlSchemaClassGenerator/FileOutputWriter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.CodeDom;
using System.Collections.Generic;
using System.IO;

namespace XmlSchemaClassGenerator
Expand All @@ -19,6 +20,11 @@ public FileOutputWriter(string directory, bool createIfNotExists = true)

public string OutputDirectory { get; }

/// <summary>
/// A list of all the files written.
/// </summary>
public IList<string> WrittenFiles { get; } = new List<string>();

public override void Write(CodeNamespace cn)
{
var cu = new CodeCompileUnit();
Expand All @@ -42,6 +48,7 @@ protected virtual void WriteFile(string path, CodeCompileUnit cu)
fs = null;
Write(writer, cu);
}
WrittenFiles.Add(path);
}
finally
{
Expand Down
6 changes: 6 additions & 0 deletions XmlSchemaClassGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public OutputWriter OutputWriter
set { _configuration.OutputWriter = value; }
}

public bool GenerateComplexTypesForCollections
{
get { return _configuration.GenerateComplexTypesForCollections; }
set { _configuration.GenerateComplexTypesForCollections = value; }
}

public string NamespacePrefix
{
get { return _configuration.NamespacePrefix; }
Expand Down
46 changes: 46 additions & 0 deletions XmlSchemaClassGenerator/GeneratorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,51 @@ public void WriteLog(string message)
/// Check for Unique Particle Attribution (UPA) violations
/// </summary>
public bool EnableUpaCheck { get; set; }

/// <summary>
/// When a ComplexType has a member that is used as a "collection" around another ComplexType
/// the serializer will output the intermediate ComplexType.
///
/// <code>
/// &lt;xs:element name="books"&gt;
/// &lt;xs:complexType&gt;
/// &lt;xs:sequence&gt;
/// &lt;xs:element name="components"&gt;
/// &lt;xs:complexType&gt;
/// &lt;xs:sequence&gt;
/// &lt;xs:element name="component" type="componentType" maxOccurs="unbounded"/&gt;
/// &lt;/xs:sequence&gt;
/// &lt;/xs:complexType&gt;
/// &lt;/xs:element&gt;
/// &lt;/xs:sequence&gt;
/// &lt;xs:complexType&gt;
/// &lt;/xs:element&gt;
/// </code>
///
/// With <code>false</code> it generates the classes:
///
/// <code>
/// public class books {
/// public Container&lt;componentType&gt; components {get; set;}
/// }
///
/// public class componentType {}
/// </code>
///
/// With <code>true</code> it generates the classes:
///
/// <code>
/// public class books {
/// public Container&lt;componentType&gt; components {get; set;}
/// }
///
/// public class bookscomponents {
/// public Container&lt;componentType&gt; components {get; set;}
/// }
///
/// public class componentType {}
/// </code>
/// </summary>
public bool GenerateComplexTypesForCollections { get; set; } = false;
}
}
5 changes: 5 additions & 0 deletions XmlSchemaClassGenerator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,11 @@ private IEnumerable<PropertyModel> CreatePropertiesForElements(Uri source, TypeM
XmlParticle = item.XmlParticle,
XmlParent = item.XmlParent,
};

if (property.IsArray && !_configuration.GenerateComplexTypesForCollections)
{
property.Type.Namespace.Types.Remove(property.Type.Name);
}
}
else
{
Expand Down
5 changes: 4 additions & 1 deletion XmlSchemaClassGenerator/TypeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,10 @@ private ClassModel TypeClassModel
get { return Type as ClassModel; }
}

private bool IsArray
/// <summary>
/// A property is an array if it is a sequence containing a single element with maxOccurs > 1.
/// </summary>
public bool IsArray
{
get
{
Expand Down

0 comments on commit f61b578

Please sign in to comment.