Skip to content

Commit 746e7b6

Browse files
authored
Merge pull request #96 from LokiMidgard/InternalVisiblility
Internal visiblility
2 parents 0126fc1 + 926b84e commit 746e7b6

File tree

5 files changed

+156
-94
lines changed

5 files changed

+156
-94
lines changed

XmlSchemaClassGenerator.Console/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static void Main(string[] args)
3030
var entityFramework = false;
3131
var interfaces = true;
3232
var pascal = true;
33+
var assembly = false;
3334
var collectionType = typeof(Collection<>);
3435
Type collectionImplementationType = null;
3536
var codeTypeReferenceOptions = default(CodeTypeReferenceOptions);
@@ -75,6 +76,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
7576
{ "f|ef", "generate Entity Framework Code First compatible classes", v => entityFramework = v != null },
7677
{ "t|interface", "generate interfaces for groups and attribute groups (default is enabled)", v => interfaces = v != null },
7778
{ "a|pascal", "use Pascal case for class and property names (default is enabled)", v => pascal = v != null },
79+
{ "av|assemblyVisible", "use the internal visibility modifier (default is false)", v => assembly = v != null },
7880
{ "u|enableUpaCheck", "should XmlSchemaSet check for Unique Particle Attribution (UPA) (default is enabled)", v => enableUpaCheck = v != null },
7981
{ "ct|collectionType=", "collection type to use (default is " + typeof(Collection<>).FullName + ")", v => collectionType = v == null ? typeof(Collection<>) : Type.GetType(v, true) },
8082
{ "cit|collectionImplementationType=", "the default collection type implementation to use (default is null)", v => collectionImplementationType = v == null ? null : Type.GetType(v, true) },
@@ -121,6 +123,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l
121123
EntityFramework = entityFramework,
122124
GenerateInterfaces = interfaces,
123125
NamingScheme = pascal ? NamingScheme.PascalCase : NamingScheme.Direct,
126+
AssemblyVisible=assembly,
124127
CollectionType = collectionType,
125128
CollectionImplementationType = collectionImplementationType,
126129
CodeTypeReferenceOptions = codeTypeReferenceOptions,

XmlSchemaClassGenerator.Tests/XmlTests.cs

Lines changed: 113 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ private IEnumerable<string> ConvertXml(string name, string xsd, Generator genera
4141
DataAnnotationMode = generatorPrototype.DataAnnotationMode,
4242
GenerateDesignerCategoryAttribute = generatorPrototype.GenerateDesignerCategoryAttribute,
4343
EntityFramework = generatorPrototype.EntityFramework,
44+
AssemblyVisible = generatorPrototype.AssemblyVisible,
4445
GenerateInterfaces = generatorPrototype.GenerateInterfaces,
4546
MemberVisitor = generatorPrototype.MemberVisitor,
46-
CodeTypeReferenceOptions = generatorPrototype.CodeTypeReferenceOptions
47+
CodeTypeReferenceOptions = generatorPrototype.CodeTypeReferenceOptions
4748
};
4849

4950
var set = new XmlSchemaSet();
@@ -389,12 +390,12 @@ public void DontGenerateElementForEmptyCollectionInChoice()
389390
}
390391

391392

