2016-03-22 23:02:25 +03:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Linq ;
using System.Reflection ;
using System.Xml ;
using Xamarin.Forms.Internals ;
2019-09-12 03:35:40 +03:00
using Xamarin.Forms.Xaml.Diagnostics ;
2016-03-22 23:02:25 +03:00
using Xamarin.Forms.Xaml.Internals ;
2016-09-08 21:45:43 +03:00
using static System . String ;
2016-03-22 23:02:25 +03:00
namespace Xamarin.Forms.Xaml
{
2017-09-25 09:52:03 +03:00
class ApplyPropertiesVisitor : IXamlNodeVisitor
2016-03-22 23:02:25 +03:00
{
2017-09-25 09:52:03 +03:00
public static readonly IList < XmlName > Skips = new List < XmlName > {
2016-03-22 23:02:25 +03:00
XmlName . xKey ,
XmlName . xTypeArguments ,
XmlName . xArguments ,
XmlName . xFactoryMethod ,
2016-11-15 22:39:48 +03:00
XmlName . xName ,
XmlName . xDataType
2016-03-22 23:02:25 +03:00
} ;
2017-09-25 09:52:03 +03:00
public ApplyPropertiesVisitor ( HydrationContext context , bool stopOnResourceDictionary = false )
2016-03-22 23:02:25 +03:00
{
Context = context ;
StopOnResourceDictionary = stopOnResourceDictionary ;
}
2017-09-25 09:52:03 +03:00
Dictionary < INode , object > Values = > Context . Values ;
HydrationContext Context { get ; }
2016-03-22 23:02:25 +03:00
2017-01-25 16:47:27 +03:00
public TreeVisitingMode VisitingMode = > TreeVisitingMode . BottomUp ;
public bool StopOnDataTemplate = > true ;
2016-03-22 23:02:25 +03:00
public bool StopOnResourceDictionary { get ; }
2017-01-25 16:47:27 +03:00
public bool VisitNodeOnDataTemplate = > true ;
2018-01-11 22:03:12 +03:00
public bool SkipChildren ( INode node , INode parentNode ) = > false ;
2019-02-26 22:09:16 +03:00
public bool IsResourceDictionary ( ElementNode node ) = > Context . Types . TryGetValue ( node , out var type ) & & typeof ( ResourceDictionary ) . IsAssignableFrom ( type ) ;
2016-03-22 23:02:25 +03:00
public void Visit ( ValueNode node , INode parentNode )
{
var parentElement = parentNode as IElementNode ;
2016-09-08 21:45:43 +03:00
var value = Values [ node ] ;
2019-02-26 22:09:16 +03:00
if ( ! Values . TryGetValue ( parentNode , out var source ) & & Context . ExceptionHandler ! = null )
return ;
2016-03-22 23:02:25 +03:00
XmlName propertyName ;
2018-01-05 12:11:51 +03:00
2016-09-08 21:45:43 +03:00
if ( TryGetPropertyName ( node , parentNode , out propertyName ) ) {
2018-01-05 12:11:51 +03:00
if ( TrySetRuntimeName ( propertyName , source , value , node ) )
return ;
2016-03-22 23:02:25 +03:00
if ( Skips . Contains ( propertyName ) )
return ;
if ( parentElement . SkipProperties . Contains ( propertyName ) )
return ;
2017-09-25 09:52:03 +03:00
if ( propertyName . Equals ( XamlParser . McUri , "Ignorable" ) )
2016-03-22 23:02:25 +03:00
return ;
SetPropertyValue ( source , propertyName , value , Context . RootElement , node , Context , node ) ;
2016-09-08 21:45:43 +03:00
} else if ( IsCollectionItem ( node , parentNode ) & & parentNode is IElementNode ) {
2016-03-22 23:02:25 +03:00
// Collection element, implicit content, or implicit collection element.
2017-09-25 09:52:03 +03:00
var contentProperty = GetContentPropertyName ( Context . Types [ parentElement ] . GetTypeInfo ( ) ) ;
2016-09-08 21:45:43 +03:00
if ( contentProperty ! = null ) {
2016-03-22 23:02:25 +03:00
var name = new XmlName ( ( ( ElementNode ) parentNode ) . NamespaceURI , contentProperty ) ;
if ( Skips . Contains ( name ) )
return ;
if ( parentElement . SkipProperties . Contains ( propertyName ) )
return ;
SetPropertyValue ( source , name , value , Context . RootElement , node , Context , node ) ;
}
}
}
public void Visit ( MarkupNode node , INode parentNode )
{
}
public void Visit ( ElementNode node , INode parentNode )
{
2017-09-25 09:52:03 +03:00
XmlName propertyName ;
2017-04-07 10:48:17 +03:00
if ( TryGetPropertyName ( node , parentNode , out propertyName ) & & propertyName = = XmlName . _CreateContent ) {
2017-01-25 16:47:27 +03:00
var s0 = Values [ parentNode ] ;
if ( s0 is ElementTemplate ) {
SetTemplate ( s0 as ElementTemplate , node ) ;
return ;
}
}
2016-03-22 23:02:25 +03:00
var parentElement = parentNode as IElementNode ;
2017-01-25 16:47:27 +03:00
propertyName = XmlName . Empty ;
2016-08-15 23:18:26 +03:00
//Simplify ListNodes with single elements
var pList = parentNode as ListNode ;
if ( pList ! = null & & pList . CollectionItems . Count = = 1 ) {
propertyName = pList . XmlName ;
parentNode = parentNode . Parent ;
parentElement = parentNode as IElementNode ;
}
2019-02-26 22:09:16 +03:00
if ( ! Values . TryGetValue ( node , out var value ) & & Context . ExceptionHandler ! = null )
return ;
2017-04-07 10:48:17 +03:00
2016-08-15 23:18:26 +03:00
if ( propertyName ! = XmlName . Empty | | TryGetPropertyName ( node , parentNode , out propertyName ) ) {
2016-03-22 23:02:25 +03:00
if ( Skips . Contains ( propertyName ) )
return ;
if ( parentElement . SkipProperties . Contains ( propertyName ) )
return ;
2019-02-26 22:09:16 +03:00
if ( ! Values . TryGetValue ( parentNode , out var source ) & & Context . ExceptionHandler ! = null )
return ;
2017-09-25 09:52:03 +03:00
ProvideValue ( ref value , node , source , propertyName ) ;
2017-01-25 16:47:27 +03:00
SetPropertyValue ( source , propertyName , value , Context . RootElement , node , Context , node ) ;
2017-11-22 14:59:55 +03:00
}
else if ( IsCollectionItem ( node , parentNode ) & & parentNode is IElementNode ) {
2019-02-26 22:09:16 +03:00
if ( ! Values . TryGetValue ( parentNode , out var source ) & & Context . ExceptionHandler ! = null )
return ;
2017-09-25 09:52:03 +03:00
ProvideValue ( ref value , node , source , XmlName . Empty ) ;
string contentProperty ;
2017-11-22 14:59:55 +03:00
Exception xpe = null ;
var xKey = node . Properties . ContainsKey ( XmlName . xKey ) ? ( ( ValueNode ) node . Properties [ XmlName . xKey ] ) . Value as string : null ;
//ResourceDictionary
if ( xpe = = null & & TryAddToResourceDictionary ( source as ResourceDictionary , value , xKey , node , out xpe ) )
return ;
2017-04-07 10:48:17 +03:00
2016-03-22 23:02:25 +03:00
// Collection element, implicit content, or implicit collection element.
2017-11-22 14:59:55 +03:00
if ( xpe = = null & & typeof ( IEnumerable ) . IsAssignableFrom ( Context . Types [ parentElement ] ) & & Context . Types [ parentElement ] . GetRuntimeMethods ( ) . Any ( mi = > mi . Name = = "Add" & & mi . GetParameters ( ) . Length = = 1 ) ) {
var addMethod =
Context . Types [ parentElement ] . GetRuntimeMethods ( ) . First ( mi = > mi . Name = = "Add" & & mi . GetParameters ( ) . Length = = 1 ) ;
2018-01-05 12:11:51 +03:00
2017-11-22 14:59:55 +03:00
addMethod . Invoke ( source , new [ ] { value } ) ;
return ;
}
if ( xpe = = null & & ( contentProperty = GetContentPropertyName ( Context . Types [ parentElement ] . GetTypeInfo ( ) ) ) ! = null ) {
2016-03-22 23:02:25 +03:00
var name = new XmlName ( node . NamespaceURI , contentProperty ) ;
if ( Skips . Contains ( name ) )
return ;
if ( parentElement . SkipProperties . Contains ( propertyName ) )
return ;
SetPropertyValue ( source , name , value , Context . RootElement , node , Context , node ) ;
2017-11-22 14:59:55 +03:00
return ;
}
xpe = xpe ? ? new XamlParseException ( $"Can not set the content of {((IElementNode)parentNode).XmlType.Name} as it doesn't have a ContentPropertyAttribute" , node ) ;
if ( Context . ExceptionHandler ! = null )
Context . ExceptionHandler ( xpe ) ;
else
throw xpe ;
}
else if ( IsCollectionItem ( node , parentNode ) & & parentNode is ListNode ) {
2019-02-26 22:09:16 +03:00
if ( ! Values . TryGetValue ( parentNode . Parent , out var source ) & & Context . ExceptionHandler ! = null )
return ;
2017-09-25 09:52:03 +03:00
ProvideValue ( ref value , node , source , XmlName . Empty ) ;
2016-03-22 23:02:25 +03:00
var parentList = ( ListNode ) parentNode ;
if ( Skips . Contains ( parentList . XmlName ) )
return ;
2017-11-22 14:59:55 +03:00
Exception xpe = null ;
var xKey = node . Properties . ContainsKey ( XmlName . xKey ) ? ( ( ValueNode ) node . Properties [ XmlName . xKey ] ) . Value as string : null ;
2016-03-22 23:02:25 +03:00
2019-09-05 15:18:03 +03:00
var collection = GetPropertyValue ( source , parentList . XmlName , Context , parentList , out _ , out _ ) as IEnumerable ;
2017-11-14 11:37:45 +03:00
if ( collection = = null )
2017-11-22 14:59:55 +03:00
xpe = new XamlParseException ( $"Property {parentList.XmlName.LocalName} is null or is not IEnumerable" , node ) ;
2017-11-14 11:37:45 +03:00
2017-11-22 14:59:55 +03:00
if ( xpe = = null & & TryAddToResourceDictionary ( collection as ResourceDictionary , value , xKey , node , out xpe ) )
return ;
2016-03-22 23:02:25 +03:00
2017-11-22 14:59:55 +03:00
MethodInfo addMethod ;
if ( xpe = = null & & ( addMethod = collection . GetType ( ) . GetRuntimeMethods ( ) . First ( mi = > mi . Name = = "Add" & & mi . GetParameters ( ) . Length = = 1 ) ) ! = null ) {
2018-11-23 12:00:46 +03:00
addMethod . Invoke ( collection , new [ ] { value } ) ;
2017-11-22 14:59:55 +03:00
return ;
}
xpe = xpe ? ? new XamlParseException ( $"Value of {parentList.XmlName.LocalName} does not have a Add() method" , node ) ;
if ( Context . ExceptionHandler ! = null )
Context . ExceptionHandler ( xpe ) ;
else
throw xpe ;
2016-03-22 23:02:25 +03:00
}
}
2018-01-05 12:11:51 +03:00
2016-03-22 23:02:25 +03:00
public void Visit ( RootNode node , INode parentNode )
{
}
public void Visit ( ListNode node , INode parentNode )
{
}
public static bool TryGetPropertyName ( INode node , INode parentNode , out XmlName name )
{
name = default ( XmlName ) ;
var parentElement = parentNode as IElementNode ;
if ( parentElement = = null )
return false ;
2016-09-08 21:45:43 +03:00
foreach ( var kvp in parentElement . Properties ) {
2016-03-22 23:02:25 +03:00
if ( kvp . Value ! = node )
continue ;
name = kvp . Key ;
return true ;
}
return false ;
}
2017-09-25 09:52:03 +03:00
internal static bool IsCollectionItem ( INode node , INode parentNode )
2016-03-22 23:02:25 +03:00
{
var parentList = parentNode as IListNode ;
if ( parentList = = null )
return false ;
return parentList . CollectionItems . Contains ( node ) ;
}
internal static string GetContentPropertyName ( TypeInfo typeInfo )
{
2016-09-08 21:45:43 +03:00
while ( typeInfo ! = null ) {
2016-03-22 23:02:25 +03:00
var propName = GetContentPropertyName ( typeInfo . CustomAttributes ) ;
if ( propName ! = null )
return propName ;
typeInfo = typeInfo ? . BaseType ? . GetTypeInfo ( ) ;
}
return null ;
}
2017-09-25 09:52:03 +03:00
void ProvideValue ( ref object value , ElementNode node , object source , XmlName propertyName )
{
var markupExtension = value as IMarkupExtension ;
var valueProvider = value as IValueProvider ;
if ( markupExtension = = null & & valueProvider = = null )
return ;
XamlServiceProvider serviceProvider = null ;
if ( value . GetType ( ) . GetTypeInfo ( ) . GetCustomAttribute < AcceptEmptyServiceProviderAttribute > ( ) = = null )
serviceProvider = new XamlServiceProvider ( node , Context ) ;
if ( serviceProvider ! = null & & propertyName ! = XmlName . Empty )
( ( XamlValueTargetProvider ) serviceProvider . IProvideValueTarget ) . TargetProperty = GetTargetProperty ( source , propertyName , Context , node ) ;
2018-11-09 12:03:17 +03:00
try {
if ( markupExtension ! = null )
value = markupExtension . ProvideValue ( serviceProvider ) ;
else if ( valueProvider ! = null )
value = valueProvider . ProvideValue ( serviceProvider ) ;
} catch ( Exception e ) {
if ( Context . ExceptionHandler ! = null )
Context . ExceptionHandler ( e ) ;
else
throw e ;
}
2017-09-25 09:52:03 +03:00
}
2016-03-22 23:02:25 +03:00
static string GetContentPropertyName ( IEnumerable < CustomAttributeData > attributes )
{
var contentAttribute =
attributes . FirstOrDefault ( cad = > ContentPropertyAttribute . ContentPropertyTypes . Contains ( cad . AttributeType . FullName ) ) ;
if ( contentAttribute = = null | | contentAttribute . ConstructorArguments . Count ! = 1 )
return null ;
2016-09-08 21:45:43 +03:00
if ( contentAttribute . ConstructorArguments [ 0 ] . ArgumentType = = typeof ( string ) )
return ( string ) contentAttribute . ConstructorArguments [ 0 ] . Value ;
2016-03-22 23:02:25 +03:00
return null ;
}
static bool GetRealNameAndType ( ref Type elementType , string namespaceURI , ref string localname ,
2017-09-25 09:52:03 +03:00
HydrationContext context , IXmlLineInfo lineInfo )
2016-03-22 23:02:25 +03:00
{
var dotIdx = localname . IndexOf ( '.' ) ;
2016-09-08 21:45:43 +03:00
if ( dotIdx > 0 ) {
2016-03-22 23:02:25 +03:00
var typename = localname . Substring ( 0 , dotIdx ) ;
localname = localname . Substring ( dotIdx + 1 ) ;
XamlParseException xpe ;
elementType = XamlParser . GetElementType ( new XmlType ( namespaceURI , typename , null ) , lineInfo ,
context . RootElement . GetType ( ) . GetTypeInfo ( ) . Assembly , out xpe ) ;
2018-01-05 12:11:51 +03:00
2016-03-22 23:02:25 +03:00
if ( xpe ! = null )
throw xpe ;
return true ;
}
return false ;
}
static BindableProperty GetBindableProperty ( Type elementType , string localName , IXmlLineInfo lineInfo ,
bool throwOnError = false )
{
2018-02-08 21:18:56 +03:00
#if NETSTANDARD1_0
var bindableFieldInfo = elementType . GetFields ( ) . FirstOrDefault ( fi = > fi . Name = = localName + "Property" ) ;
#else
2018-06-18 15:21:49 +03:00
// F# does not support public fields, so allow internal (Assembly) as well as public
const BindingFlags supportedFlags = BindingFlags . Static | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . FlattenHierarchy ;
var bindableFieldInfo = elementType . GetFields ( supportedFlags )
. FirstOrDefault ( fi = > ( fi . IsAssembly | | fi . IsPublic ) & & fi . Name = = localName + "Property" ) ;
2018-02-08 21:18:56 +03:00
#endif
2016-03-22 23:02:25 +03:00
Exception exception = null ;
2016-09-08 21:45:43 +03:00
if ( exception = = null & & bindableFieldInfo = = null ) {
2016-03-22 23:02:25 +03:00
exception =
new XamlParseException (
2017-09-25 09:52:03 +03:00
Format ( "BindableProperty {0} not found on {1}" , localName + "Property" , elementType . Name ) , lineInfo ) ;
2016-03-22 23:02:25 +03:00
}
if ( exception = = null )
return bindableFieldInfo . GetValue ( null ) as BindableProperty ;
if ( throwOnError )
throw exception ;
return null ;
}
2017-09-25 09:52:03 +03:00
static object GetTargetProperty ( object xamlelement , XmlName propertyName , HydrationContext context , IXmlLineInfo lineInfo )
2017-04-07 10:48:17 +03:00
{
var localName = propertyName . LocalName ;
//If it's an attached BP, update elementType and propertyName
var bpOwnerType = xamlelement . GetType ( ) ;
GetRealNameAndType ( ref bpOwnerType , propertyName . NamespaceURI , ref localName , context , lineInfo ) ;
var property = GetBindableProperty ( bpOwnerType , localName , lineInfo , false ) ;
if ( property ! = null )
return property ;
var elementType = xamlelement . GetType ( ) ;
var propertyInfo = elementType . GetRuntimeProperties ( ) . FirstOrDefault ( p = > p . Name = = localName ) ;
return propertyInfo ;
}
2017-09-25 09:52:03 +03:00
public static void SetPropertyValue ( object xamlelement , XmlName propertyName , object value , object rootElement , INode node , HydrationContext context , IXmlLineInfo lineInfo )
2016-03-22 23:02:25 +03:00
{
2016-09-08 21:45:43 +03:00
var localName = propertyName . LocalName ;
2016-03-22 23:02:25 +03:00
var serviceProvider = new XamlServiceProvider ( node , context ) ;
2016-09-08 21:45:43 +03:00
Exception xpe = null ;
2017-11-22 14:59:55 +03:00
var xKey = node is IElementNode & & ( ( IElementNode ) node ) . Properties . ContainsKey ( XmlName . xKey ) ? ( ( ValueNode ) ( ( IElementNode ) node ) . Properties [ XmlName . xKey ] ) . Value as string : null ;
2016-03-22 23:02:25 +03:00
//If it's an attached BP, update elementType and propertyName
2016-09-08 21:45:43 +03:00
var bpOwnerType = xamlelement . GetType ( ) ;
var attached = GetRealNameAndType ( ref bpOwnerType , propertyName . NamespaceURI , ref localName , context , lineInfo ) ;
var property = GetBindableProperty ( bpOwnerType , localName , lineInfo , false ) ;
2016-03-22 23:02:25 +03:00
//If the target is an event, connect
2018-03-21 18:33:48 +03:00
if ( xpe = = null & & TryConnectEvent ( xamlelement , localName , attached , value , rootElement , lineInfo , out xpe ) )
2016-09-08 21:45:43 +03:00
return ;
2016-03-22 23:02:25 +03:00
2016-09-08 21:45:43 +03:00
//If Value is DynamicResource and it's a BP, SetDynamicResource
if ( xpe = = null & & TrySetDynamicResource ( xamlelement , property , value , lineInfo , out xpe ) )
2016-03-22 23:02:25 +03:00
return ;
2016-09-08 21:45:43 +03:00
//If value is BindingBase, SetBinding
if ( xpe = = null & & TrySetBinding ( xamlelement , property , localName , value , lineInfo , out xpe ) )
return ;
2016-03-22 23:02:25 +03:00
2016-09-08 21:45:43 +03:00
//If it's a BindableProberty, SetValue
2020-06-05 02:41:37 +03:00
if ( xpe = = null & & TrySetValue ( xamlelement , property , attached , value , lineInfo , serviceProvider , out xpe ) )
2016-03-22 23:02:25 +03:00
return ;
2016-09-08 21:45:43 +03:00
//If we can assign that value to a normal property, let's do it
2020-06-05 02:41:37 +03:00
if ( xpe = = null & & TrySetProperty ( xamlelement , localName , value , lineInfo , serviceProvider , context , out xpe ) )
2016-09-08 21:45:43 +03:00
return ;
2016-03-22 23:02:25 +03:00
2016-09-08 21:45:43 +03:00
//If it's an already initialized property, add to it
2020-06-05 02:41:37 +03:00
if ( xpe = = null & & TryAddToProperty ( xamlelement , propertyName , value , xKey , lineInfo , serviceProvider , context , out xpe ) )
2016-03-22 23:02:25 +03:00
return ;
2016-09-08 21:45:43 +03:00
2018-01-05 12:11:51 +03:00
xpe = xpe ? ? new XamlParseException ( $"Cannot assign property \" { localName } \ ": Property does not exist, or is not assignable, or mismatching type between value and property" , lineInfo ) ;
2017-03-16 22:09:22 +03:00
if ( context . ExceptionHandler ! = null )
context . ExceptionHandler ( xpe ) ;
2016-09-08 21:45:43 +03:00
else
throw xpe ;
}
2019-09-05 15:18:03 +03:00
public static object GetPropertyValue ( object xamlElement , XmlName propertyName , HydrationContext context , IXmlLineInfo lineInfo , out Exception xpe , out object targetProperty )
2017-11-14 11:37:45 +03:00
{
var localName = propertyName . LocalName ;
2019-09-05 15:18:03 +03:00
xpe = null ;
2017-11-20 13:13:43 +03:00
targetProperty = null ;
2017-11-14 11:37:45 +03:00
//If it's an attached BP, update elementType and propertyName
var bpOwnerType = xamlElement . GetType ( ) ;
var attached = GetRealNameAndType ( ref bpOwnerType , propertyName . NamespaceURI , ref localName , context , lineInfo ) ;
var property = GetBindableProperty ( bpOwnerType , localName , lineInfo , false ) ;
//If it's a BindableProberty, GetValue
2019-09-05 15:18:03 +03:00
if ( xpe = = null & & TryGetValue ( xamlElement , property , attached , out var value , lineInfo , out xpe , out targetProperty ) )
2017-11-14 11:37:45 +03:00
return value ;
//If it's a normal property, get it
2017-11-20 13:13:43 +03:00
if ( xpe = = null & & TryGetProperty ( xamlElement , localName , out value , lineInfo , context , out xpe , out targetProperty ) )
2017-11-14 11:37:45 +03:00
return value ;
xpe = xpe ? ? new XamlParseException ( $"Property {localName} is not found or does not have an accessible getter" , lineInfo ) ;
return null ;
}
2018-03-21 18:33:48 +03:00
static bool TryConnectEvent ( object element , string localName , bool attached , object value , object rootElement , IXmlLineInfo lineInfo , out Exception exception )
2016-09-08 21:45:43 +03:00
{
exception = null ;
2018-03-21 18:33:48 +03:00
if ( attached )
return false ;
2016-09-08 21:45:43 +03:00
var elementType = element . GetType ( ) ;
var eventInfo = elementType . GetRuntimeEvent ( localName ) ;
var stringValue = value as string ;
if ( eventInfo = = null | | IsNullOrEmpty ( stringValue ) )
return false ;
2019-06-04 14:45:19 +03:00
foreach ( var mi in rootElement . GetType ( ) . GetRuntimeMethods ( ) ) {
if ( mi . Name = = ( string ) value ) {
try {
eventInfo . AddEventHandler ( element , mi . CreateDelegate ( eventInfo . EventHandlerType , mi . IsStatic ? null : rootElement ) ) ;
return true ;
} catch ( ArgumentException ) {
// incorrect method signature
}
}
2016-03-22 23:02:25 +03:00
}
2019-06-04 14:45:19 +03:00
exception = new XamlParseException ( $"No method {value} with correct signature found on type {rootElement.GetType()}" , lineInfo ) ;
2016-09-08 21:45:43 +03:00
return false ;
}
static bool TrySetDynamicResource ( object element , BindableProperty property , object value , IXmlLineInfo lineInfo , out Exception exception )
{
exception = null ;
var elementType = element . GetType ( ) ;
var dynamicResource = value as DynamicResource ;
var bindable = element as BindableObject ;
if ( dynamicResource = = null | | property = = null )
return false ;
if ( bindable = = null ) {
exception = new XamlParseException ( $"{elementType.Name} is not a BindableObject" , lineInfo ) ;
return false ;
}
bindable . SetDynamicResource ( property , dynamicResource . Key ) ;
return true ;
}
static bool TrySetBinding ( object element , BindableProperty property , string localName , object value , IXmlLineInfo lineInfo , out Exception exception )
{
exception = null ;
var elementType = element . GetType ( ) ;
2019-02-28 04:58:58 +03:00
var binding = value . ConvertTo ( typeof ( BindingBase ) , pinfoRetriever : null , serviceProvider : null , exception : out exception ) as BindingBase ;
if ( exception ! = null )
return false ;
2016-09-08 21:45:43 +03:00
var bindable = element as BindableObject ;
var nativeBindingService = DependencyService . Get < INativeBindingService > ( ) ;
if ( binding = = null )
return false ;
if ( bindable ! = null & & property ! = null ) {
bindable . SetBinding ( property , binding ) ;
return true ;
}
if ( nativeBindingService ! = null & & property ! = null & & nativeBindingService . TrySetBinding ( element , property , binding ) )
return true ;
if ( nativeBindingService ! = null & & nativeBindingService . TrySetBinding ( element , localName , binding ) )
return true ;
2016-03-22 23:02:25 +03:00
if ( property ! = null )
2016-09-08 21:45:43 +03:00
exception = new XamlParseException ( $"{elementType.Name} is not a BindableObject or does not support native bindings" , lineInfo ) ;
2016-03-22 23:02:25 +03:00
2016-09-08 21:45:43 +03:00
return false ;
}
2016-03-22 23:02:25 +03:00
2016-09-08 21:45:43 +03:00
static bool TrySetValue ( object element , BindableProperty property , bool attached , object value , IXmlLineInfo lineInfo , XamlServiceProvider serviceProvider , out Exception exception )
{
exception = null ;
var elementType = element . GetType ( ) ;
var bindable = element as BindableObject ;
var nativeBindingService = DependencyService . Get < INativeBindingService > ( ) ;
if ( property = = null )
return false ;
2017-04-07 10:48:17 +03:00
if ( serviceProvider ! = null & & serviceProvider . IProvideValueTarget ! = null )
( ( XamlValueTargetProvider ) serviceProvider . IProvideValueTarget ) . TargetProperty = property ;
2016-09-08 21:45:43 +03:00
Func < MemberInfo > minforetriever ;
if ( attached )
2018-09-28 23:25:32 +03:00
minforetriever = ( ) = >
{
try {
return property . DeclaringType . GetRuntimeMethod ( "Get" + property . PropertyName , new [ ] { typeof ( BindableObject ) } ) ;
} catch ( AmbiguousMatchException e ) {
throw new XamlParseException ( $"Multiple methods with name '{property.DeclaringType}.Get{property.PropertyName}' found." , lineInfo , innerException : e ) ;
}
} ;
2016-09-08 21:45:43 +03:00
else
2018-09-28 23:25:32 +03:00
minforetriever = ( ) = >
{
try {
return property . DeclaringType . GetRuntimeProperty ( property . PropertyName ) ;
} catch ( AmbiguousMatchException e ) {
throw new XamlParseException ( $"Multiple properties with name '{property.DeclaringType}.{property.PropertyName}' found." , lineInfo , innerException : e ) ;
}
} ;
2019-02-28 04:58:58 +03:00
var convertedValue = value . ConvertTo ( property . ReturnType , minforetriever , serviceProvider , out exception ) ;
if ( exception ! = null )
return false ;
2016-09-08 21:45:43 +03:00
if ( bindable ! = null ) {
2016-03-22 23:02:25 +03:00
//SetValue doesn't throw on mismatching type, so check before to get a chance to try the property setting or the collection adding
var nullable = property . ReturnTypeInfo . IsGenericType & &
2016-09-08 21:45:43 +03:00
property . ReturnTypeInfo . GetGenericTypeDefinition ( ) = = typeof ( Nullable < > ) ;
2016-03-22 23:02:25 +03:00
if ( ( convertedValue = = null & & ( ! property . ReturnTypeInfo . IsValueType | | nullable ) ) | |
2016-09-08 21:45:43 +03:00
( property . ReturnType . IsInstanceOfType ( convertedValue ) ) ) {
2019-02-04 17:45:49 +03:00
try {
bindable . SetValue ( property , convertedValue ) ;
return true ;
}
catch ( Exception e ) {
exception = e ;
return false ;
}
2016-03-22 23:02:25 +03:00
}
2018-01-05 12:11:51 +03:00
// This might be a collection; see if we can add to it
2019-02-28 04:58:58 +03:00
return TryAddValue ( bindable , property , value , serviceProvider , out exception ) ;
2016-03-22 23:02:25 +03:00
}
2016-09-08 21:45:43 +03:00
if ( nativeBindingService ! = null & & nativeBindingService . TrySetValue ( element , property , convertedValue ) )
return true ;
2016-03-22 23:02:25 +03:00
2016-09-08 21:45:43 +03:00
exception = new XamlParseException ( $"{elementType.Name} is not a BindableObject or does not support setting native BindableProperties" , lineInfo ) ;
return false ;
}
2017-11-20 13:13:43 +03:00
static bool TryGetValue ( object element , BindableProperty property , bool attached , out object value , IXmlLineInfo lineInfo , out Exception exception , out object targetProperty )
2017-11-14 11:37:45 +03:00
{
exception = null ;
value = null ;
2017-11-20 13:13:43 +03:00
targetProperty = property ;
2017-11-14 11:37:45 +03:00
var elementType = element . GetType ( ) ;
var bindable = element as BindableObject ;
if ( property = = null )
return false ;
if ( bindable = = null )
return false ;
value = bindable . GetValue ( property ) ;
return true ;
}
2017-09-25 09:52:03 +03:00
static bool TrySetProperty ( object element , string localName , object value , IXmlLineInfo lineInfo , XamlServiceProvider serviceProvider , HydrationContext context , out Exception exception )
2016-09-08 21:45:43 +03:00
{
exception = null ;
var elementType = element . GetType ( ) ;
var propertyInfo = elementType . GetRuntimeProperties ( ) . FirstOrDefault ( p = > p . Name = = localName ) ;
2016-03-22 23:02:25 +03:00
MethodInfo setter ;
2016-09-08 21:45:43 +03:00
if ( propertyInfo = = null | | ! propertyInfo . CanWrite | | ( setter = propertyInfo . SetMethod ) = = null )
return false ;
2016-03-22 23:02:25 +03:00
2017-02-02 17:28:59 +03:00
if ( ! IsVisibleFrom ( setter , context . RootElement ) )
return false ;
2017-04-07 10:48:17 +03:00
if ( serviceProvider ! = null & & serviceProvider . IProvideValueTarget ! = null )
( ( XamlValueTargetProvider ) serviceProvider . IProvideValueTarget ) . TargetProperty = propertyInfo ;
2019-02-28 04:58:58 +03:00
object convertedValue = value . ConvertTo ( propertyInfo . PropertyType , ( ) = > propertyInfo , serviceProvider , out exception ) ;
if ( exception ! = null | | ( convertedValue ! = null & & ! propertyInfo . PropertyType . IsInstanceOfType ( convertedValue ) ) )
2016-09-08 21:45:43 +03:00
return false ;
2019-02-04 17:45:49 +03:00
try {
setter . Invoke ( element , new object [ ] { convertedValue } ) ;
return true ;
}
catch ( Exception e ) {
exception = e ;
return false ;
}
2016-09-08 21:45:43 +03:00
}
2017-11-20 13:13:43 +03:00
static bool TryGetProperty ( object element , string localName , out object value , IXmlLineInfo lineInfo , HydrationContext context , out Exception exception , out object targetProperty )
2017-11-14 11:37:45 +03:00
{
exception = null ;
value = null ;
var elementType = element . GetType ( ) ;
PropertyInfo propertyInfo = null ;
2018-05-23 09:28:59 +03:00
#if NETSTANDARD1_0
2017-11-14 11:37:45 +03:00
try {
propertyInfo = elementType . GetRuntimeProperty ( localName ) ;
} catch ( AmbiguousMatchException ) {
// Get most derived instance of property
foreach ( var property in elementType . GetRuntimeProperties ( ) . Where ( prop = > prop . Name = = localName ) ) {
if ( propertyInfo = = null | | propertyInfo . DeclaringType . IsAssignableFrom ( property . DeclaringType ) )
propertyInfo = property ;
}
}
2018-05-23 09:28:59 +03:00
#else
while ( elementType ! = null & & propertyInfo = = null ) {
2018-09-28 23:25:32 +03:00
try {
propertyInfo = elementType . GetProperty ( localName , BindingFlags . Instance | BindingFlags . Static | BindingFlags . Public | BindingFlags . DeclaredOnly ) ;
} catch ( AmbiguousMatchException e ) {
throw new XamlParseException ( $"Multiple properties with name '{elementType}.{localName}' found." , lineInfo , innerException : e ) ;
}
2018-05-23 09:28:59 +03:00
elementType = elementType . BaseType ;
}
#endif
2017-11-14 11:37:45 +03:00
MethodInfo getter ;
2017-11-20 13:13:43 +03:00
targetProperty = propertyInfo ;
2017-11-14 11:37:45 +03:00
if ( propertyInfo = = null | | ! propertyInfo . CanRead | | ( getter = propertyInfo . GetMethod ) = = null )
return false ;
if ( ! IsVisibleFrom ( getter , context . RootElement ) )
return false ;
value = getter . Invoke ( element , new object [ ] { } ) ;
return true ;
}
static bool IsVisibleFrom ( MethodInfo method , object rootElement )
2017-02-02 17:28:59 +03:00
{
2017-11-14 11:37:45 +03:00
if ( method . IsPublic )
2017-02-02 17:28:59 +03:00
return true ;
2017-11-14 11:37:45 +03:00
if ( method . IsPrivate & & method . DeclaringType = = rootElement . GetType ( ) )
2017-02-02 17:28:59 +03:00
return true ;
2017-11-14 11:37:45 +03:00
if ( ( method . IsAssembly | | method . IsFamilyOrAssembly ) & & method . DeclaringType . AssemblyQualifiedName = = rootElement . GetType ( ) . AssemblyQualifiedName )
2017-02-02 17:28:59 +03:00
return true ;
2017-11-14 11:37:45 +03:00
if ( method . IsFamily & & method . DeclaringType . IsAssignableFrom ( rootElement . GetType ( ) ) )
2017-02-02 17:28:59 +03:00
return true ;
return false ;
}
2017-11-22 14:59:55 +03:00
static bool TryAddToProperty ( object element , XmlName propertyName , object value , string xKey , IXmlLineInfo lineInfo , XamlServiceProvider serviceProvider , HydrationContext context , out Exception exception )
2016-09-08 21:45:43 +03:00
{
exception = null ;
2019-09-05 15:18:03 +03:00
if ( ! ( GetPropertyValue ( element , propertyName , context , lineInfo , out _ , out var targetProperty ) is IEnumerable collection ) )
2016-09-08 21:45:43 +03:00
return false ;
2017-11-22 14:59:55 +03:00
if ( exception = = null & & TryAddToResourceDictionary ( collection as ResourceDictionary , value , xKey , lineInfo , out exception ) )
return true ;
if ( exception ! = null )
return false ;
2016-09-08 21:45:43 +03:00
var addMethod = collection . GetType ( ) . GetRuntimeMethods ( ) . First ( mi = > mi . Name = = "Add" & & mi . GetParameters ( ) . Length = = 1 ) ;
if ( addMethod = = null )
return false ;
2017-11-22 14:59:55 +03:00
if ( serviceProvider ! = null )
( ( XamlValueTargetProvider ) serviceProvider . IProvideValueTarget ) . TargetProperty = targetProperty ;
2019-03-19 01:45:15 +03:00
try {
addMethod . Invoke ( collection , new [ ] { value . ConvertTo ( addMethod . GetParameters ( ) [ 0 ] . ParameterType , ( Func < TypeConverter > ) null , serviceProvider , out exception ) } ) ;
}
catch ( Exception e ) {
exception = e ;
}
2019-02-28 04:58:58 +03:00
return exception = = null ;
2016-03-22 23:02:25 +03:00
}
2017-11-22 14:59:55 +03:00
static bool TryAddToResourceDictionary ( ResourceDictionary resourceDictionary , object value , string xKey , IXmlLineInfo lineInfo , out Exception exception )
{
exception = null ;
if ( resourceDictionary = = null )
return false ;
if ( xKey ! = null )
resourceDictionary . Add ( xKey , value ) ;
else if ( value is Style )
resourceDictionary . Add ( ( Style ) value ) ;
2017-11-22 16:56:20 +03:00
else if ( value is ResourceDictionary )
resourceDictionary . Add ( ( ResourceDictionary ) value ) ;
2017-12-20 12:47:50 +03:00
else if ( value is StyleSheets . StyleSheet )
resourceDictionary . Add ( ( StyleSheets . StyleSheet ) value ) ;
2017-11-22 14:59:55 +03:00
else {
exception = new XamlParseException ( "resources in ResourceDictionary require a x:Key attribute" , lineInfo ) ;
return false ;
}
return true ;
}
2016-03-22 23:02:25 +03:00
void SetTemplate ( ElementTemplate dt , INode node )
{
#pragma warning disable 0612
2016-09-08 21:45:43 +03:00
( ( IDataTemplate ) dt ) . LoadTemplate = ( ) = > {
2016-03-22 23:02:25 +03:00
#pragma warning restore 0612
2016-08-16 21:33:44 +03:00
var cnode = node . Clone ( ) ;
2019-10-07 17:09:51 +03:00
var context = new HydrationContext { ParentContext = Context , RootAssembly = Context . RootAssembly , RootElement = Context . RootElement , ExceptionHandler = Context . ExceptionHandler } ;
2016-08-16 21:33:44 +03:00
cnode . Accept ( new XamlNodeVisitor ( ( n , parent ) = > n . Parent = parent ) , node . Parent ) ; //set parents for {StaticResource}
cnode . Accept ( new ExpandMarkupsVisitor ( context ) , null ) ;
cnode . Accept ( new NamescopingVisitor ( context ) , null ) ;
cnode . Accept ( new CreateValuesVisitor ( context ) , null ) ;
cnode . Accept ( new RegisterXNamesVisitor ( context ) , null ) ;
cnode . Accept ( new FillResourceDictionariesVisitor ( context ) , null ) ;
cnode . Accept ( new ApplyPropertiesVisitor ( context , true ) , null ) ;
2016-09-08 21:45:43 +03:00
return context . Values [ cnode ] ;
2016-03-22 23:02:25 +03:00
} ;
}
2018-01-05 12:11:51 +03:00
2019-02-28 04:58:58 +03:00
static bool TryAddValue ( BindableObject bindable , BindableProperty property , object value , XamlServiceProvider serviceProvider , out Exception exception )
2018-01-05 12:11:51 +03:00
{
2019-02-28 04:58:58 +03:00
exception = null ;
if ( property ? . ReturnTypeInfo ? . GenericTypeArguments = = null )
2018-01-05 12:11:51 +03:00
return false ;
2019-02-28 04:58:58 +03:00
if ( property . ReturnType = = null )
2018-01-05 12:11:51 +03:00
return false ;
2019-02-28 04:58:58 +03:00
if ( property . ReturnTypeInfo . GenericTypeArguments . Length ! = 1 | | ! property . ReturnTypeInfo . GenericTypeArguments [ 0 ] . IsInstanceOfType ( value ) )
2018-01-05 12:11:51 +03:00
return false ;
// This might be a collection we can add to; see if we can find an Add method
2019-02-28 04:58:58 +03:00
var addMethod = GetAllRuntimeMethods ( property . ReturnType ) . FirstOrDefault ( mi = > mi . Name = = "Add" & & mi . GetParameters ( ) . Length = = 1 ) ;
2018-01-05 12:11:51 +03:00
if ( addMethod = = null )
return false ;
// If there's an add method, get the collection
var collection = bindable . GetValue ( property ) ;
// And add the new value to it
2019-02-28 04:58:58 +03:00
addMethod . Invoke ( collection , new [ ] { value . ConvertTo ( addMethod . GetParameters ( ) [ 0 ] . ParameterType , ( Func < TypeConverter > ) null , serviceProvider , out exception ) } ) ;
return exception = = null ;
2018-01-05 12:11:51 +03:00
}
static IEnumerable < MethodInfo > GetAllRuntimeMethods ( Type type )
{
return type . GetRuntimeMethods ( )
. Concat ( type . GetTypeInfo ( ) . ImplementedInterfaces . SelectMany ( t = > t . GetRuntimeMethods ( ) ) ) ;
}
bool TrySetRuntimeName ( XmlName propertyName , object source , object value , ValueNode node )
{
if ( propertyName ! = XmlName . xName )
return false ;
var runTimeName = source . GetType ( ) . GetTypeInfo ( ) . GetCustomAttribute < RuntimeNamePropertyAttribute > ( ) ;
if ( runTimeName = = null )
return false ;
SetPropertyValue ( source , new XmlName ( "" , runTimeName . Name ) , value , Context . RootElement , node , Context , node ) ;
return true ;
}
2016-03-22 23:02:25 +03:00
}
2018-04-20 06:50:47 +03:00
}