in src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs [17:207]
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlMarkupExtensionNode
{
Value: XamlAstObjectNode { Type: XamlAstClrTypeReference { Type: { } type } } objectNode
} markupExtensionNode
&& type.FindMethods(m => m.IsPublic && m.Parameters.Count is 1 or 2 && m.ReturnType == context.Configuration.WellKnownTypes.Boolean && m.Name == "ShouldProvideOption").ToArray() is { } methods
&& methods.Any())
{
var optionAttribute = context.GetAvaloniaTypes().MarkupExtensionOptionAttribute;
var defaultOptionAttribute = context.GetAvaloniaTypes().MarkupExtensionDefaultOptionAttribute;
var typeArgument = type.GenericArguments.FirstOrDefault();
IXamlAstValueNode? defaultValue = null;
var values = new List<OptionsMarkupExtensionBranch>();
if (objectNode.Arguments.FirstOrDefault() is { } argument)
{
var hasDefaultProp = objectNode.Type.GetClrType().GetAllProperties().Any(p =>
p.CustomAttributes.Any(a => a.Type == defaultOptionAttribute));
if (hasDefaultProp)
{
if (objectNode.Arguments.Count > 1)
{
throw new XamlTransformException("Options MarkupExtensions allow only single argument", objectNode);
}
defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
objectNode.Arguments.Remove(argument);
}
}
foreach (var extProp in objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().ToArray())
{
if (!extProp.Values.Any())
{
continue;
}
var shouldRemoveProp = false;
var onObjs = extProp.Values.OfType<XamlAstObjectNode>()
.Where(o => o.Type.GetClrType() == context.GetAvaloniaTypes().OnExtensionType).ToArray();
if (onObjs.Any())
{
shouldRemoveProp = true;
foreach (var onObj in onObjs)
{
var optionsPropNode = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Options")
?.Values.Single();
var options = (optionsPropNode as XamlAstTextNode)?.Text.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
?? Array.Empty<string>();
if (options.Length == 0)
{
throw new XamlTransformException("On.Options string must be set", onObj);
}
var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
if (content is null)
{
throw new XamlTransformException("On content object must be set", onObj);
}
var propertiesSet = options
.Select(o => type.GetAllProperties()
.FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
?? throw new XamlTransformException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
.ToArray();
foreach (var propertySet in propertiesSet)
{
AddBranchNode(content.Values, propertySet.CustomAttributes, content);
}
}
}
else
{
shouldRemoveProp = AddBranchNode(extProp.Values, extProp.Property.GetClrProperty().CustomAttributes, extProp);
}
if (shouldRemoveProp)
{
objectNode.Children.Remove(extProp);
}
}
if (defaultValue is null && !values.Any())
{
throw new XamlTransformException("Options markup extension requires at least one option to be set", objectNode);
}
return new OptionsMarkupExtensionNode(
markupExtensionNode, values.ToArray(), defaultValue,
context.Configuration.TypeMappings.ServiceProvider);
bool AddBranchNode(
IReadOnlyCollection<IXamlAstValueNode> valueNodes,
IReadOnlyCollection<IXamlCustomAttribute> propAttributes,
IXamlLineInfo li)
{
var transformed = TransformNode(valueNodes, typeArgument, li);
if (propAttributes.FirstOrDefault(a => a.Type == defaultOptionAttribute) is { } defAttr)
{
defaultValue = transformed;
return true;
}
else if (propAttributes.FirstOrDefault(a => a.Type == optionAttribute) is { } optAttr)
{
var option = optAttr.Parameters.Single();
if (option is null)
{
throw new XamlTransformException("MarkupExtension option must not be null", li);
}
var optionAsString = option.ToString() ?? string.Empty;
IXamlAstValueNode? optionNode = null;
foreach (var method in methods)
{
try
{
var targetType = method.Parameters.Last();
if (targetType.FullName == "System.Type")
{
if (option is IXamlType typeOption)
{
optionNode = new XamlTypeExtensionNode(li,
new XamlAstClrTypeReference(li, typeOption, false), targetType);
}
}
else if (targetType == context.Configuration.WellKnownTypes.String)
{
optionNode = new XamlConstantNode(li, targetType, optionAsString);
}
else if (targetType.IsEnum)
{
if (TypeSystemHelpers.TryGetEnumValueNode(targetType, optionAsString, li, false,
out var enumConstantNode))
{
optionNode = enumConstantNode;
}
}
else if (TypeSystemHelpers.ParseConstantIfTypeAllows(optionAsString, targetType, li,
out var constantNode))
{
optionNode = constantNode;
}
}
catch (FormatException)
{
// try next method overload
}
if (optionNode is not null)
{
values.Add(new OptionsMarkupExtensionBranch(optionNode, transformed, method));
return true;
}
}
throw new XamlTransformException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
}
return false;
}
}
return node;
IXamlAstValueNode TransformNode(
IReadOnlyCollection<IXamlAstValueNode> values,
IXamlType? suggestedType,
IXamlLineInfo line)
{
if (suggestedType is not null)
{
values = values
.Select(v => XamlTransformHelpers
.TryGetCorrectlyTypedValue(context, v, suggestedType, out var converted)
? converted : v)
.ToArray();
}
if (values.Count > 1)
{
throw new XamlTransformException("Options markup extension supports only a singular value", line);
}
return values.Single();
}
}