From 6a0e6ecbcdcb9e3ca74732742e1d0e37f28ed1dc Mon Sep 17 00:00:00 2001 From: Patrick Kranz Date: Fri, 28 Dec 2018 14:16:31 +0100 Subject: [PATCH 1/6] Added support to set visibility of classes to internal --- XmlSchemaClassGenerator.Console/Program.cs | 3 ++ XmlSchemaClassGenerator/Generator.cs | 6 +++ .../GeneratorConfiguration.cs | 7 ++- XmlSchemaClassGenerator/TypeModel.cs | 53 ++++++++++--------- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 7296b2c7..6966b8c6 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -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); @@ -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 }, + { "a|assemblyVisible", "use the internal visibillity 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) }, @@ -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, diff --git a/XmlSchemaClassGenerator/Generator.cs b/XmlSchemaClassGenerator/Generator.cs index b0f34d8c..9d1ed80b 100644 --- a/XmlSchemaClassGenerator/Generator.cs +++ b/XmlSchemaClassGenerator/Generator.cs @@ -75,6 +75,12 @@ public NamingScheme NamingScheme set { _configuration.NamingScheme = value; } } + public bool AssemblyVisible + { + get { return _configuration.AssemblyVisible; } + set { _configuration.AssemblyVisible = value; } + } + /// /// Emit the "Order" attribute value for XmlElementAttribute to ensure the correct order /// of the serialized XML elements. diff --git a/XmlSchemaClassGenerator/GeneratorConfiguration.cs b/XmlSchemaClassGenerator/GeneratorConfiguration.cs index 343f7db4..497fd4f4 100644 --- a/XmlSchemaClassGenerator/GeneratorConfiguration.cs +++ b/XmlSchemaClassGenerator/GeneratorConfiguration.cs @@ -129,7 +129,10 @@ public GeneratorConfiguration() /// Generate from XML comments. /// public bool GenerateDescriptionAttribute { get; set; } - + /// + /// Generate types as internal if true. public otherwise. + /// + public bool AssemblyVisible { get; set; } /// /// Generator Code reference options /// @@ -172,7 +175,7 @@ public void WriteLog(string message) public bool DisableComments { get; set; } public bool DoNotUseUnderscoreInPrivateMemberNames { get; set; } - + /// /// Check for Unique Particle Attribution (UPA) violations /// diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index 225e4501..7fbe02c5 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -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) { @@ -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; }"; @@ -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); } } From 9951829b4229f85b22b2b4eae9bac65d63b7ca53 Mon Sep 17 00:00:00 2001 From: Patrick Kranz Date: Fri, 28 Dec 2018 14:16:54 +0100 Subject: [PATCH 2/6] Added test for internal visibility. --- XmlSchemaClassGenerator.Tests/XmlTests.cs | 181 ++++++++++++++-------- 1 file changed, 113 insertions(+), 68 deletions(-) diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index bf6bac67..b830e08d 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -41,9 +41,10 @@ private IEnumerable 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(); @@ -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 = @" + [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 = @" @@ -409,26 +410,26 @@ public void EditorBrowsableAttributeRespectsCodeTypeReferenceOptions(CodeTypeRef "; - 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 = @" + 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 = @" @@ -437,23 +438,23 @@ public void MixedTypeMustNotCollideWithExistingMembers() "; - 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 = @" + 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 = @" @@ -461,30 +462,30 @@ public void MixedTypeMustNotCollideWithContainingTypeName() "; - 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 = @" @@ -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 = @" + + + + + + + + + + + + + + + + + + + +"; + + 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"); From 7272a77b677c3772b2f98a3cdec4b6e6523230a5 Mon Sep 17 00:00:00 2001 From: Patrick Kranz Date: Fri, 28 Dec 2018 16:20:13 +0100 Subject: [PATCH 3/6] Fix Typo --- XmlSchemaClassGenerator.Console/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 6966b8c6..2259deb9 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -76,7 +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 }, - { "a|assemblyVisible", "use the internal visibillity modifier (default is false)", v => assembly = v != null }, + { "a|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) }, From 926b84eb1d299ba7b2d4941a85aa6c0d9c5dd6b6 Mon Sep 17 00:00:00 2001 From: Patrick Kranz Date: Fri, 28 Dec 2018 17:32:30 +0100 Subject: [PATCH 4/6] Fix already taken -a option now -av --- XmlSchemaClassGenerator.Console/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 2259deb9..fd12c561 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -76,7 +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 }, - { "a|assemblyVisible", "use the internal visibility modifier (default is false)", v => assembly = 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) }, From 1baf01655cdc153d18677a2610e05d79483b8247 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Thu, 3 Jan 2019 13:54:30 +0000 Subject: [PATCH 5/6] Expose XmlSchemaAttribute.FixedValue --- XmlSchemaClassGenerator/ModelBuilder.cs | 2 ++ XmlSchemaClassGenerator/TypeModel.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index e8c69ba2..81347c5e 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -448,6 +448,7 @@ private IEnumerable CreatePropertiesForAttributes(Uri source, Typ IsAttribute = true, IsNullable = attribute.Use != XmlSchemaUse.Required, DefaultValue = attribute.DefaultValue ?? (attribute.Use != XmlSchemaUse.Optional ? attribute.FixedValue : null), + FixedValue = attribute.FixedValue, Form = attribute.Form == XmlSchemaForm.None ? attribute.GetSchema().AttributeFormDefault : attribute.Form, XmlNamespace = attribute.QualifiedName.Namespace != "" && attribute.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace ? attribute.QualifiedName.Namespace : null, }; @@ -522,6 +523,7 @@ private IEnumerable CreatePropertiesForElements(Uri source, TypeM IsNullable = item.MinOccurs < 1.0m || (item.XmlParent is XmlSchemaChoice), IsCollection = item.MaxOccurs > 1.0m || particle.MaxOccurs > 1.0m, // http://msdn.microsoft.com/en-us/library/vstudio/d3hx2s7e(v=vs.100).aspx DefaultValue = element.DefaultValue ?? ((item.MinOccurs >= 1.0m && !(item.XmlParent is XmlSchemaChoice)) ? element.FixedValue : null), + FixedValue = element.FixedValue, Form = element.Form == XmlSchemaForm.None ? element.GetSchema().ElementFormDefault : element.Form, XmlNamespace = element.QualifiedName.Namespace != "" && element.QualifiedName.Namespace != typeModel.XmlSchemaName.Namespace ? element.QualifiedName.Namespace : null, XmlParticle = item.XmlParticle, diff --git a/XmlSchemaClassGenerator/TypeModel.cs b/XmlSchemaClassGenerator/TypeModel.cs index 7fbe02c5..76eba060 100644 --- a/XmlSchemaClassGenerator/TypeModel.cs +++ b/XmlSchemaClassGenerator/TypeModel.cs @@ -499,6 +499,7 @@ public class PropertyModel public bool IsNillable { get; set; } public bool IsCollection { get; set; } public string DefaultValue { get; set; } + public string FixedValue { get; set; } public XmlSchemaForm Form { get; set; } public string XmlNamespace { get; set; } public List Documentation { get; private set; } From 6c3880bef37d91f9ad7782c1ed97f2dd12372351 Mon Sep 17 00:00:00 2001 From: Michael Ganss Date: Wed, 9 Jan 2019 12:26:47 +0100 Subject: [PATCH 6/6] Add hint for empty XML namespaces See also #51 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index c1d01b65..0351c34d 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,16 @@ Using the optional `|` syntax of the `-n` command line option you can map indivi dotnet-xscgen.exe -n "|a.xsd=Example.NamespaceA" -n "|b.xsd=Example.NamespaceB" a.xsd b.xsd ``` +#### Mapping empty XML namespaces + +In order to provide a C# namespace name for an empty XML namespace you can specify it on the command line like this: + +``` +XmlSchemaClassGenerator.Console.exe -n =Example example.xsd +``` + +Note the space between `-n` and `=Example`. + Nullables ---------------------------------