From 98cf91c3853ad8935db98f6d7ab201e9983e9b84 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 30 Nov 2022 02:25:48 -0500 Subject: [PATCH] Properly support relative Uris in StyleInclude/ResourceInclude --- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 2 +- .../XamlIncludeGroupTransformer.cs | 21 +++-- .../Converters/AvaloniaUriTypeConverter.cs | 2 +- .../Xaml/StyleIncludeTests.cs | 88 ++++++++++++++++++- 4 files changed, 100 insertions(+), 13 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 64fdfe155d..925bf0a4fa 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -274,7 +274,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { var uriText = text.Trim(); - var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.Absolute : UriKind.Relative); + var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.RelativeOrAbsolute : UriKind.Relative); if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _)) { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs index 09a65c9d64..f0895de5e2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs @@ -36,34 +36,37 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node); } + // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`. if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri - || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath }) + || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath } + || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind }) { // TODO: make it a compiler warning // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet. return node; } - if (originalAssetPath.StartsWith("/")) + var uriPath = new Uri(originalAssetPath, (UriKind)uriKind); + if (!uriPath.IsAbsoluteUri) { var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null."); - originalAssetPath = baseUrl.Substring(0, baseUrl.LastIndexOf('/')) + originalAssetPath; + uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath); } - else if (!originalAssetPath.StartsWith("avares://")) + else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase)) { return context.ParseError( $"Avalonia supports only \"avares://\" sources or relative sources starting with \"/\" on the \"{nodeTypeName}\" node.", node); } - originalAssetPath = Uri.UnescapeDataString(new Uri(originalAssetPath).AbsoluteUri); - var assetPath = originalAssetPath.Replace("avares://", ""); + var assetPathUri = Uri.UnescapeDataString(uriPath.AbsoluteUri); + var assetPath = assetPathUri.Replace("avares://", ""); var assemblyNameSeparator = assetPath.IndexOf('/'); var assembly = assetPath.Substring(0, assemblyNameSeparator); var fullTypeName = Path.GetFileNameWithoutExtension(assetPath.Replace('/', '.')); - if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument) + if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, assetPathUri, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument) { if (targetDocument.ClassType is not null) { @@ -72,7 +75,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer if (targetDocument.BuildMethod is null) { - return context.ParseError($"\"{originalAssetPath}\" cannot be instantiated.", node); + return context.ParseError($"\"{assetPathUri}\" cannot be instantiated.", node); } return FromMethod(context, targetDocument.BuildMethod, node); @@ -81,7 +84,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly) { - return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{originalAssetPath}\" source.", node); + return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", node); } if (assetAssembly.FindType(fullTypeName) is { } type diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs index 3c3a718213..54c57d8b7b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters if (s == null) return null; //On Unix Uri tries to interpret paths starting with "/" as file Uris - var kind = s.StartsWith("/") ? UriKind.Relative : UriKind.Absolute; + var kind = s.StartsWith("/") ? UriKind.Relative : UriKind.RelativeOrAbsolute; if (!Uri.TryCreate(s, kind, out var res)) throw new ArgumentException("Unable to parse URI: " + s); return res; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs index 4ff4405109..8eed5013a2 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs @@ -79,7 +79,35 @@ public class StyleIncludeTests } [Fact] - public void Relative_StyleInclude_Is_Resolved_With_Two_Files() + public void Relative_Back_StyleInclude_Is_Resolved_With_Two_Files() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Subfolder/Style.xaml"), @" +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Subfolder/Folder/Root.xaml"), @" + + + + +") + }; + + var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var style = Assert.IsType"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @" + + + + +") + }; + + var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var style = Assert.IsType"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @" + + + ") };