diff --git a/XmlSchemaClassGenerator.SourceGenerator.Tests/Samples/simple_schema.xsd b/XmlSchemaClassGenerator.SourceGenerator.Tests/Samples/simple_schema.xsd new file mode 100644 index 00000000..c89fcff4 --- /dev/null +++ b/XmlSchemaClassGenerator.SourceGenerator.Tests/Samples/simple_schema.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XmlSchemaClassGenerator.SourceGenerator.Tests/SimpleSchemaTests.cs b/XmlSchemaClassGenerator.SourceGenerator.Tests/SimpleSchemaTests.cs new file mode 100644 index 00000000..ff18e144 --- /dev/null +++ b/XmlSchemaClassGenerator.SourceGenerator.Tests/SimpleSchemaTests.cs @@ -0,0 +1,21 @@ +using Xunit; + +namespace XmlSchemaClassGenerator.SourceGenerator.Tests +{ + public class SimpleSchemaTests + { + [Fact] + public void Compiles() + { + new Sample.Generated.MyRootElement + { + Child1 = true, + Child2 = "foo", + ComplexChild1 = new Sample.Generated.MyRootElementComplexChild1 + { + Child11 = null + } + }; + } + } +} diff --git a/XmlSchemaClassGenerator.SourceGenerator.Tests/XmlSchemaClassGenerator.SourceGenerator.Tests.csproj b/XmlSchemaClassGenerator.SourceGenerator.Tests/XmlSchemaClassGenerator.SourceGenerator.Tests.csproj new file mode 100644 index 00000000..c2dd5eba --- /dev/null +++ b/XmlSchemaClassGenerator.SourceGenerator.Tests/XmlSchemaClassGenerator.SourceGenerator.Tests.csproj @@ -0,0 +1,40 @@ + + + + netcoreapp3.1 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/XmlSchemaClassGenerator.SourceGenerator/XmlSchemaClassGenerator.SourceGenerator.csproj b/XmlSchemaClassGenerator.SourceGenerator/XmlSchemaClassGenerator.SourceGenerator.csproj new file mode 100644 index 00000000..5963eb31 --- /dev/null +++ b/XmlSchemaClassGenerator.SourceGenerator/XmlSchemaClassGenerator.SourceGenerator.csproj @@ -0,0 +1,74 @@ + + + + netstandard2.0 + false + true + + XmlSchemaClassGenerator.SourceGenerator + Sven Hübner + Source generator for POCOs from XSD schema files, based on XmlSchemaClassGenerator + xml;xsd;Schema;Source Generator;poco;XmlSchemaClassGenerator + https://github.com/mganss/XmlSchemaClassGenerator + Apache-2.0 + git + git://github.com/mganss/XmlSchemaClassGenerator + XmlSchemaClassGenerator + true + true + true + snupkg + + + + + + + + + + + + + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XmlSchemaClassGenerator.SourceGenerator/XmlSchemaClassGenerator.SourceGenerator.props b/XmlSchemaClassGenerator.SourceGenerator/XmlSchemaClassGenerator.SourceGenerator.props new file mode 100644 index 00000000..08b674a0 --- /dev/null +++ b/XmlSchemaClassGenerator.SourceGenerator/XmlSchemaClassGenerator.SourceGenerator.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/XmlSchemaClassGenerator.SourceGenerator/XsdSourceGenerator.cs b/XmlSchemaClassGenerator.SourceGenerator/XsdSourceGenerator.cs new file mode 100644 index 00000000..97db1721 --- /dev/null +++ b/XmlSchemaClassGenerator.SourceGenerator/XsdSourceGenerator.cs @@ -0,0 +1,171 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Schema; + +namespace XmlSchemaClassGenerator +{ + [Generator] + public class XsdSourceGenerator : ISourceGenerator + { + internal class MemoryOutputWriter : OutputWriter + { + private readonly bool separateFiles; + private readonly string schemaFileName; + private readonly string prefix; + + public ICollection<(string Name, string Content)> Contents { get; private set; } = new List<(string, string)>(); + + public MemoryOutputWriter(bool separateFiles, string schemaFileName, string prefix) + { + this.separateFiles = separateFiles; + this.schemaFileName = schemaFileName; + this.prefix = prefix; + } + + public override void Write(CodeNamespace cn) + { + var cu = new CodeCompileUnit(); + cu.Namespaces.Add(cn); + + if (separateFiles) + { + WriteSeparateFiles(cn); + } + else + { + using (var writer = new StringWriter()) + { + Write(writer, cu); + Contents.Add(($"{this.prefix ?? string.Empty}{this.schemaFileName}.g.cs", writer.ToString())); + } + } + } + + private void WriteSeparateFiles(CodeNamespace cn) + { + var validName = GetSanitizedName(cn.Name); + var ccu = new CodeCompileUnit(); + var cns = new CodeNamespace(validName); + + cns.Imports.AddRange(cn.Imports.Cast().ToArray()); + cns.Comments.AddRange(cn.Comments); + ccu.Namespaces.Add(cns); + + foreach (CodeTypeDeclaration ctd in cn.Types) + { + var contentName = ctd.Name; + cns.Types.Clear(); + cns.Types.Add(ctd); + using (var writer = new StringWriter()) + { + Write(writer, ccu); + Contents.Add(($"{this.prefix ?? string.Empty}{contentName}.g.cs", writer.ToString())); + } + } + } + + static readonly Regex InvalidCharacters = new Regex($"[{string.Join("", Path.GetInvalidFileNameChars())}]", RegexOptions.Compiled); + + private string GetSanitizedName(string name) => InvalidCharacters.Replace(name, "_"); + } + + public void Execute(GeneratorExecutionContext context) + { +#if DEBUG + if (!Debugger.IsAttached) + { + // Debugger.Launch(); + } +#endif + var sources = GetConfigurations(context); + + foreach (var source in sources) + { + var schemaStr = source.AdditionalText.GetText().ToString(); + var stringReader = new StringReader(schemaStr); + + var schemaSet = new XmlSchemaSet(); + schemaSet.Add(null, XmlReader.Create(stringReader)); + + var generator = new Generator(); + generator.NamespaceProvider.Add(new NamespaceKey(), source.Namespace); + generator.SeparateClasses = source.GenerateSeparateFiles; + MemoryOutputWriter memoryOutputWriter = new MemoryOutputWriter( + source.GenerateSeparateFiles, + Path.GetFileNameWithoutExtension(source.AdditionalText.Path), + source.Prefix); + generator.OutputWriter = memoryOutputWriter; + generator.Generate(schemaSet); + + foreach (var (name, content) in memoryOutputWriter.Contents) + { + context.AddSource(name, content); + } + } + } + + public void Initialize(GeneratorInitializationContext context) + { + // do nothing + } + + static IEnumerable GetConfigurations(GeneratorExecutionContext context) + { + foreach (AdditionalText file in context.AdditionalFiles) + { + AnalyzerConfigOptions fileOptions = context.AnalyzerConfigOptions.GetOptions(file); + if (!fileOptions.TryGetValue("build_metadata.AdditionalFiles.xscgen_namespace", out var @namespace)) + { + @namespace = "Generated"; + } + + if (!fileOptions.TryGetValue("build_metadata.AdditionalFiles.xscgen_prefix", out var prefix)) + { + prefix = null; + } + + bool generateSeparateFiles = + fileOptions.TryGetValue("build_metadata.AdditionalFiles.xscgen_separatefiles", out var generateSeparateFilesStr) && + bool.TryParse(generateSeparateFilesStr, out var parsedGenerateSeparateFiles) && + parsedGenerateSeparateFiles; + + if (Path.GetExtension(file.Path).Equals(".xsd", StringComparison.OrdinalIgnoreCase)) + { + yield return new GenerationSource( + file, + @namespace, + generateSeparateFiles, + prefix); + } + } + } + + sealed class GenerationSource + { + public GenerationSource( + AdditionalText additionalText, + string @namespace, + bool generateSeparateFiles, + string prefix) + { + AdditionalText = additionalText; + Namespace = @namespace; + GenerateSeparateFiles = generateSeparateFiles; + Prefix = prefix; + } + + public AdditionalText AdditionalText { get; } + public string Namespace { get; } + public bool GenerateSeparateFiles { get; } + public string Prefix { get; } + } + } +} diff --git a/XmlSchemaClassGenerator.sln b/XmlSchemaClassGenerator.sln index 23fb93ad..9e36bfda 100644 --- a/XmlSchemaClassGenerator.sln +++ b/XmlSchemaClassGenerator.sln @@ -20,6 +20,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "xscgen-proj", "xscgen-proj\xscgen-proj.csproj", "{2F20FF02-12AB-448B-BC78-DD76DD4C9E66}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XmlSchemaClassGenerator.SourceGenerator", "XmlSchemaClassGenerator.SourceGenerator\XmlSchemaClassGenerator.SourceGenerator.csproj", "{1A4760D7-7618-41E8-BC97-C68566B7A16C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XmlSchemaClassGenerator.SourceGenerator.Tests", "XmlSchemaClassGenerator.SourceGenerator.Tests\XmlSchemaClassGenerator.SourceGenerator.Tests.csproj", "{333F3108-2714-4706-ABA7-4298EFEBFCA7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +54,14 @@ Global {2F20FF02-12AB-448B-BC78-DD76DD4C9E66}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F20FF02-12AB-448B-BC78-DD76DD4C9E66}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F20FF02-12AB-448B-BC78-DD76DD4C9E66}.Release|Any CPU.Build.0 = Release|Any CPU + {1A4760D7-7618-41E8-BC97-C68566B7A16C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A4760D7-7618-41E8-BC97-C68566B7A16C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A4760D7-7618-41E8-BC97-C68566B7A16C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A4760D7-7618-41E8-BC97-C68566B7A16C}.Release|Any CPU.Build.0 = Release|Any CPU + {333F3108-2714-4706-ABA7-4298EFEBFCA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {333F3108-2714-4706-ABA7-4298EFEBFCA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {333F3108-2714-4706-ABA7-4298EFEBFCA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {333F3108-2714-4706-ABA7-4298EFEBFCA7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/XmlSchemaClassGenerator/XmlSchemaClassGenerator.csproj b/XmlSchemaClassGenerator/XmlSchemaClassGenerator.csproj index 233ee716..834ee80b 100644 --- a/XmlSchemaClassGenerator/XmlSchemaClassGenerator.csproj +++ b/XmlSchemaClassGenerator/XmlSchemaClassGenerator.csproj @@ -48,7 +48,7 @@ - +