392-
[Theory]
393-
[InlineData(CodeTypeReferenceOptions.GlobalReference, "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]")]
394-
[InlineData((CodeTypeReferenceOptions)0, "[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]")]
395-
public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeReferenceOptions codeTypeReferenceOptions, string expectedLine)
396-
{
397-
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
393+
[Theory]
394+
[InlineData(CodeTypeReferenceOptions.GlobalReference, "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]")]
395+
[InlineData((CodeTypeReferenceOptions)0, "[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]")]
396+
public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeReferenceOptions codeTypeReferenceOptions, string expectedLine)
397+
{
398+
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
398399
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
399400
<xs:complexType name=""document"">
400401
<xs:attribute name=""some-value"">
@@ -409,26 +410,26 @@ public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeRef
409410
</xs:complexType>
410411
</xs:schema>";
411412

412-
var generatedType = ConvertXml(nameof(EditorBrowsableAttributeRespectsCodeTypeReferenceOptions), xsd, new Generator
413-
{
414-
CodeTypeReferenceOptions = codeTypeReferenceOptions,
415-
GenerateNullables = true,
416-
GenerateInterfaces = false,
417-
NamespaceProvider = new NamespaceProvider
418-
{
419-
GenerateNamespace = key => "Test"
420-
}
421-
});
422-
423-
Assert.Contains(
424-
expectedLine,
425-
generatedType.First());
426-
}
427-
428-
[Fact]
429-
public void MixedTypeMustNotCollideWithExistingMembers()
430-
{
431-
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
413+
var generatedType = ConvertXml(nameof(EditorBrowsableAttributeRespectsCodeTypeReferenceOptions), xsd, new Generator
414+
{
415+
CodeTypeReferenceOptions = codeTypeReferenceOptions,
416+
GenerateNullables = true,
417+
GenerateInterfaces = false,
418+
NamespaceProvider = new NamespaceProvider
419+
{
420+
GenerateNamespace = key => "Test"
421+
}
422+
});
423+
424+
Assert.Contains(
425+
expectedLine,
426+
generatedType.First());
427+
}
428+
429+
[Fact]
430+
public void MixedTypeMustNotCollideWithExistingMembers()
431+
{
432+
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
432433
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" targetNamespace=""http://local.none"" xmlns:l=""http://local.none"">
433434
<xs:element name=""document"" type=""l:elem"">
434435
</xs:element>
@@ -437,54 +438,54 @@ public void MixedTypeMustNotCollideWithExistingMembers()
437438
</xs:complexType>
438439
</xs:schema>";
439440

440-
var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
441-
{
442-
NamespaceProvider = new NamespaceProvider
443-
{
444-
GenerateNamespace = key => "Test"
445-
}
446-
});
447-
448-
Assert.Contains(
449-
@"public string[] Text_1 { get; set; }",
450-
generatedType.First());
451-
}
452-
453-
[Fact]
454-
public void MixedTypeMustNotCollideWithContainingTypeName()
455-
{
456-
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
441+
var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
442+
{
443+
NamespaceProvider = new NamespaceProvider
444+
{
445+
GenerateNamespace = key => "Test"
446+
}
447+
});
448+
449+
Assert.Contains(
450+
@"public string[] Text_1 { get; set; }",
451+
generatedType.First());
452+
}
453+
454+
[Fact]
455+
public void MixedTypeMustNotCollideWithContainingTypeName()
456+
{
457+
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
457458
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" targetNamespace=""http://local.none"" xmlns:l=""http://local.none"">
458459
<xs:element name=""document"" type=""l:Text"">
459460
</xs:element>
460461
<xs:complexType name=""Text"" mixed=""true"">
461462
</xs:complexType>
462463
</xs:schema>";
463464

464-
var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
465-
{
466-
NamespaceProvider = new NamespaceProvider
467-
{
468-
GenerateNamespace = key => "Test"
469-
}
470-
});
471-
472-
Assert.Contains(
473-
@"public string[] Text_1 { get; set; }",
474-
generatedType.First());
475-
}
476-
477-
[Theory]
478-
[InlineData(@"xml/sameattributenames.xsd", @"xml/sameattributenames_import.xsd")]
479-
public void CollidingAttributeAndPropertyNamesCanBeResolved(params string[] files)
480-
{
481-
// Compilation would previously throw due to duplicate type name within type
482-
var assembly = Compiler.GenerateFiles("AttributesWithSameName", files);
483-
484-
Assert.NotNull(assembly);
485-
}
486-
487-
[Fact]
465+
var generatedType = ConvertXml(nameof(MixedTypeMustNotCollideWithExistingMembers), xsd, new Generator
466+
{
467+
NamespaceProvider = new NamespaceProvider
468+
{
469+
GenerateNamespace = key => "Test"
470+
}
471+
});
472+
473+
Assert.Contains(
474+
@"public string[] Text_1 { get; set; }",
475+
generatedType.First());
476+
}
477+
478+
[Theory]
479+
[InlineData(@"xml/sameattributenames.xsd", @"xml/sameattributenames_import.xsd")]
480+
public void CollidingAttributeAndPropertyNamesCanBeResolved(params string[] files)
481+
{
482+
// Compilation would previously throw due to duplicate type name within type
483+
var assembly = Compiler.GenerateFiles("AttributesWithSameName", files);
484+
485+
Assert.NotNull(assembly);
486+
}
487+
488+
[Fact]
488489
public void ComplexTypeWithAttributeGroupExtension()
489490
{
490491
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
@@ -630,6 +631,50 @@ public void ChoiceMembersAreNullable()
630631
Assert.Contains("Opt4Specified", content);
631632
}
632633

