Skip to content

Commit

Permalink
Merge pull request #96 from LokiMidgard/InternalVisiblility
Browse files Browse the repository at this point in the history
Internal visiblility
  • Loading branch information
mganss authored Dec 28, 2018
2 parents 0126fc1 + 926b84e commit 746e7b6
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 94 deletions.
3 changes: 3 additions & 0 deletions XmlSchemaClassGenerator.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ static void Main(string[] args)
var entityFramework = false;
var interfaces = true;
var pascal = true;
var assembly = false;
var collectionType = typeof(Collection<>);
Type collectionImplementationType = null;
var codeTypeReferenceOptions = default(CodeTypeReferenceOptions);
Expand Down Expand Up @@ -75,6 +76,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
{ "f|ef", "generate Entity Framework Code First compatible classes", v => entityFramework = v != null },
{ "t|interface", "generate interfaces for groups and attribute groups (default is enabled)", v => interfaces = v != null },
{ "a|pascal", "use Pascal case for class and property names (default is enabled)", v => pascal = v != null },
{ "av|assemblyVisible", "use the internal visibility modifier (default is false)", v => assembly = v != null },
{ "u|enableUpaCheck", "should XmlSchemaSet check for Unique Particle Attribution (UPA) (default is enabled)", v => enableUpaCheck = v != null },
{ "ct|collectionType=", "collection type to use (default is " + typeof(Collection<>).FullName + ")", v => collectionType = v == null ? typeof(Collection<>) : Type.GetType(v, true) },
{ "cit|collectionImplementationType=", "the default collection type implementation to use (default is null)", v => collectionImplementationType = v == null ? null : Type.GetType(v, true) },
Expand Down Expand Up @@ -121,6 +123,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
EntityFramework = entityFramework,
GenerateInterfaces = interfaces,
NamingScheme = pascal ? NamingScheme.PascalCase : NamingScheme.Direct,
AssemblyVisible=assembly,
CollectionType = collectionType,
CollectionImplementationType = collectionImplementationType,
CodeTypeReferenceOptions = codeTypeReferenceOptions,
Expand Down
181 changes: 113 additions & 68 deletions XmlSchemaClassGenerator.Tests/XmlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ private IEnumerable<string> ConvertXml(string name, string xsd, Generator genera
DataAnnotationMode = generatorPrototype.DataAnnotationMode,
GenerateDesignerCategoryAttribute = generatorPrototype.GenerateDesignerCategoryAttribute,
EntityFramework = generatorPrototype.EntityFramework,
AssemblyVisible = generatorPrototype.AssemblyVisible,
GenerateInterfaces = generatorPrototype.GenerateInterfaces,
MemberVisitor = generatorPrototype.MemberVisitor,
CodeTypeReferenceOptions = generatorPrototype.CodeTypeReferenceOptions
CodeTypeReferenceOptions = generatorPrototype.CodeTypeReferenceOptions
};

var set = new XmlSchemaSet();
Expand Down Expand Up @@ -389,12 +390,12 @@ public void DontGenerateElementForEmptyCollectionInChoice()
}


[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""?>
[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"">
Expand All @@ -409,26 +410,26 @@ public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeRef
</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""?>
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>
Expand All @@ -437,54 +438,54 @@ public void MixedTypeMustNotCollideWithExistingMembers()
</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""?>
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]
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 @@ -630,6 +631,50 @@ public void ChoiceMembersAreNullable()
Assert.Contains("Opt4Specified", content);
}

[Fact]
public void AssemblyVisibleIsInternal()
{
// We test to see whether choices which are part of a larger ComplexType are marked as nullable.
// Because nullability isn't directly exposed in the generated C#, we use "XXXSpecified" on a value type
// as a proxy.

const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"" xmlns:xlink=""http://www.w3.org/1999/xlink"" elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
<xs:complexType name=""Root"">
<xs:sequence>
<!-- Choice directly inside a complex type -->
<xs:element name=""Sub"">
<xs:complexType>
<xs:choice>
<xs:element name=""Opt1"" type=""xs:int""/>
<xs:element name=""Opt2"" type=""xs:int""/>
</xs:choice>
</xs:complexType>
</xs:element>
<!-- Choice as part of a larger sequence -->
<xs:choice>
<xs:element name=""Opt3"" type=""xs:int""/>
<xs:element name=""Opt4"" type=""xs:int""/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:schema>";

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

Assert.Contains("internal partial class RootSub", content);
Assert.Contains("internal partial class Root", content);
}

