From 41d76f256b12501c698c4a0d4142688c1c2e210f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 24 Apr 2023 18:13:47 -0400 Subject: [PATCH 1/2] Add AvaloniaListAttribute --- .../Metadata/AvaloniaListAttribute.cs | 23 +++++++++++++++++++ .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 14 +++++++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 2 ++ .../Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs diff --git a/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs new file mode 100644 index 0000000000..d523808d32 --- /dev/null +++ b/src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace Avalonia.Metadata; + +/// +/// Defines how compiler should split avalonia list string value before parsing individual items. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public sealed class AvaloniaListAttribute : Attribute +{ + /// + /// Separator used to split input string. + /// Default value is ','. + /// + public string[]? Separators { get; init; } + + /// + /// Split options used to split input string. + /// Default value is RemoveEmptyEntries with TrimEntries. + /// + // StringSplitOptions.TrimEntries = 2, but only on net6 target. + public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2; +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index cd005ce24d..6c7b04dbfb 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -337,6 +337,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var separators = new[] { "," }; var splitOptions = StringSplitOptions.RemoveEmptyEntries | trimOption; + var attribute = type.GetAllCustomAttributes().FirstOrDefault(a => a.Type == types.AvaloniaListAttribute); + if (attribute is not null) + { + if (attribute.Properties.TryGetValue("Separators", out var separatorsArray)) + { + separators = ((Array)separatorsArray)?.OfType().ToArray(); + } + + if (attribute.Properties.TryGetValue("SplitOptions", out var splitOptionsObj)) + { + splitOptions = (StringSplitOptions)splitOptionsObj; + } + } + items = text.Split(separators, splitOptions ^ trimOption); // Compiler targets netstandard, so we need to emulate StringSplitOptions.TrimEntries, if it was requested. if (splitOptions.HasFlag(trimOption)) 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 8ab84f4615..b5c0c7734d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -33,6 +33,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType InheritDataTypeFromItemsAttribute { get; } public IXamlType MarkupExtensionOptionAttribute { get; } public IXamlType MarkupExtensionDefaultOptionAttribute { get; } + public IXamlType AvaloniaListAttribute { get; } public IXamlType AvaloniaList { get; } public IXamlType OnExtensionType { get; } public IXamlType UnsetValueType { get; } @@ -143,6 +144,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers InheritDataTypeFromItemsAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromItemsAttribute"); MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute"); MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute"); + AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute"); AvaloniaList = cfg.TypeSystem.GetType("Avalonia.Collections.AvaloniaList`1"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, AvaloniaObject, diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index 5d1025f30d..e5254eb1b2 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit 5d1025f30d0ed6d8f419d82959c148276301f393 +Subproject commit e5254eb1b2017f78a92acd466c8fa1e47401056b From 89e1680501859c0f31fa4da65b2f5c28e3205e82 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 24 Apr 2023 18:13:55 -0400 Subject: [PATCH 2/2] Fix DefinitionList parsing --- src/Avalonia.Controls/DefinitionList.cs | 2 ++ .../Xaml/BasicTests.cs | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Avalonia.Controls/DefinitionList.cs b/src/Avalonia.Controls/DefinitionList.cs index c850647bf4..63a54731e0 100644 --- a/src/Avalonia.Controls/DefinitionList.cs +++ b/src/Avalonia.Controls/DefinitionList.cs @@ -1,9 +1,11 @@ using System.Collections; using System.Collections.Specialized; using Avalonia.Collections; +using Avalonia.Metadata; namespace Avalonia.Controls { + [AvaloniaList(Separators = new [] { ",", " " })] public abstract class DefinitionList : AvaloniaList where T : DefinitionBase { public DefinitionList() diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 421ed2c979..70541b6196 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -261,6 +261,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var grid = AvaloniaRuntimeXamlLoader.Parse(xaml); + Assert.Equal(4, grid.ColumnDefinitions.Count); + Assert.Equal(4, grid.RowDefinitions.Count); + + var expected1 = new GridLength(100); + var expected2 = GridLength.Auto; + var expected3 = new GridLength(1, GridUnitType.Star); + var expected4 = new GridLength(100, GridUnitType.Star); + + Assert.Equal(expected1, grid.ColumnDefinitions[0].Width); + Assert.Equal(expected2, grid.ColumnDefinitions[1].Width); + Assert.Equal(expected3, grid.ColumnDefinitions[2].Width); + Assert.Equal(expected4, grid.ColumnDefinitions[3].Width); + + Assert.Equal(expected1, grid.RowDefinitions[0].Height); + Assert.Equal(expected2, grid.RowDefinitions[1].Height); + Assert.Equal(expected3, grid.RowDefinitions[2].Height); + Assert.Equal(expected4, grid.RowDefinitions[3].Height); + } + + [Fact] + public void Grid_Row_Col_Definitions_Are_Parsed_Space_Delimiter() + { + var xaml = @" + +"; + + var grid = AvaloniaRuntimeXamlLoader.Parse(xaml); + + Assert.Equal(4, grid.ColumnDefinitions.Count); Assert.Equal(4, grid.RowDefinitions.Count);