634+
[Fact]
635+
public void AssemblyVisibleIsInternal()
636+
{
637+
// We test to see whether choices which are part of a larger ComplexType are marked as nullable.
638+
// Because nullability isn't directly exposed in the generated C#, we use "XXXSpecified" on a value type
639+
// as a proxy.
640+
641+
const string xsd = @"<?xml version=""1.0"" encoding=""UTF-8""?>
642+
<xs:schema xmlns:xs=""http://www.w3.org/2001/XMLSchema"" xmlns:xlink=""http://www.w3.org/1999/xlink"" elementFormDefault=""qualified"" attributeFormDefault=""unqualified"">
643+
<xs:complexType name=""Root"">
644+
<xs:sequence>
645+
<!-- Choice directly inside a complex type -->
646+
<xs:element name=""Sub"">
647+
<xs:complexType>
648+
<xs:choice>
649+
<xs:element name=""Opt1"" type=""xs:int""/>
650+
<xs:element name=""Opt2"" type=""xs:int""/>
651+
</xs:choice>
652+
</xs:complexType>
653+
</xs:element>
654+
<!-- Choice as part of a larger sequence -->
655+
<xs:choice>
656+
<xs:element name=""Opt3"" type=""xs:int""/>
657+
<xs:element name=""Opt4"" type=""xs:int""/>
658+
</xs:choice>
659+
</xs:sequence>
660+
</xs:complexType>
661+
</xs:schema>";
662+
663+
var generator = new Generator
664+
{
665+
NamespaceProvider = new NamespaceProvider
666+
{
667+
GenerateNamespace = key => "Test"
668+
},
669+
AssemblyVisible = true
670+
};
671+
var contents = ConvertXml(nameof(ComplexTypeWithAttributeGroupExtension), xsd, generator);
672+
var content = Assert.Single(contents);
673+
674+
Assert.Contains("internal partial class RootSub", content);
675+
Assert.Contains("internal partial class Root", content);
676+
}
677+
633678
private static void CompareOutput(string expected, string actual)
634679
{
635680
string Normalize(string input) => Regex.Replace(input, @"[ \t]*\r\n", "\n");

XmlSchemaClassGenerator/Generator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ public NamingScheme NamingScheme
7575
set { _configuration.NamingScheme = value; }
7676
}
7777

78+
public bool AssemblyVisible
79+
{
80+
get { return _configuration.AssemblyVisible; }
81+
set { _configuration.AssemblyVisible = value; }
82+
}
83+
7884
/// <summary>
7985
/// Emit the "Order" attribute value for XmlElementAttribute to ensure the correct order
8086
/// of the serialized XML elements.

XmlSchemaClassGenerator/GeneratorConfiguration.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ public GeneratorConfiguration()
129129
/// Generate <see cref="System.ComponentModel.DescriptionAttribute"/> from XML comments.
130130
/// </summary>
131131
public bool GenerateDescriptionAttribute { get; set; }
132-
132+
/// <summary>
133+
/// Generate types as <c>internal</c> if <c>true</c>. <c>public</c> otherwise.
134+
/// </summary>
135+
public bool AssemblyVisible { get; set; }
133136
/// <summary>
134137
/// Generator Code reference options
135138
/// </summary>
@@ -172,7 +175,7 @@ public void WriteLog(string message)
172175

