committed by
GitHub
27 changed files with 506 additions and 189 deletions
@ -0,0 +1,113 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
using XamlX.Ast; |
|||
using XamlX.IL.Emitters; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; |
|||
#nullable enable |
|||
|
|||
internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
var resourceDictionaryType = context.GetAvaloniaTypes().ResourceDictionary; |
|||
if (node is not XamlObjectInitializationNode resourceDictionaryNode |
|||
|| resourceDictionaryNode.Type != resourceDictionaryType |
|||
|| resourceDictionaryNode.Manipulation is not XamlManipulationGroupNode resourceDictionaryManipulation) |
|||
{ |
|||
return node; |
|||
} |
|||
|
|||
var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude; |
|||
var mergeSourceNodes = new List<XamlPropertyAssignmentNode>(); |
|||
var hasAnyNonMergedResource = false; |
|||
foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray()) |
|||
{ |
|||
void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode) |
|||
{ |
|||
if (assignmentNode.Property.Name == "MergedDictionaries" |
|||
&& assignmentNode.Values.FirstOrDefault() is XamlValueWithManipulationNode valueNode) |
|||
{ |
|||
if (valueNode.Type.GetClrType() == mergeResourceIncludeType) |
|||
{ |
|||
if (valueNode.Manipulation is XamlObjectInitializationNode objectInitialization |
|||
&& objectInitialization.Manipulation is XamlPropertyAssignmentNode sourceAssignmentNode) |
|||
{ |
|||
parent.Children.Remove(assignmentNode); |
|||
mergeSourceNodes.Add(sourceAssignmentNode); |
|||
} |
|||
else |
|||
{ |
|||
throw new XamlDocumentParseException(context.CurrentDocument, |
|||
"Invalid MergeResourceInclude node found. Make sure that Source property is set.", |
|||
valueNode); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
hasAnyNonMergedResource = true; |
|||
} |
|||
|
|||
if (hasAnyNonMergedResource && mergeSourceNodes.Any()) |
|||
{ |
|||
throw new XamlDocumentParseException(context.CurrentDocument, |
|||
"Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed", |
|||
valueNode); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (manipulationNode is XamlPropertyAssignmentNode singleValueAssignment) |
|||
{ |
|||
ProcessXamlPropertyAssignmentNode(resourceDictionaryManipulation, singleValueAssignment); |
|||
} |
|||
else if (manipulationNode is XamlManipulationGroupNode groupNodeValues) |
|||
{ |
|||
foreach (var groupNodeValue in groupNodeValues.Children.OfType<XamlPropertyAssignmentNode>().ToArray()) |
|||
{ |
|||
ProcessXamlPropertyAssignmentNode(groupNodeValues, groupNodeValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
var manipulationGroup = new XamlManipulationGroupNode(node, new List<IXamlAstManipulationNode>()); |
|||
foreach (var sourceNode in mergeSourceNodes) |
|||
{ |
|||
var (originalAssetPath, propertyNode) = |
|||
AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true); |
|||
if (originalAssetPath is null) |
|||
{ |
|||
return node; |
|||
} |
|||
|
|||
var targetDocument = context.Documents.FirstOrDefault(d => |
|||
string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase)) |
|||
?.XamlDocument.Root as XamlValueWithManipulationNode; |
|||
if (targetDocument is null) |
|||
{ |
|||
return context.ParseError( |
|||
$"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node); |
|||
} |
|||
|
|||
var singleRootObject = ((XamlManipulationGroupNode)targetDocument.Manipulation) |
|||
.Children.OfType<XamlObjectInitializationNode>().Single(); |
|||
if (singleRootObject.Type != resourceDictionaryType) |
|||
{ |
|||
return context.ParseError( |
|||
$"MergeResourceInclude can only include another ResourceDictionary", propertyNode, node); |
|||
} |
|||
|
|||
manipulationGroup.Children.Add(singleRootObject.Manipulation); |
|||
} |
|||
|
|||
if (manipulationGroup.Children.Any()) |
|||
{ |
|||
// MergedDictionaries are read first, so we need ot inject our merged values in the beginning.
|
|||
resourceDictionaryManipulation.Children.Insert(0, manipulationGroup); |
|||
} |
|||
|
|||
return node; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Avalonia.Controls; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Markup.Xaml.Styling; |
|||
|
|||
/// <summary>
|
|||
/// Loads a resource dictionary from a specified URL.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If used from the XAML code, it is merged into the parent dictionary in the compile time.
|
|||
/// When used in runtime, this type behaves like <see cref="ResourceInclude"/>.
|
|||
/// </remarks>
|
|||
[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)] |
|||
public class MergeResourceInclude : ResourceInclude |
|||
{ |
|||
public MergeResourceInclude(Uri? baseUri) : base(baseUri) |
|||
{ |
|||
} |
|||
|
|||
public MergeResourceInclude(IServiceProvider serviceProvider) : base(serviceProvider) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using System; |
|||
using System.Xml; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.Xaml; |
|||
|
|||
public class MergeResourceIncludeTests |
|||
{ |
|||
[Fact] |
|||
public void MergeResourceInclude_Works_With_Single_Resource() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources.xaml"), @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush2'>Red</SolidColorBrush> |
|||
</ResourceDictionary>"),
|
|||
new RuntimeXamlLoaderDocument(@"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<ResourceDictionary> |
|||
<SolidColorBrush x:Key='brush1'>Blue</SolidColorBrush> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<MergeResourceInclude Source='avares://Tests/Resources.xaml'/>
|
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</UserControl.Resources> |
|||
</UserControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var contentControl = Assert.IsType<UserControl>(objects[1]); |
|||
|
|||
var resources = Assert.IsType<ResourceDictionary>(contentControl.Resources); |
|||
Assert.Empty(resources.MergedDictionaries); |
|||
|
|||
var initialResource = (ISolidColorBrush)resources["brush1"]!; |
|||
Assert.Equal(Colors.Blue, initialResource.Color); |
|||
|
|||
var mergedResource = (ISolidColorBrush)resources["brush2"]!; |
|||
Assert.Equal(Colors.Red, mergedResource.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Mixing_MergeResourceInclude_And_ResourceInclude_Is_Not_Allowed() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush1'>Red</SolidColorBrush> |
|||
</ResourceDictionary>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush2'>Blue</SolidColorBrush> |
|||
</ResourceDictionary>"),
|
|||
new RuntimeXamlLoaderDocument(@"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ResourceDictionary.MergedDictionaries> |
|||
<MergeResourceInclude Source='avares://Tests/Resources1.xaml'/>
|
|||
<ResourceInclude Source='avares://Tests/Resources2.xaml'/>
|
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary>")
|
|||
}; |
|||
|
|||
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MergeResourceInclude_Works_With_Multiple_Resources() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush1'>Red</SolidColorBrush> |
|||
<SolidColorBrush x:Key='brush2'>Blue</SolidColorBrush> |
|||
</ResourceDictionary>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush4'>Yellow</SolidColorBrush> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<MergeResourceInclude Source='avares://Tests/Resources1_2.xaml'/>
|
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary>"),
|
|||
new RuntimeXamlLoaderDocument(@"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ResourceDictionary.MergedDictionaries> |
|||
<MergeResourceInclude Source='avares://Tests/Resources1.xaml'/>
|
|||
<MergeResourceInclude Source='avares://Tests/Resources2.xaml'/>
|
|||
</ResourceDictionary.MergedDictionaries> |
|||
<SolidColorBrush x:Key='brush5'>Black</SolidColorBrush> |
|||
<SolidColorBrush x:Key='brush6'>White</SolidColorBrush> |
|||
</ResourceDictionary>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1_2.xaml"), @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush3'>Green</SolidColorBrush> |
|||
</ResourceDictionary>"),
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var resources = Assert.IsType<ResourceDictionary>(objects[2]); |
|||
Assert.Empty(resources.MergedDictionaries); |
|||
|
|||
Assert.Equal(Colors.Red, ((ISolidColorBrush)resources["brush1"]!).Color); |
|||
Assert.Equal(Colors.Blue, ((ISolidColorBrush)resources["brush2"]!).Color); |
|||
Assert.Equal(Colors.Green, ((ISolidColorBrush)resources["brush3"]!).Color); |
|||
Assert.Equal(Colors.Yellow, ((ISolidColorBrush)resources["brush4"]!).Color); |
|||
Assert.Equal(Colors.Black, ((ISolidColorBrush)resources["brush5"]!).Color); |
|||
Assert.Equal(Colors.White, ((ISolidColorBrush)resources["brush6"]!).Color); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue