Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,733 changes: 5,733 additions & 0 deletions GeneratedSchemaLibraries/Microsoft Search/Microsoft.Search.Query.xsd.cs

Large diffs are not rendered by default.

246 changes: 246 additions & 0 deletions XObjectsCode/Src/Scriban/ScribanGlobals.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#nullable enable
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Scriban;

namespace Xml.Schema.Linq.CodeGen.Scriban;

static class ScribanGlobals
{
public static void Comments(TemplateContext ctx, object target)
{
var comments = target switch
{
CodeTypeDeclaration decl => decl.Comments,
CodeTypeMember m => m.Comments,
_ => null,
};

if (comments == null || comments.Count == 0)
return;

foreach (CodeCommentStatement c in comments)
ctx.Write($"/// {c.Comment.Text.Replace("\n", "\n///")}\n");
ctx.ResetPreviousNewLine();
ctx.Write(ctx.CurrentIndent);
}

public static IEnumerable<CodeTypeDeclaration> Classes(CodeTypeDeclaration type)
{
return type.Members
.OfType<CodeTypeDeclaration>()
.Where(x => !x.IsEnum && !x.Name.EndsWith("EnumValidator"));
}

public static CodeTypeDeclaration EnumDecl(string enumName, CodeTypeDeclaration type)
{
enumName = enumName.TrimEnd('?');
return type.Members
.OfType<CodeTypeDeclaration>()
.First(x => x.IsEnum && x.Name == enumName);
}

public static CodeConstructor? Ctor(CodeTypeDeclaration type, int args = 0)
{
return type.Members
.OfType<CodeConstructor>()
.FirstOrDefault(x => x.Parameters.Count == args);
}

public static IEnumerable<CodeMemberProperty> Properties(CodeTypeDeclaration type)
{
return type.Members
.OfType<CodeMemberProperty>()
.Where(x =>
// Exclude properties like SchemaName, TypeOrigin or TypeManager
!x.CustomAttributes.Cast<CodeAttributeDeclaration>().Any(a => a.Name == "DebuggerBrowsable") &&
x.Name != "TypedValue");
}

public static bool IsList(CodeMemberProperty prop)
{
return prop.Type.BaseType == "IList`1";
}

public static bool IsElement(CodeMemberProperty prop)
{
return IsList(prop) ||
prop.GetStatements[0] is CodeVariableDeclarationStatement { Type.BaseType: "XElement" };
}

public static IEnumerable<CodeMemberProperty> Elements(CodeTypeDeclaration type)
{
return Properties(type).Where(IsElement);
}

public static bool HasElements(CodeTypeDeclaration type)
{
return Elements(type).Any();
}

public static bool IsOptional(CodeMemberProperty prop)
{
return prop.Comments
.Cast<CodeCommentStatement>()
.Any(x => x.Comment.Text.Contains("Occurrence: optional"));
}

public static bool IsTypeDefinition(CodeTypeDeclaration type)
{
return type.TypeAttributes.HasFlag(TypeAttributes.Sealed);
}

public static string? Validator(CodeTypeDeclaration type)
{
var statement = type.Members
.OfType<CodeMemberProperty>()
.First(x => x.Name == "TypedValue")
.SetStatements[0];

if (statement is not CodeExpressionStatement
{
Expression: CodeMethodInvokeExpression
{
Method.MethodName: "SetValueWithValidation",
Parameters: [_, _, CodeFieldReferenceExpression
{
TargetObject: CodeTypeReferenceExpression
{
Type: var typeRef
}
}]
}
})
return null;

return typeRef.BaseType;
}

public static string? SimpleType(CodeTypeDeclaration type)
{
var typeDecl = type.Members
.OfType<CodeMemberProperty>()
.FirstOrDefault(x => x.Name == "TypedValue")
?.Type;
return typeDecl != null ? TypeName(typeDecl, nullable: false) : null;
}

public static IEnumerable<string> EnumValues(CodeTypeDeclaration enumType)
{
return enumType.Members
.OfType<CodeMemberField>()
.Select(x => x.Name);
}

public static string? DefaultValue(CodeMemberProperty prop, CodeTypeDeclaration type)
{
var name = prop.Name + "DefaultValue";
var init = type.Members
.OfType<CodeMemberField>()
.FirstOrDefault(x => x.Name == name)
?.InitExpression;

return init switch
{
CodeMethodInvokeExpression i => $"{i.Method.MethodName}(\"{ ((CodePrimitiveExpression)i.Parameters[0]).Value }\")",
CodePrimitiveExpression p => (string)p.Value,
CodeFieldReferenceExpression f => f.FieldName,
_ => null,
};
}

public static string LocalName(CodeTypeDeclaration type, string name)
{
var init = type.Members
.OfType<CodeMemberField>()
.FirstOrDefault(x => x.Name == name)
?.InitExpression as CodeMethodInvokeExpression;
if (init is null) return "TODO: review null";
return (string)(init.Parameters[0] as CodePrimitiveExpression)!.Value;
}

public static string Namespace(CodeTypeDeclaration type, string name)
{
var init = type.Members
.OfType<CodeMemberField>()
.FirstOrDefault(x => x.Name == name)
?.InitExpression as CodeMethodInvokeExpression;
if (init is null) return "TODO: review null";
return (string)(init.Parameters[1] as CodePrimitiveExpression)!.Value;
}

public static bool HasContentModel(CodeTypeDeclaration type)
{
return type.Members
.OfType<CodeMemberField>()
.Any(x => x.Name == "contentModel");
}

public static string TypeName(CodeTypeReference type, bool nullable = true)
{
if (type.ArrayElementType != null)
return TypeName(type.ArrayElementType) + "[]";

var name = type.BaseType;

if (type.TypeArguments.Count > 0)
{
return Regex.Replace(name, @"`\d+$", "")
+ "<"
+ string.Join(", ", type.TypeArguments.Cast<CodeTypeReference>().Select(x => TypeName(x)))
+ ">";
}

if (!nullable)
name = name.TrimEnd('?');

return name switch
{
"System.Boolean" => "bool",
"System.Byte" => "byte",
"System.Int32" => "int",
"System.String" => "string",
_ => name,
};
}

public static string ListElement(string typeName)
{
return Regex.Match(typeName, "^IList<([^>]+)>$") is { Success: true, Groups: var g }
? g[1].Value
: typeName;
}

public static string XmlTypeCode(object type, string? name = null)
{
// HACK: this is plain wrong but a shortcut to make the proof of concept match 100% without going to deep in CodeDom analysis
if (name == "language") return "XmlTypeCode.Language";

if (type is CodeTypeReference typeRef)
type = TypeName(typeRef, nullable: false);

return type switch
{
"bool" => "XmlTypeCode.Boolean",
"byte[]" => "XmlTypeCode.Base64Binary",
"int" => "XmlTypeCode.Int",
"string" => "XmlTypeCode.String",
_ => "TODO",
};
}

public static IEnumerable<CodeTypeDeclaration> AllTypes(CodeNamespace ns)
{
return ns.Types
.Cast<CodeTypeDeclaration>()
.Where(x => x.Name is not ("XRootNamespace" or "XRoot" or "LinqToXsdTypeManager"));
}

public static IEnumerable<CodeTypeDeclaration> ElementTypes(CodeNamespace ns)
{
return AllTypes(ns).Where(x => !IsTypeDefinition(x));
}
}
33 changes: 33 additions & 0 deletions XObjectsCode/Src/Scriban/TemplateLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Scriban;
using Scriban.Parsing;
using Scriban.Runtime;
using System.IO;
using System.Threading.Tasks;

namespace Xml.Schema.Linq.CodeGen.Scriban;

class TemplateLoader : ITemplateLoader
{
static string FullPath(string name)
{
return Path.Combine(
Path.GetDirectoryName(typeof(TemplateLoader).Assembly.Location),
"Templates",
name);
}

public static Template Load(string name)
{
var file = FullPath(name);
return Template.Parse(File.ReadAllText(file), file);
}

public string GetPath(TemplateContext context, SourceSpan callerSpan, string templateName)
=> FullPath(templateName);

public string Load(TemplateContext context, SourceSpan callerSpan, string templatePath)
=> File.ReadAllText(templatePath);

public ValueTask<string> LoadAsync(TemplateContext context, SourceSpan callerSpan, string templatePath)
=> new ValueTask<string>(File.ReadAllText(templatePath));
}
32 changes: 25 additions & 7 deletions XObjectsCode/Src/XObjectsCoreGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using Scriban;
using Scriban.Runtime;
using Xml.Schema.Linq.CodeGen;
using Xml.Schema.Linq.CodeGen.Scriban;
using Xml.Schema.Linq.Extensions;
using XObjects;

Expand Down Expand Up @@ -143,8 +147,9 @@ public static Dictionary<string, TextWriter> Generate(IEnumerable<string> xsdFil
return GenerateCodeCompileUnits(schemaSet, settings)
.Select(x =>
{
var writer = x.unit.ToStringWriter();
var writer = new StringWriter(new StringBuilder(x.code));

// TODO: put directly in template
if (settings.NullableReferences)
{
// HACK: CodeDom doesn't allow us to add #pragmas.
Expand All @@ -168,7 +173,7 @@ public static Dictionary<string, TextWriter> Generate(IEnumerable<string> xsdFil
/// </returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="schemaSet"/> is <see langword="null"/></exception>
/// <exception cref="T:System.ArgumentNullException"><paramref name="settings"/> is <see langword="null"/></exception>
public static IEnumerable<(string clrNamespace, CodeCompileUnit unit)> GenerateCodeCompileUnits(XmlSchemaSet schemaSet, LinqToXsdSettings settings)
public static IEnumerable<(string clrNamespace, string code)> GenerateCodeCompileUnits(XmlSchemaSet schemaSet, LinqToXsdSettings settings)
{
if (schemaSet == null) throw new ArgumentNullException(nameof(schemaSet));
if (settings == null) throw new ArgumentNullException(nameof(settings));
Expand All @@ -178,16 +183,29 @@ public static Dictionary<string, TextWriter> Generate(IEnumerable<string> xsdFil
var codeGenerator = new CodeDomTypesGenerator(settings);
var namespaces = codeGenerator.GenerateTypes(mapping);

var template = TemplateLoader.Load("file.scriban-cs");

return settings.SplitFilesByNamespace
? namespaces.GroupBy(ns => ns.Name).Select(g => (g.Key, BuildUnit(g)))
: new[] { ((string)null, BuildUnit(namespaces)) };

static CodeCompileUnit BuildUnit(IEnumerable<CodeNamespace> namespaces)
// TODO: rename
string BuildUnit(IEnumerable<CodeNamespace> namespaces)
{
var ccu = new CodeCompileUnit();
foreach (var ns in namespaces)
ccu.Namespaces.Add(ns);
return ccu;
var globals = new ScriptObject();
globals.Import(typeof(ScribanGlobals));
globals.Import(
new { Settings = settings, Namespaces = namespaces.ToArray() },
renamer: m => m.Name);

var context = new TemplateContext()
{
MemberRenamer = m => m.Name,
TemplateLoader = new TemplateLoader(),
};
context.PushGlobal(globals);

return template.Render(context);
}
}

Expand Down
Loading
Loading