private static void CompareOutput(string expected, string actual)
{
string Normalize(string input) => Regex.Replace(input, @"[ \t]*\r\n", "\n");
Expand Down
6 changes: 6 additions & 0 deletions XmlSchemaClassGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ public NamingScheme NamingScheme
set { _configuration.NamingScheme = value; }
}

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

/// <summary>
/// Emit the "Order" attribute value for XmlElementAttribute to ensure the correct order
/// of the serialized XML elements.
Expand Down
7 changes: 5 additions & 2 deletions XmlSchemaClassGenerator/GeneratorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ public GeneratorConfiguration()
/// Generate <see cref="System.ComponentModel.DescriptionAttribute"/> from XML comments.
/// </summary>
public bool GenerateDescriptionAttribute { get; set; }

/// <summary>
/// Generate types as <c>internal</c> if <c>true</c>. <c>public</c> otherwise.
/// </summary>
public bool AssemblyVisible { get; set; }
/// <summary>
/// Generator Code reference options
/// </summary>
Expand Down Expand Up @@ -172,7 +175,7 @@ public void WriteLog(string message)

public bool DisableComments { get; set; }
public bool DoNotUseUnderscoreInPrivateMemberNames { get; set; }

/// <summary>
/// Check for Unique Particle Attribution (UPA) violations
/// </summary>
Expand Down
53 changes: 29 additions & 24 deletions XmlSchemaClassGenerator/TypeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ public override CodeTypeDeclaration Generate()

classDeclaration.IsClass = true;
classDeclaration.IsPartial = true;
if (Configuration.AssemblyVisible)
{
classDeclaration.TypeAttributes = (classDeclaration.TypeAttributes & ~System.Reflection.TypeAttributes.VisibilityMask) | System.Reflection.TypeAttributes.NestedAssembly;
}


if (Configuration.EnableDataBinding)
{
Expand Down Expand Up @@ -382,29 +387,29 @@ public override CodeTypeDeclaration Generate()
keyProperty.IsKey = true;
}

foreach (var property in Properties.GroupBy(x => x.Name))
{
var propertyIndex = 0;
foreach (var p in property)
{
if (propertyIndex > 0)
{
p.Name += $"_{propertyIndex}";
}
p.AddMembersTo(classDeclaration, Configuration.EnableDataBinding);
propertyIndex++;
}
}

if (IsMixed && (BaseClass == null || (BaseClass is ClassModel && !AllBaseClasses.Any(b => b.IsMixed))))
{
var propName = "Text";

// To not collide with any existing members
for (var propertyIndex = 1; Properties.Any(x => x.Name.Equals(propName, StringComparison.Ordinal)) || propName.Equals(classDeclaration.Name, StringComparison.Ordinal); propertyIndex++)
{
propName = $"Text_{propertyIndex}";
}
foreach (var property in Properties.GroupBy(x => x.Name))
{
var propertyIndex = 0;
foreach (var p in property)
{
if (propertyIndex > 0)
{
p.Name += $"_{propertyIndex}";
}
p.AddMembersTo(classDeclaration, Configuration.EnableDataBinding);
propertyIndex++;
}
}

if (IsMixed && (BaseClass == null || (BaseClass is ClassModel && !AllBaseClasses.Any(b => b.IsMixed))))
{
var propName = "Text";

// To not collide with any existing members
for (var propertyIndex = 1; Properties.Any(x => x.Name.Equals(propName, StringComparison.Ordinal)) || propName.Equals(classDeclaration.Name, StringComparison.Ordinal); propertyIndex++)
{
propName = $"Text_{propertyIndex}";
}
var text = new CodeMemberField(typeof(string[]), propName);
// hack to generate automatic property
text.Name += " { get; set; }";
Expand Down Expand Up @@ -893,7 +898,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi

var editorBrowsableAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(EditorBrowsableAttribute), Configuration.CodeTypeReferenceOptions));
editorBrowsableAttribute.Arguments.Add(new CodeAttributeArgument(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(new CodeTypeReference(typeof(EditorBrowsableState), Configuration.CodeTypeReferenceOptions)), "Never")));
specifiedMember.CustomAttributes.Add(editorBrowsableAttribute);
specifiedMember.CustomAttributes.Add(editorBrowsableAttribute);
member.CustomAttributes.Add(editorBrowsableAttribute);
if (Configuration.EntityFramework) { member.CustomAttributes.Add(notMappedAttribute); }
}
Expand Down

0 comments on commit 746e7b6

Please sign in to comment.