173176
public bool DisableComments { get; set; }
174177
public bool DoNotUseUnderscoreInPrivateMemberNames { get; set; }
175-
178+
176179
/// <summary>
177180
/// Check for Unique Particle Attribution (UPA) violations
178181
/// </summary>

XmlSchemaClassGenerator/TypeModel.cs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ public override CodeTypeDeclaration Generate()
272272

273273
classDeclaration.IsClass = true;
274274
classDeclaration.IsPartial = true;
275+
if (Configuration.AssemblyVisible)
276+
{
277+
classDeclaration.TypeAttributes = (classDeclaration.TypeAttributes & ~System.Reflection.TypeAttributes.VisibilityMask) | System.Reflection.TypeAttributes.NestedAssembly;
278+
}
279+
275280

276281
if (Configuration.EnableDataBinding)
277282
{
@@ -382,29 +387,29 @@ public override CodeTypeDeclaration Generate()
382387
keyProperty.IsKey = true;
383388
}
384389

385-
foreach (var property in Properties.GroupBy(x => x.Name))
386-
{
387-
var propertyIndex = 0;
388-
foreach (var p in property)
389-
{
390-
if (propertyIndex > 0)
391-
{
392-
p.Name += $"_{propertyIndex}";
393-
}
394-
p.AddMembersTo(classDeclaration, Configuration.EnableDataBinding);
395-
propertyIndex++;
396-
}
397-
}
398-
399-
if (IsMixed && (BaseClass == null || (BaseClass is ClassModel && !AllBaseClasses.Any(b => b.IsMixed))))
400-
{
401-
var propName = "Text";
402-
403-
// To not collide with any existing members
404-
for (var propertyIndex = 1; Properties.Any(x => x.Name.Equals(propName, StringComparison.Ordinal)) || propName.Equals(classDeclaration.Name, StringComparison.Ordinal); propertyIndex++)
405-
{
406-
propName = $"Text_{propertyIndex}";
407-
}
390+
foreach (var property in Properties.GroupBy(x => x.Name))
391+
{
392+
var propertyIndex = 0;
393+
foreach (var p in property)
394+
{
395+
if (propertyIndex > 0)
396+
{
397+
p.Name += $"_{propertyIndex}";
398+
}
399+
p.AddMembersTo(classDeclaration, Configuration.EnableDataBinding);
400+
propertyIndex++;
401+
}
402+
}
403+
404+
if (IsMixed && (BaseClass == null || (BaseClass is ClassModel && !AllBaseClasses.Any(b => b.IsMixed))))
405+
{
406+
var propName = "Text";
407+
408+
// To not collide with any existing members
409+
for (var propertyIndex = 1; Properties.Any(x => x.Name.Equals(propName, StringComparison.Ordinal)) || propName.Equals(classDeclaration.Name, StringComparison.Ordinal); propertyIndex++)
410+
{
411+
propName = $"Text_{propertyIndex}";
412+
}
408413
var text = new CodeMemberField(typeof(string[]), propName);
409414
// hack to generate automatic property
410415
text.Name += " { get; set; }";
@@ -893,7 +898,7 @@ public void AddMembersTo(CodeTypeDeclaration typeDeclaration, bool withDataBindi
893898

894899
var editorBrowsableAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(EditorBrowsableAttribute), Configuration.CodeTypeReferenceOptions));
895900
editorBrowsableAttribute.Arguments.Add(new CodeAttributeArgument(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(new CodeTypeReference(typeof(EditorBrowsableState), Configuration.CodeTypeReferenceOptions)), "Never")));
896-
specifiedMember.CustomAttributes.Add(editorBrowsableAttribute);
901+
specifiedMember.CustomAttributes.Add(editorBrowsableAttribute);
897902
member.CustomAttributes.Add(editorBrowsableAttribute);
898903
if (Configuration.EntityFramework) { member.CustomAttributes.Add(notMappedAttribute); }
899904
}

0 commit comments

Comments
 (0)