From 0209ab435f6c0e3470f3960d299b71123c6915f1 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 1 Oct 2019 15:32:18 +0200 Subject: [PATCH 1/5] [fix] resolving xsd dependencies before creating types and elements to make namespace provider work correctly --- XmlSchemaClassGenerator/ModelBuilder.cs | 41 +++++++++++++++++++- XmlSchemaClassGenerator/NamespaceProvider.cs | 3 ++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index 4be64d4c..ac4c3a7f 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -44,14 +44,51 @@ public ModelBuilder(GeneratorConfiguration configuration, XmlSchemaSet set) .DistinctBy(g => g.QualifiedName.ToString()) .ToDictionary(g => g.QualifiedName); - foreach (var globalType in set.GlobalTypes.Values.Cast()) + List dependencyOrder = new List(); + foreach (var schema in set.Schemas().Cast()) + { + ResolveDependencies(schema, dependencyOrder); + } + + foreach (var schema in dependencyOrder) + { + var types = set.GlobalTypes.Values.Cast().Where(s => s.GetSchema() == schema); + CreateTypes(types); + var elements = set.GlobalElements.Values.Cast().Where(s => s.GetSchema() == schema); + CreateElements(elements); + } + } + + private void ResolveDependencies(XmlSchema schema, List dependencyOrder) + { + if (dependencyOrder.Contains(schema)) + return; + + var imports = schema.Includes.OfType(); + if (imports.Any()) + { + foreach (var import in imports) + { + ResolveDependencies(import.Schema, dependencyOrder); + } + } + dependencyOrder.Add(schema); + } + + + private void CreateTypes(IEnumerable types) + { + foreach (var globalType in types) { var schema = globalType.GetSchema(); var source = CodeUtilities.CreateUri(schema?.SourceUri); CreateTypeModel(source, globalType, globalType.QualifiedName); } + } - foreach (var rootElement in set.GlobalElements.Values.Cast()) + private void CreateElements(IEnumerable elements) + { + foreach (var rootElement in elements) { var rootSchema = rootElement.GetSchema(); var source = CodeUtilities.CreateUri(rootSchema.SourceUri); diff --git a/XmlSchemaClassGenerator/NamespaceProvider.cs b/XmlSchemaClassGenerator/NamespaceProvider.cs index fffe5b2f..753b9146 100644 --- a/XmlSchemaClassGenerator/NamespaceProvider.cs +++ b/XmlSchemaClassGenerator/NamespaceProvider.cs @@ -158,6 +158,9 @@ public string FindNamespace(NamespaceKey key, string defaultNamespace = null) var path = key.Source.IsAbsoluteUri ? key.Source.LocalPath : key.Source.OriginalString; keyValues.Add(new NamespaceKey(new Uri(Path.GetFileName(path), UriKind.Relative))); + // Search for both file name and XmlSchemaNamespace pair + keyValues.Add(new NamespaceKey(new Uri(Path.GetFileName(path), UriKind.Relative), key.XmlSchemaNamespace)); + // Search for XmlSchemaNamespace only keyValues.Add(new NamespaceKey(key.XmlSchemaNamespace)); From f5d0c3a26a5a0c97ff2155d6f03bbbf5871b726b Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 1 Oct 2019 20:10:00 +0200 Subject: [PATCH 2/5] [fix] previous commit did not have all unit test passing --- XmlSchemaClassGenerator/ModelBuilder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/XmlSchemaClassGenerator/ModelBuilder.cs b/XmlSchemaClassGenerator/ModelBuilder.cs index ac4c3a7f..46d0c27d 100644 --- a/XmlSchemaClassGenerator/ModelBuilder.cs +++ b/XmlSchemaClassGenerator/ModelBuilder.cs @@ -69,8 +69,9 @@ private void ResolveDependencies(XmlSchema schema, List dependencyOrd { foreach (var import in imports) { - ResolveDependencies(import.Schema, dependencyOrder); - } + if (import.Schema != null) + ResolveDependencies(import.Schema, dependencyOrder); + } } dependencyOrder.Add(schema); } From 48a8473b871fec4c216c10a3a83d6dbc93314298 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 1 Oct 2019 22:56:17 +0200 Subject: [PATCH 3/5] [test] adding unit test for custom csharp namespace testing which validates correct namespace generation for complex schema dependencies supported with commit: 0209ab435f6c0e3470f3960d299b71123c6915f1 --- XmlSchemaClassGenerator.Console/Program.cs | 30 +++++---- .../XmlSchemaClassGenerator.Tests.csproj | 1 + XmlSchemaClassGenerator.Tests/XmlTests.cs | 66 ++++++++++++++++++- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 773ab244..0f5bee7f 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -117,7 +117,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l uris.AddRange(expandedGlob); } - var namespaceMap = namespaces.Select(n => ParseNamespace(n, namespacePrefix)).ToNamespaceProvider(key => + var namespaceMap = namespaces.Select(n => Utility.ParseNamespace(n, namespacePrefix)).ToNamespaceProvider(key => { var xn = key.XmlSchemaNamespace; var name = string.Join(".", xn.Split('/').Where(p => p != "schema" && GeneratorConfiguration.IdentifierRegex.IsMatch(p)) @@ -170,7 +170,21 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l generator.Generate(uris); } - static KeyValuePair ParseNamespace(string nsArg, string namespacePrefix) + static void ShowHelp(OptionSet p) + { + System.Console.WriteLine("Usage: dotnet xscgen [OPTIONS]+ xsdFile..."); + System.Console.WriteLine("Generate C# classes from XML Schema files."); + System.Console.WriteLine("Version " + typeof(Generator).Assembly.GetName().Version); + System.Console.WriteLine(@"xsdFiles may contain globs, e.g. ""content\{schema,xsd}\**\*.xsd"", and URLs."); + System.Console.WriteLine(@"Append - to option to disable it, e.g. --interface-."); + System.Console.WriteLine(); + System.Console.WriteLine("Options:"); + p.WriteOptionDescriptions(System.Console.Out); + } + } + public static class Utility + { + public static KeyValuePair ParseNamespace(string nsArg, string namespacePrefix) { var parts = nsArg.Split(new[] { '=' }, 2); var xmlNs = parts[0]; @@ -184,17 +198,5 @@ static KeyValuePair ParseNamespace(string nsArg, string na } return new KeyValuePair(new NamespaceKey(source, xmlNs), netNs); } - - static void ShowHelp(OptionSet p) - { - System.Console.WriteLine("Usage: dotnet xscgen [OPTIONS]+ xsdFile..."); - System.Console.WriteLine("Generate C# classes from XML Schema files."); - System.Console.WriteLine("Version " + typeof(Generator).Assembly.GetName().Version); - System.Console.WriteLine(@"xsdFiles may contain globs, e.g. ""content\{schema,xsd}\**\*.xsd"", and URLs."); - System.Console.WriteLine(@"Append - to option to disable it, e.g. --interface-."); - System.Console.WriteLine(); - System.Console.WriteLine("Options:"); - p.WriteOptionDescriptions(System.Console.Out); - } } } diff --git a/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj b/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj index ec99af34..299c6e7b 100644 --- a/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj +++ b/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 21ca0b06..8ad94cee 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -14,6 +14,7 @@ using Ganss.IO; using Microsoft.CodeAnalysis; using Microsoft.Xml.XMLGen; +using XmlSchemaClassGenerator.Console; using Xunit; using Xunit.Abstractions; @@ -370,16 +371,77 @@ void TestCompareToXsd(Type t1, Type t2, string file) [Fact, TestPriority(1)] [UseCulture("en-US")] - public void TestBpmn() + public void TestCustomNamespaces() { + string customNsPattern = "|{0}={1}"; + string bpmnXsd = "BPMN20.xsd"; + string semantXsd = "Semantic.xsd"; + string bpmndiXsd = "BPMNDI.xsd"; + string dcXsd = "DC.xsd"; + string diXsd = "DI.xsd"; + + Dictionary xsdToCsharpNsMap = new Dictionary + { + { bpmnXsd, "Namespace1" }, + { semantXsd, "Namespace1" }, + { bpmndiXsd, "Namespace2" }, + { dcXsd, "Namespace3" }, + { diXsd, "Namespace4" } + }; + + Dictionary xsdToCsharpTypeMap = new Dictionary + { + { bpmnXsd, "TDefinitions" }, + { semantXsd, "TActivity" }, + { bpmndiXsd, "BPMNDiagram" }, + { dcXsd, "Font" }, + { diXsd, "DiagramElement" } + }; + + List customNamespaceConfig = new List(); + + foreach (var ns in xsdToCsharpNsMap) + customNamespaceConfig.Add(string.Format(customNsPattern, ns.Key, ns.Value)); + var assembly = Compiler.Generate("Bpmn", BpmnPattern, new Generator { DataAnnotationMode = DataAnnotationMode.All, GenerateNullables = true, - MemberVisitor = (member, model) => { } + MemberVisitor = (member, model) => { }, + NamespaceProvider = customNamespaceConfig.Select(n => Utility.ParseNamespace(n, null)).ToNamespaceProvider() }); Assert.NotNull(assembly); + Type type = null; + + type = assembly.GetTypes().SingleOrDefault(t => t.Name == xsdToCsharpTypeMap[bpmnXsd]); + Assert.NotNull(type); + Assert.Equal(xsdToCsharpNsMap[bpmnXsd], type.Namespace); + + type = assembly.GetTypes().SingleOrDefault(t => t.Name == xsdToCsharpTypeMap[semantXsd]); + Assert.NotNull(type); + Assert.Equal(xsdToCsharpNsMap[semantXsd], type.Namespace); + + type = assembly.GetTypes().SingleOrDefault(t => t.Name == xsdToCsharpTypeMap[bpmndiXsd]); + Assert.NotNull(type); + Assert.Equal(xsdToCsharpNsMap[bpmndiXsd], type.Namespace); + + type = assembly.GetTypes().SingleOrDefault(t => t.Name == xsdToCsharpTypeMap[dcXsd]); + Assert.NotNull(type); + Assert.Equal(xsdToCsharpNsMap[dcXsd], type.Namespace); + + type = assembly.GetTypes().SingleOrDefault(t => t.Name == xsdToCsharpTypeMap[diXsd]); + Assert.NotNull(type); + Assert.Equal(xsdToCsharpNsMap[diXsd], type.Namespace); + } + + [Fact, TestPriority(2)] + [UseCulture("en-US")] + public void TestBpmn() + { + var assembly = Compiler.Generate("Bpmn", BpmnPattern); + Assert.NotNull(assembly); + var type = assembly.GetTypes().SingleOrDefault(t => t.Name == "TDefinitions"); Assert.NotNull(type); From 1645b4f45880056274d006f3ceac7c15229b4578 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 2 Oct 2019 10:29:43 +0200 Subject: [PATCH 4/5] [cleanup] removing dependency on console project from tests by moving ParseNamespace method to CodeUtilities class --- XmlSchemaClassGenerator.Console/Program.cs | 19 +------------------ .../XmlSchemaClassGenerator.Tests.csproj | 1 - XmlSchemaClassGenerator.Tests/XmlTests.cs | 3 +-- XmlSchemaClassGenerator/CodeUtilities.cs | 15 +++++++++++++++ 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/XmlSchemaClassGenerator.Console/Program.cs b/XmlSchemaClassGenerator.Console/Program.cs index 0f5bee7f..3390e2b6 100644 --- a/XmlSchemaClassGenerator.Console/Program.cs +++ b/XmlSchemaClassGenerator.Console/Program.cs @@ -117,7 +117,7 @@ A file name may be given by appending a pipe sign (|) followed by a file name (l uris.AddRange(expandedGlob); } - var namespaceMap = namespaces.Select(n => Utility.ParseNamespace(n, namespacePrefix)).ToNamespaceProvider(key => + var namespaceMap = namespaces.Select(n => CodeUtilities.ParseNamespace(n, namespacePrefix)).ToNamespaceProvider(key => { var xn = key.XmlSchemaNamespace; var name = string.Join(".", xn.Split('/').Where(p => p != "schema" && GeneratorConfiguration.IdentifierRegex.IsMatch(p)) @@ -182,21 +182,4 @@ static void ShowHelp(OptionSet p) p.WriteOptionDescriptions(System.Console.Out); } } - public static class Utility - { - public static KeyValuePair ParseNamespace(string nsArg, string namespacePrefix) - { - var parts = nsArg.Split(new[] { '=' }, 2); - var xmlNs = parts[0]; - var netNs = parts[1]; - var parts2 = xmlNs.Split(new[] { '|' }, 2); - var source = parts2.Length == 2 ? new Uri(parts2[1], UriKind.RelativeOrAbsolute) : null; - xmlNs = parts2[0]; - if (!string.IsNullOrEmpty(namespacePrefix)) - { - netNs = namespacePrefix + "." + netNs; - } - return new KeyValuePair(new NamespaceKey(source, xmlNs), netNs); - } - } } diff --git a/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj b/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj index 299c6e7b..ec99af34 100644 --- a/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj +++ b/XmlSchemaClassGenerator.Tests/XmlSchemaClassGenerator.Tests.csproj @@ -21,7 +21,6 @@ - diff --git a/XmlSchemaClassGenerator.Tests/XmlTests.cs b/XmlSchemaClassGenerator.Tests/XmlTests.cs index 8ad94cee..b1c26181 100644 --- a/XmlSchemaClassGenerator.Tests/XmlTests.cs +++ b/XmlSchemaClassGenerator.Tests/XmlTests.cs @@ -14,7 +14,6 @@ using Ganss.IO; using Microsoft.CodeAnalysis; using Microsoft.Xml.XMLGen; -using XmlSchemaClassGenerator.Console; using Xunit; using Xunit.Abstractions; @@ -408,7 +407,7 @@ public void TestCustomNamespaces() DataAnnotationMode = DataAnnotationMode.All, GenerateNullables = true, MemberVisitor = (member, model) => { }, - NamespaceProvider = customNamespaceConfig.Select(n => Utility.ParseNamespace(n, null)).ToNamespaceProvider() + NamespaceProvider = customNamespaceConfig.Select(n => CodeUtilities.ParseNamespace(n, null)).ToNamespaceProvider() }); Assert.NotNull(assembly); diff --git a/XmlSchemaClassGenerator/CodeUtilities.cs b/XmlSchemaClassGenerator/CodeUtilities.cs index 167c95c7..51cd4133 100644 --- a/XmlSchemaClassGenerator/CodeUtilities.cs +++ b/XmlSchemaClassGenerator/CodeUtilities.cs @@ -297,5 +297,20 @@ internal static string NormalizeNewlines(string text) }; internal static Uri CreateUri(string uri) => string.IsNullOrEmpty(uri) ? null : new Uri(uri); + + public static KeyValuePair ParseNamespace(string nsArg, string namespacePrefix) + { + var parts = nsArg.Split(new[] { '=' }, 2); + var xmlNs = parts[0]; + var netNs = parts[1]; + var parts2 = xmlNs.Split(new[] { '|' }, 2); + var source = parts2.Length == 2 ? new Uri(parts2[1], UriKind.RelativeOrAbsolute) : null; + xmlNs = parts2[0]; + if (!string.IsNullOrEmpty(namespacePrefix)) + { + netNs = namespacePrefix + "." + netNs; + } + return new KeyValuePair(new NamespaceKey(source, xmlNs), netNs); + } } } \ No newline at end of file From 4d4deaaa46cbf5bbc3054a004d2cb4fef6bec0dd Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 2 Oct 2019 11:08:48 +0200 Subject: [PATCH 5/5] [test] adding unit tests for testing ParseNamespace utility method --- .../NamespaceProviderTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/XmlSchemaClassGenerator.Tests/NamespaceProviderTests.cs b/XmlSchemaClassGenerator.Tests/NamespaceProviderTests.cs index f65d2e36..949b96c1 100644 --- a/XmlSchemaClassGenerator.Tests/NamespaceProviderTests.cs +++ b/XmlSchemaClassGenerator.Tests/NamespaceProviderTests.cs @@ -63,5 +63,43 @@ public void NamespaceKeyComparableTest() Assert.False(new NamespaceKey("http://test") <= null); Assert.True(new NamespaceKey("http://test") != null); } + + [Theory] + [InlineData("http://www.w3.org/2001/XMLSchema", "test.xsd", "MyNamespace", "Test")] + [InlineData("http://www.w3.org/2001/XMLSchema", "test.xsd", "MyNamespace", null)] + [InlineData("http://www.w3.org/2001/XMLSchema", "test.xsd", "MyNamespace", "")] + [InlineData("", "test.xsd", "MyNamespace", "Test")] + [InlineData("", "test.xsd", "MyNamespace", null)] + [InlineData("", "test.xsd", "MyNamespace", "")] + [InlineData(null, "test.xsd", "MyNamespace", "Test")] + [InlineData(null, "test.xsd", "MyNamespace", null)] + [InlineData(null, "test.xsd", "MyNamespace", "")] + public void TestParseNamespaceUtilityMethod1(string xmlNs, string xmlSchema, string netNs, string netPrefix) + { + string customNsPattern = "{0}|{1}={2}"; + + var uri = new Uri(xmlSchema, UriKind.RelativeOrAbsolute); + var fullNetNs = (string.IsNullOrEmpty(netPrefix)) ? netNs : string.Join('.', netPrefix, netNs); + + var expected = new KeyValuePair(new NamespaceKey(uri, xmlNs), fullNetNs); + var actual = CodeUtilities.ParseNamespace(string.Format(customNsPattern, xmlNs, xmlSchema, netNs), netPrefix); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("test.xsd", "MyNamespace", "Test")] + [InlineData("test.xsd", "MyNamespace", null)] + [InlineData("test.xsd", "MyNamespace", "")] + public void TestParseNamespaceUtilityMethod2(string xmlSchema, string netNs, string netPrefix) + { + string customNsPattern = "{0}={1}"; + + var fullNetNs = (string.IsNullOrEmpty(netPrefix)) ? netNs : string.Join('.', netPrefix, netNs); + var expected = new KeyValuePair(new NamespaceKey(null, xmlSchema), fullNetNs); + var actual = CodeUtilities.ParseNamespace(string.Format(customNsPattern, xmlSchema, netNs), netPrefix); + + Assert.Equal(expected, actual); + } } }