From 4460de529ebb617dbe5928bb89a8c44e424caad7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 25 Nov 2022 00:56:43 -0500 Subject: [PATCH] Implement multiple documents loading and transforming in AvaloniaRuntimeXamlLoader --- .../DesignWindowLoader.cs | 3 +- .../AvaloniaRuntimeXamlLoader.cs | 46 +++--- .../AvaloniaXamlIlRuntimeCompiler.cs | 133 +++++++++++++----- .../AvaloniaXamlIlCompiler.cs | 48 ++++++- .../IXamlAstGroupTransformer.cs | 78 ++++++++++ .../IXamlDocumentResource.cs | 17 +++ .../AvaloniaXamlIlWellKnownTypes.cs | 9 ++ .../XamlDocumentParseException.cs | 21 +++ .../XamlDocumentResource.cs | 40 ++++++ .../Avalonia.Markup.Xaml.csproj | 1 + .../AvaloniaXamlLoader.cs | 7 +- .../RuntimeXamlLoaderConfiguration.cs | 13 +- .../RuntimeXamlLoaderDocument.cs | 70 +++++++++ .../DesignXamlLoader.cs | 6 +- .../CompiledBindingExtensionTests.cs | 6 +- .../XamlTestBase.cs | 4 +- 16 files changed, 413 insertions(+), 89 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 9a901f909a..b4cfffcdca 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -38,10 +38,9 @@ namespace Avalonia.DesignerSupport var useCompiledBindings = localAsm?.GetCustomAttributes() .FirstOrDefault(a => a.Key == "AvaloniaUseCompiledBindingsByDefault")?.Value; - var loaded = loader.Load(stream, new RuntimeXamlLoaderConfiguration + var loaded = loader.Load(new RuntimeXamlLoaderDocument(baseUri, stream), new RuntimeXamlLoaderConfiguration { LocalAssembly = localAsm, - BaseUri = baseUri, DesignMode = true, UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue }); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs index 9393bb0aa4..eb05844ffb 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using Avalonia.Markup.Xaml.XamlIl; - +#nullable enable namespace Avalonia.Markup.Xaml { public static class AvaloniaRuntimeXamlLoader @@ -17,31 +18,15 @@ namespace Avalonia.Markup.Xaml /// The URI of the XAML being loaded. /// Indicates whether the XAML is being loaded in design mode. /// The loaded object. - public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false) + public static object Load(string xaml, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null, bool designMode = false) { - Contract.Requires(xaml != null); + xaml = xaml ?? throw new ArgumentNullException(nameof(xaml)); using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) { return Load(stream, localAssembly, rootInstance, uri, designMode); } } - - /// - /// Loads XAML from a string. - /// - /// The string containing the XAML. - /// Xaml loader configuration. - /// The loaded object. - public static object Load(string xaml, RuntimeXamlLoaderConfiguration configuration) - { - Contract.Requires(xaml != null); - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) - { - return Load(stream, configuration); - } - } /// /// Loads XAML from a stream. @@ -52,9 +37,10 @@ namespace Avalonia.Markup.Xaml /// The URI of the XAML being loaded. /// Indicates whether the XAML is being loaded in design mode. /// The loaded object. - public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null, + public static object Load(Stream stream, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null, bool designMode = false) - => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode, false); + => AvaloniaXamlIlRuntimeCompiler.Load(new RuntimeXamlLoaderDocument(uri, rootInstance, stream), + new RuntimeXamlLoaderConfiguration { DesignMode = designMode, LocalAssembly = localAssembly }); /// /// Loads XAML from a stream. @@ -62,9 +48,17 @@ namespace Avalonia.Markup.Xaml /// The stream containing the XAML. /// Xaml loader configuration. /// The loaded object. - public static object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) - => AvaloniaXamlIlRuntimeCompiler.Load(stream, configuration.LocalAssembly, configuration.RootInstance, - configuration.BaseUri, configuration.DesignMode, configuration.UseCompiledBindingsByDefault); + public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration? configuration = null) + => AvaloniaXamlIlRuntimeCompiler.Load(document, configuration ?? new RuntimeXamlLoaderConfiguration()); + + /// + /// Loads group of XAML files from a stream. + /// + /// Collection of documents. + /// Xaml loader configuration. + /// The loaded objects per each input document. + public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration? configuration = null) + => AvaloniaXamlIlRuntimeCompiler.LoadGroup(documents, configuration ?? new RuntimeXamlLoaderConfiguration()); /// /// Parse XAML from a string. @@ -72,7 +66,7 @@ namespace Avalonia.Markup.Xaml /// The string containing the XAML. /// Default assembly for clr-namespace:. /// The loaded object. - public static object Parse(string xaml, Assembly localAssembly = null) + public static object Parse(string xaml, Assembly? localAssembly = null) => Load(xaml, localAssembly); /// @@ -82,7 +76,7 @@ namespace Avalonia.Markup.Xaml /// >The string containing the XAML. /// >Default assembly for clr-namespace:. /// The loaded object. - public static T Parse(string xaml, Assembly localAssembly = null) + public static T Parse(string xaml, Assembly? localAssembly = null) => (T)Parse(xaml, localAssembly); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index b4b258e53e..1ee4402481 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Platform; +using XamlX.Ast; using XamlX.Transform; using XamlX.TypeSystem; using XamlX.IL; @@ -150,12 +151,12 @@ namespace Avalonia.Markup.Xaml.XamlIl } - static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault) + static object LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) { var success = false; try { - var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault); + var rv = LoadSreCore(document, configuration); success = true; return rv; } @@ -166,45 +167,100 @@ namespace Avalonia.Markup.Xaml.XamlIl } } + static IReadOnlyList LoadGroupSre(IReadOnlyCollection documents, + RuntimeXamlLoaderConfiguration configuration) + { + var success = false; + try + { + var rv = LoadGroupSreCore(documents, configuration); + success = true; + return rv; + } + finally + { + if( _sreCanSave) + DumpRuntimeCompilationResults(); + } + } - static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault) + static IReadOnlyList LoadGroupSreCore(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration configuration) { - InitializeSre(); + var localAssembly = configuration.LocalAssembly; if (localAssembly?.GetName() != null) EmitIgnoresAccessCheckToAttribute(localAssembly.GetName()); var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly); - var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); - var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N")); + var clrPropertyBuilder = _sreBuilder.DefineType("ClrProperties_" + Guid.NewGuid().ToString("N")); var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N")); var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N")); - + var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm, - _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, - new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), - new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), - new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), + _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, + new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), + new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), + new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), _sreEmitMappings, - _sreContextType) { EnableIlVerification = true, DefaultCompileBindings = useCompiledBindingsByDefault }; + _sreContextType) + { + EnableIlVerification = true, + DefaultCompileBindings = configuration.UseCompiledBindingsByDefault, + IsDesignMode = configuration.DesignMode + }; - IXamlType overrideType = null; - if (rootInstance != null) + var parsedDocuments = new List(); + var rootInstances = new List(); + + foreach (var document in documents) { - overrideType = _sreTypeSystem.GetType(rootInstance.GetType()); + string xaml; + using (var sr = new StreamReader(document.XamlStream)) + xaml = sr.ReadToEnd(); + + IXamlType overrideType = null; + if (document.RootInstance != null) + { + overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType()); + } + + var parsed = compiler.Parse(xaml, overrideType); + compiler.Transform(parsed); + + var xamlName = GetSafeUriIdentifier(document.BaseUri) + ?? document.RootInstance?.GetType().Name + ?? ((IXamlAstValueNode)parsed.Root).Type.GetClrType().Name; + var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + xamlName); + var builder = _sreTypeSystem.CreateTypeBuilder(tb); + parsedDocuments.Add(new XamlDocumentResource(parsed, document.BaseUri?.ToString(), null, null, + builder, + compiler.DefinePopulateMethod(builder, parsed, AvaloniaXamlIlCompiler.PopulateName, true), + compiler.DefineBuildMethod(builder, parsed, AvaloniaXamlIlCompiler.BuildName, true))); + rootInstances.Add(document.RootInstance); } - compiler.IsDesignMode = isDesignMode; - compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType); - var created = tb.CreateTypeInfo(); + compiler.TransformGroup(parsedDocuments); + + var createdTypes = parsedDocuments.Select(document => + { + compiler.Compile(document.XamlDocument, document.TypeBuilder, document.PopulateMethod, + document.BuildMethod, document.Uri, document.FileSource); + return _sreTypeSystem.GetType(document.TypeBuilder.CreateType()); + }).ToArray(); + clrPropertyBuilder.CreateTypeInfo(); indexerClosureType.CreateTypeInfo(); trampolineBuilder.CreateTypeInfo(); - return LoadOrPopulate(created, rootInstance); + return createdTypes.Zip(rootInstances, (l, r) => (l, r)).Select(t => LoadOrPopulate(t.Item1, t.Item2)).ToArray(); + } + + static object LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + { + return LoadGroupSreCore(new[] { document }, configuration).Single(); } #endif - static object LoadOrPopulate(Type created, object rootInstance) + static object LoadOrPopulate(Type created, object rootInstance) { var isp = Expression.Parameter(typeof(IServiceProvider)); @@ -249,19 +305,37 @@ namespace Avalonia.Markup.Xaml.XamlIl } } - public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri, - bool isDesignMode, bool useCompiledBindingsByDefault) + public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) { +#if RUNTIME_XAML_CECIL string xaml; - using (var sr = new StreamReader(stream)) + using (var sr = new StreamReader(document.XamlStream)) xaml = sr.ReadToEnd(); + return LoadCecil(xaml, configuration.LocalAssembly, document.RootInstance,document.BaseUri, configuration.UseCompiledBindingsByDefault); +#else + return LoadSre(document, configuration); +#endif + } + + public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration configuration) + { #if RUNTIME_XAML_CECIL - return LoadCecil(xaml, localAssembly, rootInstance, uri, useCompiledBindingsByDefault); + throw new NotImplementedException("Load group was not implemented for the Cecil backend"); #else - return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault); + return LoadGroupSre(documents, configuration); #endif } + private static string GetSafeUriIdentifier(Uri uri) + { + return uri?.ToString() + .Replace(":", "_") + .Replace("/", "_") + .Replace("?", "_") + .Replace("=", "_") + .Replace(".", "_"); + } + #if RUNTIME_XAML_CECIL private static Dictionary populate, Func build)> @@ -303,12 +377,7 @@ namespace Avalonia.Markup.Xaml.XamlIl overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName); } - var safeUri = uri.ToString() - .Replace(":", "_") - .Replace("/", "_") - .Replace("?", "_") - .Replace("=", "_") - .Replace(".", "_"); + var safeUri = GetSafeUriIdentifier(uri); if (_cecilGeneratedCache.TryGetValue(safeUri, out var cached)) return LoadOrPopulate(cached, rootInstance); @@ -335,7 +404,7 @@ namespace Avalonia.Markup.Xaml.XamlIl { DefaultCompileBindings = useCompiledBindingsByDefault }; - compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType); + compiler.ParseAndCompile(xaml, uri.ToString(), null, tb, overrideType); var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll"); using(var f = File.Create(asmPath)) asm.Write(f); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index e601701d5c..ccda94ff1a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; - +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX; using XamlX.Ast; @@ -83,6 +83,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter()); Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter()); + + GroupTransformers = new() + { + new AvaloniaXamlIncludeTransformer() + }; } public AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings, @@ -115,7 +120,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions set => _bindingTransformer.CompileBindingsByDefault = value; } - public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder tb, IXamlType overrideRootType) + public List GroupTransformers { get; } + + public void TransformGroup(IReadOnlyCollection documents, bool strict = true) + { + var ctx = new AstGroupTransformationContext(documents, _configuration, strict); + foreach (var transformer in GroupTransformers) + { + foreach (var doc in documents) + { + var root = doc.XamlDocument.Root; + ctx.CurrentDocument = doc; + ctx.RootObject = (IXamlAstValueNode)root; + ctx.VisitChildren(ctx.RootObject, transformer); + root = ctx.Visit(root, transformer); + + doc.XamlDocument.Root = root; + } + } + } + + public XamlDocument Parse(string xaml, IXamlType overrideRootType) { var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary { @@ -148,9 +173,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions OverrideRootType(parsed, rootType); + return parsed; + } + + public void Compile(XamlDocument document, IXamlTypeBuilder tb, IXamlMethodBuilder populateMethod, IXamlMethodBuilder buildMethod, string baseUri, IFileSource fileSource) + { + Compile(document, _contextType, populateMethod, buildMethod, + _configuration.TypeMappings.XmlNamespaceInfoProvider == null ? + null : + tb.DefineSubType(_configuration.WellKnownTypes.Object, + "__AvaloniaXamlIlNsInfo", false), (name, bt) => tb.DefineSubType(bt, name, false), + (s, returnType, parameters) => tb.DefineDelegateSubType(s, false, returnType, parameters), baseUri, + fileSource); + } + + public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder tb, IXamlType overrideRootType) + { + var parsed = Parse(xaml, overrideRootType); + Transform(parsed); Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource); - } public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs new file mode 100644 index 0000000000..32bf37431f --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using XamlX; +using XamlX.Ast; +using XamlX.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; + +internal class AstGroupTransformationContext : AstTransformationContext +{ + public AstGroupTransformationContext(IReadOnlyCollection documents, TransformerConfiguration configuration, bool strictMode = true) + : base(configuration, null, strictMode) + { + Documents = documents; + } + + public IXamlDocumentResource CurrentDocument { get; set; } + + public IReadOnlyCollection Documents { get; } + + public new IXamlAstNode ParseError(string message, IXamlAstNode node) => + Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node)); + + public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) => + Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender)); + + class Visitor : IXamlAstVisitor + { + private readonly AstGroupTransformationContext _context; + private readonly IXamlAstGroupTransformer _transformer; + + public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) + { + _context = context; + _transformer = transformer; + } + + public IXamlAstNode Visit(IXamlAstNode node) + { +#if Xaml_DEBUG + return _transformer.Transform(_context, node); +#else + try + { + return _transformer.Transform(_context, node); + } + catch (Exception e) when (!(e is XmlException)) + { + throw new XamlDocumentParseException( + _context.CurrentDocument?.FileSource?.FilePath, + "Internal compiler error while transforming node " + node + ":\n" + e, + node); + } +#endif + } + + public void Push(IXamlAstNode node) => _context.PushParent(node); + + public void Pop() => _context.PopParent(); + } + + public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer) + { + root = root.Visit(new Visitor(this, transformer)); + return root; + } + + public void VisitChildren(IXamlAstNode root, IXamlAstGroupTransformer transformer) + { + root.VisitChildren(new Visitor(this, transformer)); + } +} + +internal interface IXamlAstGroupTransformer +{ + IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node); +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs new file mode 100644 index 0000000000..b859b3ed59 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs @@ -0,0 +1,17 @@ +using System; +using XamlX.Ast; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +#nullable enable + +internal interface IXamlDocumentResource +{ + IXamlMethod? BuildMethod { get; } + IXamlType? ClassType { get; } + string? Uri { get; } + IXamlMethod PopulateMethod { get; } + IFileSource? FileSource { get; } + XamlDocument XamlDocument { get; } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index dd37ae6c93..cb1da5ef6e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; using XamlX.Emit; using XamlX.IL; using XamlX.Transform; @@ -266,5 +267,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration)); return rv; } + + public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstGroupTransformationContext ctx) + { + if (ctx.TryGetItem(out var rv)) + return rv; + ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration)); + return rv; + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs new file mode 100644 index 0000000000..d031a6086b --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs @@ -0,0 +1,21 @@ +using XamlX; +using XamlX.Ast; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +internal class XamlDocumentParseException : XamlParseException +{ + public string FilePath { get; } + + public XamlDocumentParseException(string path, XamlParseException parseException) + : base(parseException.Message, parseException.LineNumber, parseException.LinePosition) + { + FilePath = path; + } + + public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo) + : base(message, lineInfo.Line, lineInfo.Position) + { + FilePath = path; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs new file mode 100644 index 0000000000..d5d452a9f3 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs @@ -0,0 +1,40 @@ +using System; +using XamlX.Ast; +using XamlX.IL; +using XamlX.TypeSystem; +#nullable enable + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; + +internal class XamlDocumentResource : IXamlDocumentResource +{ + public XamlDocumentResource( + XamlDocument xamlDocument, + string? uri, + IFileSource? fileSource, + IXamlType? classType, + IXamlTypeBuilder typeBuilder, + IXamlMethodBuilder populateMethod, + IXamlMethodBuilder? buildMethod) + { + XamlDocument = xamlDocument; + Uri = uri; + FileSource = fileSource; + ClassType = classType; + TypeBuilder = typeBuilder; + PopulateMethod = populateMethod; + BuildMethod = buildMethod; + } + + public XamlDocument XamlDocument { get; } + public string? Uri { get; } + public IFileSource? FileSource { get; } + + public IXamlType? ClassType { get; } + public IXamlTypeBuilder TypeBuilder { get; } + public IXamlMethodBuilder PopulateMethod { get; } + public IXamlMethodBuilder? BuildMethod { get; } + + IXamlMethod? IXamlDocumentResource.BuildMethod => BuildMethod; + IXamlMethod IXamlDocumentResource.PopulateMethod => PopulateMethod; +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index b5ba49ce2c..0ab00007e7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -43,6 +43,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 578af64abb..0032c01d2f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -11,7 +11,7 @@ namespace Avalonia.Markup.Xaml { public interface IRuntimeXamlLoader { - object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration); + object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration); } /// @@ -64,10 +64,9 @@ namespace Avalonia.Markup.Xaml using (var stream = asset.stream) { var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri); - return runtimeLoader.Load(stream, new RuntimeXamlLoaderConfiguration + return runtimeLoader.Load(new RuntimeXamlLoaderDocument(absoluteUri, stream), new RuntimeXamlLoaderConfiguration { - LocalAssembly = asset.assembly, - BaseUri = absoluteUri + LocalAssembly = asset.assembly }); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs index 9caf94fba6..b06f9a927e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs +++ b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs @@ -1,4 +1,3 @@ -using System; using System.Reflection; namespace Avalonia.Markup.Xaml; @@ -7,21 +6,11 @@ namespace Avalonia.Markup.Xaml; public class RuntimeXamlLoaderConfiguration { - /// - /// The URI of the XAML being loaded. - /// - public Uri? BaseUri { get; set; } - /// /// Default assembly for clr-namespace:. /// public Assembly? LocalAssembly { get; set; } - - /// - /// The optional instance into which the XAML should be loaded. - /// - public object? RootInstance { get; set; } - + /// /// Defines is CompiledBinding should be used by default. /// Default is 'false'. diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs new file mode 100644 index 0000000000..be22888156 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs @@ -0,0 +1,70 @@ +#nullable enable +using System; +using System.IO; +using System.Text; + +namespace Avalonia.Markup.Xaml; + +public class RuntimeXamlLoaderDocument +{ + public RuntimeXamlLoaderDocument(string xaml) + { + XamlStream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); + } + + public RuntimeXamlLoaderDocument(Uri? baseUri, string xaml) + : this(xaml) + { + BaseUri = baseUri; + } + + public RuntimeXamlLoaderDocument(object? rootInstance, string xaml) + : this(xaml) + { + RootInstance = rootInstance; + } + + public RuntimeXamlLoaderDocument(Uri? baseUri, object? rootInstance, string xaml) + : this(baseUri, xaml) + { + RootInstance = rootInstance; + } + + public RuntimeXamlLoaderDocument(Stream stream) + { + XamlStream = stream; + } + + public RuntimeXamlLoaderDocument(Uri? baseUri, Stream stream) + : this(stream) + { + BaseUri = baseUri; + } + + public RuntimeXamlLoaderDocument(object? rootInstance, Stream stream) + : this(stream) + { + RootInstance = rootInstance; + } + + public RuntimeXamlLoaderDocument(Uri? baseUri, object? rootInstance, Stream stream) + : this(baseUri, stream) + { + RootInstance = rootInstance; + } + + /// + /// The URI of the XAML being loaded. + /// + public Uri? BaseUri { get; set; } + + /// + /// The optional instance into which the XAML should be loaded. + /// + public object? RootInstance { get; set; } + + /// + /// The stream containing the XAML. + /// + public Stream XamlStream { get; set; } +} diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs index 7009151998..181883656c 100644 --- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs +++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs @@ -8,11 +8,9 @@ namespace Avalonia.Designer.HostApp { class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader { - public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) + public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) { - return AvaloniaXamlIlRuntimeCompiler.Load(stream, - configuration.LocalAssembly, configuration.RootInstance, configuration.BaseUri, - configuration.DesignMode, configuration.UseCompiledBindingsByDefault); + return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration); } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 88d2cc2912..cb34a9ac4d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1642,10 +1642,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' X='{Binding StringProperty, DataType=local:TestDataContext}' />"; - var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml, new RuntimeXamlLoaderConfiguration - { - UseCompiledBindingsByDefault = true - }); + var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), + new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true }); var compiledPath = ((CompiledBindingExtension)control.X).Path; var node = Assert.IsType(Assert.Single(compiledPath.Elements)); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs index 2fc4867b35..ea03b003ca 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs @@ -20,8 +20,8 @@ namespace Avalonia.Markup.Xaml.UnitTests class TestXamlLoaderShim : AvaloniaXamlLoader.IRuntimeXamlLoader { - public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration) - => AvaloniaRuntimeXamlLoader.Load(stream, configuration); + public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + => AvaloniaRuntimeXamlLoader.Load(document, configuration); } } }