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 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())).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; } } }