2016-03-22 23:02:25 +03:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
using System.Reflection ;
using System.Xml ;
using Xamarin.Forms.Internals ;
using Xamarin.Forms.Xaml.Internals ;
namespace Xamarin.Forms.Xaml
{
2018-01-11 22:03:12 +03:00
class CreateValuesVisitor : IXamlNodeVisitor
2016-03-22 23:02:25 +03:00
{
2017-09-25 09:52:03 +03:00
public CreateValuesVisitor ( HydrationContext context )
2016-03-22 23:02:25 +03:00
{
Context = context ;
}
Dictionary < INode , object > Values
{
get { return Context . Values ; }
}
2017-09-25 09:52:03 +03:00
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 ;
public bool StopOnResourceDictionary = > false ;
public bool VisitNodeOnDataTemplate = > false ;
2018-01-11 22:03:12 +03:00
public bool SkipChildren ( INode node , INode parentNode ) = > false ;
2018-04-20 06:50:47 +03:00
public bool IsResourceDictionary ( ElementNode node ) = > typeof ( ResourceDictionary ) . IsAssignableFrom ( Context . Types [ node ] ) ;
2016-03-22 23:02:25 +03:00
public void Visit ( ValueNode node , INode parentNode )
{
Values [ node ] = node . Value ;
}
public void Visit ( MarkupNode node , INode parentNode )
{
}
public void Visit ( ElementNode node , INode parentNode )
{
object value = null ;
XamlParseException xpe ;
2016-04-12 19:46:39 +03:00
var type = XamlParser . GetElementType ( node . XmlType , node , Context . RootElement ? . GetType ( ) . GetTypeInfo ( ) . Assembly ,
2016-03-22 23:02:25 +03:00
out xpe ) ;
if ( xpe ! = null )
throw xpe ;
Context . Types [ node ] = type ;
string ctorargname ;
if ( IsXaml2009LanguagePrimitive ( node ) )
value = CreateLanguagePrimitive ( type , node ) ;
else if ( node . Properties . ContainsKey ( XmlName . xArguments ) | | node . Properties . ContainsKey ( XmlName . xFactoryMethod ) )
value = CreateFromFactory ( type , node ) ;
else if (
type . GetTypeInfo ( )
. DeclaredConstructors . Any (
ci = >
ci . IsPublic & & ci . GetParameters ( ) . Length ! = 0 & &
ci . GetParameters ( ) . All ( pi = > pi . CustomAttributes . Any ( attr = > attr . AttributeType = = typeof ( ParameterAttribute ) ) ) ) & &
ValidateCtorArguments ( type , node , out ctorargname ) )
value = CreateFromParameterizedConstructor ( type , node ) ;
else if ( ! type . GetTypeInfo ( ) . DeclaredConstructors . Any ( ci = > ci . IsPublic & & ci . GetParameters ( ) . Length = = 0 ) & &
! ValidateCtorArguments ( type , node , out ctorargname ) )
{
2016-09-26 23:29:47 +03:00
throw new XamlParseException ( $"The Property {ctorargname} is required to create a {type.FullName} object." , node ) ;
2016-03-22 23:02:25 +03:00
}
else
{
//this is a trick as the DataTemplate parameterless ctor is internal, and we can't CreateInstance(..., false) on WP7
try
{
if ( type = = typeof ( DataTemplate ) )
value = new DataTemplate ( ) ;
if ( type = = typeof ( ControlTemplate ) )
value = new ControlTemplate ( ) ;
if ( value = = null & & node . CollectionItems . Any ( ) & & node . CollectionItems . First ( ) is ValueNode )
{
var serviceProvider = new XamlServiceProvider ( node , Context ) ;
var converted = ( ( ValueNode ) node . CollectionItems . First ( ) ) . Value . ConvertTo ( type , ( ) = > type . GetTypeInfo ( ) ,
serviceProvider ) ;
if ( converted ! = null & & converted . GetType ( ) = = type )
value = converted ;
}
if ( value = = null )
value = Activator . CreateInstance ( type ) ;
}
catch ( TargetInvocationException e )
{
if ( e . InnerException is XamlParseException | | e . InnerException is XmlException )
throw e . InnerException ;
throw ;
}
}
Values [ node ] = value ;
2016-08-15 23:06:11 +03:00
var markup = value as IMarkupExtension ;
2016-12-05 00:08:11 +03:00
if ( markup ! = null & & ( value is TypeExtension | | value is StaticExtension | | value is ArrayExtension ) )
2016-03-22 23:02:25 +03:00
{
var serviceProvider = new XamlServiceProvider ( node , Context ) ;
var visitor = new ApplyPropertiesVisitor ( Context ) ;
foreach ( var cnode in node . Properties . Values . ToList ( ) )
cnode . Accept ( visitor , node ) ;
foreach ( var cnode in node . CollectionItems )
cnode . Accept ( visitor , node ) ;
2016-08-15 23:06:11 +03:00
value = markup . ProvideValue ( serviceProvider ) ;
2016-03-22 23:02:25 +03:00
2016-12-05 00:08:11 +03:00
INode xKey ;
if ( ! node . Properties . TryGetValue ( XmlName . xKey , out xKey ) )
xKey = null ;
2016-03-22 23:02:25 +03:00
node . Properties . Clear ( ) ;
node . CollectionItems . Clear ( ) ;
2016-12-05 00:08:11 +03:00
if ( xKey ! = null )
node . Properties . Add ( XmlName . xKey , xKey ) ;
2016-03-22 23:02:25 +03:00
Values [ node ] = value ;
}
if ( value is BindableObject )
NameScope . SetNameScope ( value as BindableObject , node . Namescope ) ;
}
public void Visit ( RootNode node , INode parentNode )
{
var rnode = ( XamlLoader . RuntimeRootNode ) node ;
Values [ node ] = rnode . Root ;
Context . Types [ node ] = rnode . Root . GetType ( ) ;
2016-04-18 00:59:44 +03:00
var bindableRoot = rnode . Root as BindableObject ;
if ( bindableRoot ! = null )
NameScope . SetNameScope ( bindableRoot , node . Namescope ) ;
2016-03-22 23:02:25 +03:00
}
public void Visit ( ListNode node , INode parentNode )
{
//this is a gross hack to keep ListNode alive. ListNode must go in favor of Properties
XmlName name ;
if ( ApplyPropertiesVisitor . TryGetPropertyName ( node , parentNode , out name ) )
node . XmlName = name ;
}
bool ValidateCtorArguments ( Type nodeType , IElementNode node , out string missingArgName )
{
missingArgName = null ;
var ctorInfo =
nodeType . GetTypeInfo ( )
. DeclaredConstructors . FirstOrDefault (
ci = >
ci . GetParameters ( ) . Length ! = 0 & & ci . IsPublic & &
ci . GetParameters ( ) . All ( pi = > pi . CustomAttributes . Any ( attr = > attr . AttributeType = = typeof ( ParameterAttribute ) ) ) ) ;
if ( ctorInfo = = null )
return true ;
foreach ( var parameter in ctorInfo . GetParameters ( ) )
{
var propname =
parameter . CustomAttributes . First ( ca = > ca . AttributeType . FullName = = "Xamarin.Forms.ParameterAttribute" )
. ConstructorArguments . First ( )
. Value as string ;
if ( ! node . Properties . ContainsKey ( new XmlName ( "" , propname ) ) )
{
missingArgName = propname ;
return false ;
}
}
return true ;
}
public object CreateFromParameterizedConstructor ( Type nodeType , IElementNode node )
{
var ctorInfo =
nodeType . GetTypeInfo ( )
. DeclaredConstructors . FirstOrDefault (
ci = >
ci . GetParameters ( ) . Length ! = 0 & & ci . IsPublic & &
ci . GetParameters ( ) . All ( pi = > pi . CustomAttributes . Any ( attr = > attr . AttributeType = = typeof ( ParameterAttribute ) ) ) ) ;
object [ ] arguments = CreateArgumentsArray ( node , ctorInfo ) ;
return ctorInfo . Invoke ( arguments ) ;
}
public object CreateFromFactory ( Type nodeType , IElementNode node )
{
object [ ] arguments = CreateArgumentsArray ( node ) ;
if ( ! node . Properties . ContainsKey ( XmlName . xFactoryMethod ) )
{
//non-default ctor
return Activator . CreateInstance ( nodeType , arguments ) ;
}
var factoryMethod = ( ( string ) ( ( ValueNode ) node . Properties [ XmlName . xFactoryMethod ] ) . Value ) ;
Type [ ] types = arguments = = null ? new Type [ 0 ] : arguments . Select ( a = > a . GetType ( ) ) . ToArray ( ) ;
2016-09-26 23:40:04 +03:00
Func < MethodInfo , bool > isMatch = m = > {
if ( m . Name ! = factoryMethod )
return false ;
var p = m . GetParameters ( ) ;
if ( p . Length ! = types . Length )
return false ;
if ( ! m . IsStatic )
return false ;
for ( var i = 0 ; i < p . Length ; i + + ) {
if ( ( p [ i ] . ParameterType . IsAssignableFrom ( types [ i ] ) ) )
continue ;
2017-10-18 10:15:11 +03:00
var op_impl = p [ i ] . ParameterType . GetImplicitConversionOperator ( fromType : types [ i ] , toType : p [ i ] . ParameterType )
? ? types [ i ] . GetImplicitConversionOperator ( fromType : types [ i ] , toType : p [ i ] . ParameterType ) ;
2016-09-26 23:40:04 +03:00
if ( op_impl = = null )
return false ;
arguments [ i ] = op_impl . Invoke ( null , new [ ] { arguments [ i ] } ) ;
}
return true ;
} ;
var mi = nodeType . GetRuntimeMethods ( ) . FirstOrDefault ( isMatch ) ;
if ( mi = = null )
throw new MissingMemberException ( $"No static method found for {nodeType.FullName}::{factoryMethod} ({string.Join(" , ", types.Select(t => t.FullName))})" ) ;
2016-03-22 23:02:25 +03:00
return mi . Invoke ( null , arguments ) ;
}
public object [ ] CreateArgumentsArray ( IElementNode enode )
{
if ( ! enode . Properties . ContainsKey ( XmlName . xArguments ) )
return null ;
var node = enode . Properties [ XmlName . xArguments ] ;
var elementNode = node as ElementNode ;
if ( elementNode ! = null )
{
var array = new object [ 1 ] ;
array [ 0 ] = Values [ elementNode ] ;
return array ;
}
var listnode = node as ListNode ;
if ( listnode ! = null )
{
var array = new object [ listnode . CollectionItems . Count ] ;
for ( var i = 0 ; i < listnode . CollectionItems . Count ; i + + )
array [ i ] = Values [ ( ElementNode ) listnode . CollectionItems [ i ] ] ;
return array ;
}
return null ;
}
public object [ ] CreateArgumentsArray ( IElementNode enode , ConstructorInfo ctorInfo )
{
var n = ctorInfo . GetParameters ( ) . Length ;
var array = new object [ n ] ;
for ( var i = 0 ; i < n ; i + + )
{
var parameter = ctorInfo . GetParameters ( ) [ i ] ;
var propname =
parameter . CustomAttributes . First ( attr = > attr . AttributeType = = typeof ( ParameterAttribute ) )
. ConstructorArguments . First ( )
. Value as string ;
var name = new XmlName ( "" , propname ) ;
INode node ;
if ( ! enode . Properties . TryGetValue ( name , out node ) )
{
throw new XamlParseException (
String . Format ( "The Property {0} is required to create a {1} object." , propname , ctorInfo . DeclaringType . FullName ) ,
enode as IXmlLineInfo ) ;
}
if ( ! enode . SkipProperties . Contains ( name ) )
enode . SkipProperties . Add ( name ) ;
var value = Context . Values [ node ] ;
var serviceProvider = new XamlServiceProvider ( enode , Context ) ;
var convertedValue = value . ConvertTo ( parameter . ParameterType , ( ) = > parameter , serviceProvider ) ;
array [ i ] = convertedValue ;
}
return array ;
}
static bool IsXaml2009LanguagePrimitive ( IElementNode node )
{
2017-09-25 09:52:03 +03:00
return node . NamespaceURI = = XamlParser . X2009Uri ;
2016-03-22 23:02:25 +03:00
}
static object CreateLanguagePrimitive ( Type nodeType , IElementNode node )
{
object value = null ;
if ( nodeType = = typeof ( string ) )
value = String . Empty ;
else if ( nodeType = = typeof ( Uri ) )
value = null ;
else
value = Activator . CreateInstance ( nodeType ) ;
if ( node . CollectionItems . Count = = 1 & & node . CollectionItems [ 0 ] is ValueNode & &
( ( ValueNode ) node . CollectionItems [ 0 ] ) . Value is string )
{
var valuestring = ( ( ValueNode ) node . CollectionItems [ 0 ] ) . Value as string ;
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( SByte ) ) {
sbyte retval ;
if ( sbyte . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( Int16 ) ) {
short retval ;
if ( short . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( Int32 ) ) {
int retval ;
if ( int . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( Int64 ) ) {
long retval ;
if ( long . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
}
if ( nodeType = = typeof ( Byte ) ) {
byte retval ;
if ( byte . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
}
if ( nodeType = = typeof ( UInt16 ) ) {
ushort retval ;
if ( ushort . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
}
if ( nodeType = = typeof ( UInt32 ) ) {
uint retval ;
if ( uint . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
}
if ( nodeType = = typeof ( UInt64 ) ) {
ulong retval ;
if ( ulong . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
}
if ( nodeType = = typeof ( Single ) ) {
2016-03-22 23:02:25 +03:00
float retval ;
if ( float . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
2016-09-27 21:10:39 +03:00
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( Double ) ) {
2016-03-22 23:02:25 +03:00
double retval ;
if ( double . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
2016-09-27 21:10:39 +03:00
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( Boolean ) )
2016-03-22 23:02:25 +03:00
{
2016-09-27 21:10:39 +03:00
bool outbool ;
if ( bool . TryParse ( valuestring , out outbool ) )
return outbool ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( TimeSpan ) ) {
TimeSpan retval ;
if ( TimeSpan . TryParse ( valuestring , CultureInfo . InvariantCulture , out retval ) )
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( char ) )
2016-03-22 23:02:25 +03:00
{
2016-09-27 21:10:39 +03:00
char retval ;
if ( char . TryParse ( valuestring , out retval ) )
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
if ( nodeType = = typeof ( string ) )
return valuestring ;
if ( nodeType = = typeof ( decimal ) )
2016-03-22 23:02:25 +03:00
{
2016-09-27 21:10:39 +03:00
decimal retval ;
if ( decimal . TryParse ( valuestring , NumberStyles . Number , CultureInfo . InvariantCulture , out retval ) )
return retval ;
2016-03-22 23:02:25 +03:00
}
2016-09-27 21:10:39 +03:00
2016-03-22 23:02:25 +03:00
else if ( nodeType = = typeof ( Uri ) )
{
Uri retval ;
if ( Uri . TryCreate ( valuestring , UriKind . RelativeOrAbsolute , out retval ) )
2016-09-27 21:10:39 +03:00
return retval ;
2016-03-22 23:02:25 +03:00
}
}
return value ;
}
}
2018-04-20 06:50:47 +03:00
}