summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Xaml/CreateValuesVisitor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Xaml/CreateValuesVisitor.cs')
-rw-r--r--Xamarin.Forms.Xaml/CreateValuesVisitor.cs369
1 files changed, 369 insertions, 0 deletions
diff --git a/Xamarin.Forms.Xaml/CreateValuesVisitor.cs b/Xamarin.Forms.Xaml/CreateValuesVisitor.cs
new file mode 100644
index 00000000..96af9189
--- /dev/null
+++ b/Xamarin.Forms.Xaml/CreateValuesVisitor.cs
@@ -0,0 +1,369 @@
+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
+{
+ internal class CreateValuesVisitor : IXamlNodeVisitor
+ {
+ public CreateValuesVisitor(HydratationContext context)
+ {
+ Context = context;
+ }
+
+ Dictionary<INode, object> Values
+ {
+ get { return Context.Values; }
+ }
+
+ HydratationContext Context { get; }
+
+ public bool VisitChildrenFirst
+ {
+ get { return true; }
+ }
+
+ public bool StopOnDataTemplate
+ {
+ get { return true; }
+ }
+
+ public bool StopOnResourceDictionary
+ {
+ get { return false; }
+ }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ Values[node] = node.Value;
+
+ XmlName propertyName;
+ if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName))
+ {
+ if (propertyName.NamespaceURI == "http://schemas.openxmlformats.org/markup-compatibility/2006" &&
+ propertyName.LocalName == "Ignorable")
+ {
+ (parentNode.IgnorablePrefixes ?? (parentNode.IgnorablePrefixes = new List<string>())).AddRange(
+ (node.Value as string).Split(','));
+ }
+ }
+ }
+
+ public void Visit(MarkupNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(ElementNode node, INode parentNode)
+ {
+ object value = null;
+
+ if (node.SkipPrefix(node.NamespaceResolver.LookupPrefix(node.NamespaceURI)))
+ return;
+
+ XamlParseException xpe;
+ var type = XamlParser.GetElementType(node.XmlType, node, Context.RootElement.GetType().GetTypeInfo().Assembly,
+ 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))
+ {
+ throw new XamlParseException(
+ String.Format("The Property {0} is required to create a {1} object.", ctorargname, type.FullName), node);
+ }
+ 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;
+
+ var typeExtension = value as TypeExtension;
+ if (typeExtension != null)
+ {
+ 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);
+
+ value = typeExtension.ProvideValue(serviceProvider);
+
+ node.Properties.Clear();
+ node.CollectionItems.Clear();
+
+ 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();
+ NameScope.SetNameScope(rnode.Root as BindableObject, node.Namescope);
+ }
+
+ 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();
+ var mi = nodeType.GetRuntimeMethod(factoryMethod, types);
+ if (mi == null || !mi.IsStatic)
+ {
+ throw new MissingMemberException(String.Format("No static method found for {0}::{1} ({2})", nodeType.FullName,
+ factoryMethod, string.Join(", ", types.Select(t => t.FullName))));
+ }
+ 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)
+ {
+ return node.NamespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml";
+ }
+
+ 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;
+
+ if (nodeType == typeof (bool))
+ {
+ bool outbool;
+ if (bool.TryParse(valuestring, out outbool))
+ value = outbool;
+ }
+ else if (nodeType == typeof (char))
+ {
+ char retval;
+ if (char.TryParse(valuestring, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (string))
+ value = valuestring;
+ else if (nodeType == typeof (decimal))
+ {
+ decimal retval;
+ if (decimal.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (float))
+ {
+ float retval;
+ if (float.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (double))
+ {
+ double retval;
+ if (double.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (byte))
+ {
+ byte retval;
+ if (byte.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (short))
+ {
+ short retval;
+ if (short.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (int))
+ {
+ int retval;
+ if (int.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (long))
+ {
+ long retval;
+ if (long.TryParse(valuestring, NumberStyles.Number, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (TimeSpan))
+ {
+ TimeSpan retval;
+ if (TimeSpan.TryParse(valuestring, CultureInfo.InvariantCulture, out retval))
+ value = retval;
+ }
+ else if (nodeType == typeof (Uri))
+ {
+ Uri retval;
+ if (Uri.TryCreate(valuestring, UriKind.RelativeOrAbsolute, out retval))
+ value = retval;
+ }
+ }
+ return value;
+ }
+ }
+} \ No newline at end of file