in src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs [26:242]
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is not XamlAstObjectNode on ||
!context.GetAvaloniaTypes().Style.IsAssignableFrom(on.Type.GetClrType()))
return node;
var pn = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
// Missing selector, use the object's target type if available
if (pn == null)
{
// We already went through this node
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode metadataNode
&& metadataNode.Value == on)
{
return node;
}
if (FindStyleParentObject(on, context) is { } parentObjectNode)
{
return new AvaloniaXamlIlTargetTypeMetadataNode(
on,
new XamlAstClrTypeReference(node, parentObjectNode.Type.GetClrType(), false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
return node;
}
if (pn.Values.Count != 1)
throw new XamlSelectorsTransformException("Selector property should have exactly one value",
node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlAstTextNode tn))
throw new XamlSelectorsTransformException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter!.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
var avaloniaAttachedPropertyT = context.GetAvaloniaTypes().AvaloniaAttachedPropertyT;
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
Func<string, string, XamlAstClrTypeReference> typeResolver)
{
XamlIlSelectorNode result = initialNode;
XamlIlOrSelectorNode? results = null;
foreach (var i in syntax)
{
switch (i)
{
case SelectorGrammar.OfTypeSyntax ofType:
result = new XamlIlTypeSelector(result, typeResolver(ofType.Xmlns, ofType.TypeName).Type, true);
break;
case SelectorGrammar.IsSyntax @is:
result = new XamlIlTypeSelector(result, typeResolver(@is.Xmlns, @is.TypeName).Type, false);
break;
case SelectorGrammar.ClassSyntax @class:
result = new XamlIlStringSelector(result, XamlIlStringSelector.SelectorType.Class, @class.Class);
break;
case SelectorGrammar.NameSyntax name:
result = new XamlIlStringSelector(result, XamlIlStringSelector.SelectorType.Name, name.Name);
break;
case SelectorGrammar.PropertySyntax property:
{
var type = result.TargetType;
if (type == null)
throw new XamlTransformException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
throw new XamlTransformException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
targetProperty, out var typedValue))
throw new XamlTransformException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue);
break;
}
case SelectorGrammar.AttachedPropertySyntax attachedProperty:
{
var targetType = result.TargetType;
if (targetType == null)
{
throw new XamlTransformException("Attached Property selectors must be applied to a type.",node);
}
var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
if (attachedPropertyOwnerType is null)
{
throw new XamlTransformException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
}
var attachedPropertyName = attachedProperty.Property + "Property";
var targetPropertyField = attachedPropertyOwnerType.GetAllFields()
.FirstOrDefault(f => f.IsStatic
&& f.IsPublic
&& f.Name == attachedPropertyName
&& f.FieldType.GenericTypeDefinition == avaloniaAttachedPropertyT
);
if (targetPropertyField is null)
{
throw new XamlTransformException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
}
var targetPropertyType = XamlIlAvaloniaPropertyHelper
.GetAvaloniaPropertyType(targetPropertyField, context.GetAvaloniaTypes(), node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
targetPropertyType, out var typedValue))
throw new XamlTransformException(
$"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
node);
result = new XamlIlAttachedPropertyEqualsSelector(result, targetPropertyField, typedValue);
break;
}
case SelectorGrammar.ChildSyntax child:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Child);
break;
case SelectorGrammar.DescendantSyntax descendant:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Descendant);
break;
case SelectorGrammar.TemplateSyntax template:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Template);
break;
case SelectorGrammar.NotSyntax not:
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver));
break;
case SelectorGrammar.NthChildSyntax nth:
result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthChild);
break;
case SelectorGrammar.NthLastChildSyntax nth:
result = new XamlIlNthChildSelector(result, nth.Step, nth.Offset, XamlIlNthChildSelector.SelectorType.NthLastChild);
break;
case SelectorGrammar.CommaSyntax comma:
if (results == null)
results = new XamlIlOrSelectorNode(node, selectorType);
results.Add(result);
result = initialNode;
break;
case SelectorGrammar.NestingSyntax:
var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (parentTargetType is null)
throw new XamlTransformException($"Cannot find parent style for nested selector.", node);
result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
break;
default:
throw new XamlTransformException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
if (results != null)
{
results.Add(result);
}
return results ?? result;
}
IEnumerable<SelectorGrammar.ISyntax> parsed;
try
{
parsed = SelectorGrammar.Parse(tn.Text);
}
catch (Exception e)
{
throw new XamlSelectorsTransformException("Unable to parse selector: " + e.Message, node, e);
}
var selector = Create(parsed, (p, n)
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
var templateType = GetLastTemplateTypeFromSelector(selector);
// Empty selector, use the object's target type if available
if (selector == initialNode)
{
if (FindStyleParentObject(on, context) is { } parentObjectNode)
{
return new AvaloniaXamlIlTargetTypeMetadataNode(
on,
new XamlAstClrTypeReference(node, parentObjectNode.Type.GetClrType(), false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
return node;
}
var styleNode = new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(selector, selector.TargetType!, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
return templateType switch
{
null => styleNode,
_ => new AvaloniaXamlIlTargetTypeMetadataNode(styleNode,
new XamlAstClrTypeReference(styleNode, templateType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
};
}